12/01/2009

Scripting with SSH

I have a script to write where I need to run some of the commands on another host.  The script users will be interacting with a menu and running some commands on the local host and some on a remote host.  SSH is a great way to run those commands on the remote host, but each time my script comes to one of the ssh commands, I must re-enter the password.  I could set up SSH keys so it works without a password, but I'm making the script available to several people.  Some use keys, and some do not.  I don't want set up of SSH keys to be a prerequisite of running the script, and I also don't want the users to have to type their password a hundred times. 

So I studied the situation and found the ControlMaster option of ssh.  It allows you to establish a session in SSH, then use the session to run multiple commands.  There are a lot of pages on the web talking about how to use this feature, but nothing really good describing how to script with it.  So this is what I came up with:

#!/bin/ksh

# This script demonstrates use of an ssh control master process.  The script
# connects via ssh to a remote host and authenticates once.  Then the session
# is re-used repeatedly for the remainder of the script without prompts for
# the ssh password appearing again.  Also works when using public key
# authentication.  The script runs much faster when using a control master
# process because each ssh does not need to be re-authenticated.  And public
# key authentication can be optional for the script users in a script that
# would otherwise have excessive repeated password prompts.

PATH=/usr/xpg4/bin:/usr/bin:/bin
REMHOST=ndm@john

# start the control master process for this shell instance
ssh -o ControlPath=~/.ssh/master-$$ -o ControlMaster=auto -f -N $REMHOST 2>/dev/null 1>&2
if [ $? -gt 0 ]; then
  echo "ERROR: error authenticating to ssh server"
  exit 1
fi

# example additional ssh processes that use the established session
ssh -o ControlPath=~/.ssh/master-$$ $REMHOST id
ssh -o ControlPath=~/.ssh/master-$$ $REMHOST hostname
ssh -o ControlPath=~/.ssh/master-$$ $REMHOST hostid
ssh -o ControlPath=~/.ssh/master-$$ $REMHOST who

# end the current control master process
PROC=`ps -ef |grep "ssh -o ControlPath=\~/.ssh/master-$$" | grep -v grep | awk '{print $2}'`
if [ -n "$PROC" ]; then
  kill $PROC
fi

# housekeeping
# find socket files over 24 hours old and kill the associated processes
# for each socket file, do loop
find ~/.ssh -name "master-*" -type s -mtime +0 | while read FILEN; do
  FILEB=`basename $FILEN`
  # kill process associated with socket file, if process running
  PROC=`ps -ef |grep "ssh -o ControlPath=\~/.ssh/$FILEB" | grep -v grep | awk '{print $2}'`
  if [ -n "$PROC" ]; then
    kill $PROC
  fi
  sleep 1
  # if the socket file still exists, find out if that is because the process
  # did not die, or if it is a stale socket file that just needs to be deleted
  if [ -f $FILEB ]; then # if socket file still exists deal with it
    PROC=`ps -ef |grep "ssh -o ControlPath=\~/.ssh/$FILEB" | grep -v grep | awk '{print $2}'`
    if [ -z "$PROC" ]; then
      rm $FILEB # remove stale socket file
   else
      echo ERROR: process $PROC associated with socket file $FILEB did not die
    fi
  fi
done

# end of script


I don't expect the housekeeping section to actually find anything to clean up very often, but it sure will be nice of the script to clean up after itself if it finds something left over from previous days.

If anybody knows a more graceful way to end the control master process, I would like to see it.  It would be great if processes started with ssh -f would die when the shell that invoked them reached the end, or if the process number for the backgrounded process was available in $! or some other variable.