Subject: HOWTO run ssh-agent as an NT/2K service
From: Борис Смирнов <[email-masked]>
To: [email-masked]
Date: Thu, 1 Aug 2002 19:00:17 +0600

Hello

There is a problem when running ssh-agent from a shell prompt: when you
logout from that shell the console window won't close until one kills
the agent.
The only solution that comes in mind is running ssh-agent as a service
(on Windows NT/2K platforms).
I've found two discussions on cygwin mailing list dedicated to this
subject here
http://www.cygwin.com/ml/cygwin-patches/2001-q1/msg00184.html
and here
http://www.cygwin.com/ml/cygwin/2002-05/msg01305.html,
but in the end I came with my own solution. Read on...

Starting an ssh-agent as a service is pretty easy:
cygrunsrv -I "ssh-agent-$USER" -d "CYGWIN ssh-agent for $USER" \
          -p /usr/bin/ssh-agent -a "$S_OR_C_FLAG -d" \
          -t manual -1 ~/.ssh-agent -o -u $USER
assuming the following requirements are met:
1) CYGWIN envronment variable contains ntsec option for valid socket permissions
2) A USER has "Log on as service" privilege
3) if cygrunsrv is run from startup script, a user, probably needs
administrative privileges to be able to install/start a service.
However, it is possible for a system administrator to install as many
instances of ssh-agent services as needed and configure them to start
automatically (see the "-t" option of cygrunsrv) during system boot,
thus not requiring additional priviliges for users other then "Log on
as service" (this is in theory, I have not tried this configuration).

Now, when we started an ssh-agent service the next problem is how
to carry the information about a socket, agent listens to, to
applications. We need to set SSH_AUTH_SOCK environment variable somehow.
The easy but not reliable way:
    SSH_AUTH_SOCK=`find /tmp/ssh-* -type s -user $USER -perm 600 | tail -n 1`; export SSH_AUTH_SOCK
The reliable way to set SSH_AUTH_SOCK is only via ssh-agent output,
and this requires trivial patching of openssh sources. The problem is
that ssh-agent output to stdout is buffered, thus "-1 ~/.ssh-agent"
option of cygrunsrv makes empty file. Inserting call to fflush(stdout)
to the right place of ssh-agent.c (line 1070 for openssh-3.4p1,
before goto statement) does the trick.
The rest is pure shell scripting.

here is the patch
-8<----------------8<--------------8<--
diff -u openssh-3.4p1/ssh-agent.c openssh-3.4p1-patched/ssh-agent.c
--- openssh-3.4p1/ssh-agent.c   2002-06-26 05:19:13.000000000 +0600
+++ openssh-3.4p1-patched/ssh-agent.c   2002-07-31 14:09:16.000000000 +0600
@@ -1067,6 +1067,7 @@
                printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name,
                    SSH_AUTHSOCKET_ENV_NAME);
                printf("echo Agent pid %ld;\n", (long)parent_pid);
+               fflush(stdout);
                goto skip;
        }
        pid = fork();
-8<----------------8<--------------8<--

and the script I source from .bash_profile
it lacks one important feature - locking, to prevent simultanious
execution when several concurent consoles are starting
any ideas on how to implement it will be appreciated
-8<----------------8<--------------8<--
# start ssh-agent as a service and load private keys

unset SSH_AUTH_SOCK
unset SSH_AGENT_PID
SSH_AGENT=/usr/bin/ssh-agent
SSH_AGENT_OPT="-s -d"
SSH_AGENT_FILE="$HOME/.ssh-agent-$HOSTNAME"
SSH_ADD=/usr/bin/ssh-add
SSH_ADD_OPT=""
SSH_IDENTITIES=""

# Check for initial state:
# 1. there is only one instance of ssh-agent for the user
# 2. SSH_AGENT_FILE exists and the last string containing SSH_AUTH_SOCK
#    matches running ssh-agent
# 3. the soket exists, its path matches ssh-agent's PID, and permissions are correct
#
# If any condition fails, reset to correct state:
# 1. kill all instances of ssh-agent
# 2. delete SSH_AGENT_FILE
# 3. start ssh-agent service and create new SSH_AGENT_FILE
#
# When correct initial state is achived, set and export SSH_AUTH_SOCK
# and SSH_AGENT_PID variables, add keys

ensure_ssh_agent_started()
{
if [ `ps x | grep 'ssh-agent$' | wc -l` -ne 1 -o ! -f "$SSH_AGENT_FILE" ] ; then
        for pid in `ps x | awk '/ssh-agent$/ { print $1 }'` ; do
                kill $pid
        done
        rm -f "$SSH_AGENT_FILE"
fi

if [ `ps x | grep 'ssh-agent$' | wc -l` -eq 0 ] ; then
        echo Installing service ssh-agent-$USER...
        # this command will ask you for a user password
        # it is required to install the service that runs on
        # behalf of a user
        cygrunsrv -I "ssh-agent-$USER" -d "CYGWIN ssh-agent for $USER" \
                -p "$SSH_AGENT" -a "$SSH_AGENT_OPT" -t manual -u "$USER" \
                -o -1 "$SSH_AGENT_FILE" 

        echo Starting service ssh-agent-$USER...
        if ! cygrunsrv -S "ssh-agent-$USER" ; then
                echo Failed to start ssh-agent-$USER service
                return 1
        else
                SSH_AGENT_STARTED=1
        fi
fi
return 0
}

validate_ssh_agent_and_load_keys()
{
SSH_AGENT_PID=`ps x | awk '/ssh-agent$/ { print $1 }'`
eval `grep '^SSH_AUTH_SOCK' "$SSH_AGENT_FILE" 2>/dev/null | tail -n 1`

if test -n "$SSH_AGENT_PID" -a -n "$SSH_AUTH_SOCK" &&
   expr match "$SSH_AUTH_SOCK" "/tmp/ssh-[A-Za-z]*$SSH_AGENT_PID/agent.$SSH_AGENT_PID" 1>/dev/null &&
   find `dirname $SSH_AUTH_SOCK` -type s -perm 600 -user "$USER" 1>/dev/null 2>/dev/null ; then
        export SSH_AGENT_PID
        if [ -n "$SSH_AGENT_STARTED" ] ; then
                echo Adding keys to the agent
                $SSH_ADD $SSH_ADD_OPT $SSH_IDENTITIES
        fi
else
        unset SSH_AUTH_SOCK
        unset SSH_AGENT_PID
fi
}

ensure_ssh_agent_started && validate_ssh_agent_and_load_keys

unset ensure_ssh_agent_started
unset validate_ssh_agent_and_load_keys
unset SSH_AGENT
unset SSH_AGENT_OPT
unset SSH_AGENT_FILE
unset SSH_ADD
unset SSH_ADD_OPT
unset SSH_IDENTITIES
unset SSH_AGENT_STARTED
-8<----------------8<--------------8<--

-- 
Best regards,
 Boris

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Bug reporting:         http://cygwin.com/bugs.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/