]> thyrsus.com Git - ssh-installkeys.git/commitdiff
Initial revision
authorEric S. Raymond <esr@thyrsus.com>
Fri, 22 Nov 2002 11:26:34 +0000 (11:26 +0000)
committerEric S. Raymond <esr@thyrsus.com>
Fri, 22 Nov 2002 11:26:34 +0000 (11:26 +0000)
ssh-installkeys [new file with mode: 0755]

diff --git a/ssh-installkeys b/ssh-installkeys
new file mode 100755 (executable)
index 0000000..d3a71a2
--- /dev/null
@@ -0,0 +1,865 @@
+#!/usr/bin/env python
+"""sshprep -- prepare a remote account for ssh access
+
+This script tries to export ssh public keys to a specified site.  it will
+walk the user through generating key pairs if it doesn't find any to export.
+It handles all the fiddly details like making sure local and remote
+permissions are correct.
+
+Options:
+
+-h        Print usage summary and exit
+-l login  Specify login name to be used at the remote site (default '%s')
+-c        Check local and remote configuration only, dn't try to fix problems.
+-v        Report all commands sent to host and output received.
+"""
+
+# Conditions we don't check yet:
+# 1. Port 22 isn't taking connections
+# 2. User has no account on machine.
+
+import os, sys, getopt, string, getpass
+
+###############################################################################
+#
+# pexpect 0.94 is inlined here because it's not in the Python library yet.
+# Some comments have been removed.
+#
+
+"""
+Pexpect is a Python module for spawning child applications;
+controlling them; and responding to expected patterns in their output.
+Pexpect can be used for automating interactive applications such as
+ssh, ftp, passwd, telnet, etc. It can be used to a automate setup scripts
+for duplicating software package installations on different servers.
+It can be used for automated software testing. Pexpect is in the spirit of
+Don Libes' Expect, but Pexpect is pure Python. Other Expect-like
+modules for Python require TCL and Expect or require C extensions to
+be compiled. Pexpect does not use C, Expect, or TCL extensions. It
+should work on any platform that supports the standard Python pty
+module. The Pexpect interface focuses on ease of use so that simple
+tasks are easy.
+
+Pexpect is Open Source, free, and all that stuff.
+License: Python Software Foundation License
+         http://www.opensource.org/licenses/PythonSoftFoundation.html
+
+Noah Spurrier
+
+$Revision: 1.35 $
+$Date: 2002/10/22 21:16:08 $
+"""
+import os, sys
+import select
+# import signal
+import traceback
+import re
+import struct
+import resource
+from types import *
+try:
+    import pty
+    import tty
+    import termios
+    import fcntl
+except ImportError, e:
+    raise ImportError, str(e) + """
+A critical module was not found. Probably this OS does not support it.
+Currently pexpect is intended for UNIX operating systems (including OS-X)."""
+
+__version__ = '0.94'
+__revision__ = '$Revision: 1.35 $'
+__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn',
+    '__version__', '__revision__']
+
+# Exception classes used by this module.
+class ExceptionPexpect(Exception):
+    """Base class for all exceptions raised by this module."""
+    def __init__(self, value):
+        self.value = value
+    def __str__(self):
+        return `self.value`
+class EOF(ExceptionPexpect):
+    """Raised when EOF is read from a child."""
+class TIMEOUT(ExceptionPexpect):
+    """Raised when a read time exceeds the timeout."""
+##class MAXBUFFER(ExceptionPexpect):
+##    """Raised when a scan buffer fills before matching an expected pattern."""
+
+class spawn:
+    """This is the main class interface for Pexpect. Use this class to
+    start and control child applications.
+    """
+
+    def __init__(self, command, args=None, timeout=30):
+        """This is the constructor. The command parameter is a string
+        that includes the path and any arguments to the command. For example:
+            p = pexpect.spawn ('/usr/bin/ftp')
+            p = pexpect.spawn ('/usr/bin/ssh some@host.com')
+            p = pexpect.spawn ('/bin/ls -latr /tmp')
+        You may also construct it with a list of arguments like so:
+            p = pexpect.spawn ('/usr/bin/ftp', [])
+            p = pexpect.spawn ('/usr/bin/ssh', ['some@host.com'])
+            p = pexpect.spawn ('/bin/ls', ['-latr', '/tmp'])
+        After this the child application will be created and
+        will be ready for action. See expect() and send()/sendline().
+        """
+
+        self.STDIN_FILENO = sys.stdin.fileno()
+        self.STDOUT_FILENO = sys.stdout.fileno()
+        self.STDERR_FILENO = sys.stderr.fileno()
+
+        ### IMPLEMENT THIS FEATURE!!!
+        #self.maxbuffersize = 10000
+        # anything before maxsearchsize point is preserved, but not searched.
+        #self.maxsearchsize = 1000
+
+        self.timeout = timeout
+        self.child_fd = -1
+        self.pid = None
+        self.log_file = None    
+        self.before = None
+        self.after = None
+        self.match = None
+
+        if args is None:
+            self.args = split_command_line(command)
+            self.command = self.args[0]
+        else:
+            args.insert (0, command)
+            self.args = args
+            self.command = command
+
+        self.__spawn()
+
+    def __del__(self):
+        """This makes sure that no system resources are left open.
+        Python only garbage collects Python objects. Since OS file descriptors
+        are not Python objects, so they must be handled manually.
+        """
+       self.close()
+
+    def __spawn(self):
+        """This starts the given command in a child process. This does
+        all the fork/exec type of stuff for a pty. This is called by
+        __init__. The args parameter is a list, command is a string.
+        """
+        # The pid and child_fd of this object get set by this method.
+        # Note that it is difficult for this method to fail.
+        # You cannot detect if the child process cannot start.
+        # So the only way you can tell if the child process started
+        # or not is to try to read from the file descriptor. If you get
+        # EOF immediately then it means that the child is already dead.
+        # That may not necessarily be bad, because you may spawn a child
+        # that performs some operator, creates no stdout output, and then dies.
+        # It is a fuzzy edge case. Any child process that you are likely to
+        # want to interact with Pexpect would probably not fall into this
+        # category.
+        # FYI, This essentially does a fork/exec operation.
+
+        assert self.pid == None, 'The pid member is not None.'
+        assert self.command != None, 'The command member is None.'
+
+        if which(self.command) == None:
+            raise ExceptionPexpect ('The command was not found or was not executable: %s.' % self.command)
+# I'm no longer using this technique to test if a process is alive.
+# First, it is not portable.
+# Second, it messes up other subsystems that use signals (such as pipes).
+# Third, signals on UNIX suck.
+#        # This is necessary for isAlive() to work. Without this there is
+#        # no portable way to tell if a child process is a zombie.
+#        # Checking waitpid with WNOHANG option does not work and
+#        # checking waitpid without it would block if the child is not a zombie.
+#        # With this children should exit completely without going into
+#        # a zombie state. Note that some UNIX flavors may send the signal
+#        # before the child's pty output buffer is empty, while others
+#        # may send the signal only when the buffer is empty.
+#        # In the later case, isAlive() will always return true until the
+#        # output buffer is empty. Use expect_eof() to consume all child output.
+#        # This is not the same as the Zombie (waiting to die) problem.
+#         #signal.signal(signal.SIGCHLD, signal.SIG_IGN)
+
+        try:
+            self.pid, self.child_fd = pty.fork()
+        except OSError, e:
+            raise ExceptionPexpect('Pexpect: pty.fork() failed: ' + str(e))
+
+        if self.pid == 0: # Child
+            setwinsize(24, 80)
+            # Do not allow child to inherit open file descriptors from parent.
+            max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
+            for i in range (3, max_fd):
+                try:
+                    os.close (i)
+                except OSError:
+                    pass
+
+            os.execvp(self.command, self.args)
+            raise ExceptionPexpect ('Reached an unexpected state in __spawn().')
+
+        # Parent
+
+    def fileno (self):
+        """This returns the file descriptor of the pty for the child."""
+        return self.child_fd
+
+    def close (self):
+        """This is experimental.
+       This closes the file descriptor of the child application.
+       It makes no attempt to actually kill the child or wait for its status.
+       """
+       if self.child_fd != -1:
+           os.close (self.child_fd)
+           self.child_fd = -1
+
+    def set_echo (self, on):
+        """This sets the terminal echo-mode on or off."""
+        new = termios.tcgetattr(self.child_fd)
+        if on:
+            new[3] = new[3] | termios.ECHO # lflags
+        else:
+            new[3] = new[3] & ~termios.ECHO # lflags
+        termios.tcsetattr(self.child_fd, termios.TCSADRAIN, new)
+
+    def setlog (self, fileobject):
+        """This sets logging output to go to the given fileobject.
+        Set fileobject to None to stop logging.
+        """
+        self.log_file = fileobject
+
+    def compile_pattern_list(self, pattern):
+        """This compiles a pattern-string or a list of pattern-strings.
+        This is used by expect() when calling expect_list().
+        Thus expect() is nothing more than::
+             cpl = self.compile_pattern_list(pl)
+             return self.expect_list(clp, timeout)
+
+        If you are using expect() within a loop it may be more
+        efficient to compile the patterns first and call expect_list():
+             cpl = self.compile_pattern_list(my_pattern)
+             while some_condition:
+                ...
+                i = self.expect_list(clp, timeout)
+                ...
+        """
+        if pattern is EOF:
+            compiled_pattern_list = [EOF]
+        elif type(pattern) is StringType:
+            compiled_pattern_list = [re.compile(pattern)]
+        elif type(pattern) is ListType:
+            compiled_pattern_list = []
+            for x in pattern:
+                if x is EOF:
+                    compiled_pattern_list.append(EOF)
+                else:
+                    compiled_pattern_list.append( re.compile(x) )
+        else:
+            raise TypeError, 'Pattern argument must be a string or a list of strings.'
+
+        return compiled_pattern_list
+ 
+    def expect(self, pattern, timeout = None):
+        """This seeks through the stream looking for the given
+        pattern. The 'pattern' can be a string or a list of strings.
+        The strings are regular expressions (see module 're'). This
+        returns the index into the pattern list or raises an exception
+        on error.
+
+        After a match is found the instance attributes
+        'before', 'after' and 'match' will be set.
+        You can see all the data read before the match in 'before'.
+        You can see the data that was matched in 'after'.
+        The re.MatchObject used in the re match will be in 'match'.
+        If an error occured then 'before' will be set to all the
+        data read so far and 'after' and 'match' will be None.
+
+        Note: A list entry may be EOF instead of a string.
+        This will catch EOF exceptions and return the index
+        of the EOF entry instead of raising the EOF exception.
+        The attributes 'after' and 'match' will be None.
+        This allows you to write code like this:
+                index = p.expect (['good', 'bad', pexpect.EOF])
+                if index == 0:
+                        do_something()
+                elif index == 1:
+                        do_something_else()
+                elif index == 2:
+                        do_some_other_thing()
+        instead of code like this:
+                try:
+                        index = p.expect (['good', 'bad'])
+                        if index == 0:
+                                do_something()
+                        elif index == 1:
+                                do_something_else()
+                except EOF:
+                        do_some_other_thing()
+        These two forms are equivalent. It all depends on what you want.
+        You can also just expect the EOF if you are waiting for all output
+        of a child to finish. For example:
+                p = pexpect.spawn('/bin/ls')
+                p.expect (pexpect.EOF)
+                print p.before
+        """
+        compiled_pattern_list = self.compile_pattern_list(pattern)
+        return self.expect_list(compiled_pattern_list, timeout)
+
+    def expect_exact (self, pattern_list, local_timeout = None):
+        """This is similar to expect() except that it takes
+        list of regular strings instead of compiled regular expressions.
+        The idea is that this should be much faster. It could also be
+        useful when you don't want to have to worry about escaping
+        regular expression characters that you want to match.
+        You may also pass just a string without a list and the single
+        string will be converted to a list.
+        """
+        ### This is dumb. It shares most of the code with expect_list.
+        ### The only different is the comparison method and that
+        ### self.match is always None after calling this.
+
+        if type(pattern_list) is StringType:
+            pattern_list = [pattern_list]
+
+        try:
+            incoming = ''
+            while 1: # Keep reading until exception or return.
+                c = self.read(1, local_timeout)
+                incoming = incoming + c
+
+                # Sequence through the list of patterns and look for a match.
+                index = -1
+                for str_target in pattern_list:
+                    index = index + 1
+                    match_index = incoming.find (str_target)
+                    if match_index >= 0:
+                        self.before = incoming [ : match_index]
+                        self.after = incoming [match_index : ]
+                        self.match = None
+                        return index
+        except EOF, e:
+            if EOF in pattern_list:
+                self.before = incoming
+                self.after = EOF
+                return pattern_list.index(EOF)
+            else:
+                raise
+        except Exception, e:
+            self.before = incoming
+            self.after = None
+            self.match = None
+            raise
+            
+    def expect_list(self, pattern_list, local_timeout = None):
+        """This is called by expect(). This takes a list of compiled
+        regular expressions. This returns the index into the pattern_list
+        that matched the child's output.
+
+        """
+
+        if local_timeout is None: 
+            local_timeout = self.timeout
+        
+        try:
+            incoming = ''
+            while 1: # Keep reading until exception or return.
+                c = self.read(1, local_timeout)
+                incoming = incoming + c
+
+                # Sequence through the list of patterns and look for a match.
+                index = -1
+                for cre in pattern_list:
+                    index = index + 1
+                    if cre is EOF: 
+                        continue # The EOF pattern is not a regular expression.
+                    match = cre.search(incoming)
+                    if match is not None:
+                        self.before = incoming[ : match.start()]
+                        self.after = incoming[match.start() : ]
+                        self.match = match
+                        return index
+        except EOF, e:
+            if EOF in pattern_list:
+                self.before = incoming
+                self.after = EOF
+                return pattern_list.index(EOF)
+            else:
+                raise
+        except Exception, e:
+            self.before = incoming
+            self.after = None
+            self.match = None
+            raise
+            
+#    def expect_eof(self, timeout = None):
+#        """This reads from the child until the end of file is found.
+#        A timeout exception may be thrown.
+#        """
+#        if timeout is None: 
+#            timeout = self.timeout
+#
+#        incoming = ''
+#        self.match = self.matched = None
+#        try:
+#            while 1:
+#                c = self.read(1, timeout)
+#                incoming = incoming + c
+#        except EOF:
+#            self.before = incoming
+#            return 0
+#        except:  # save incoming data (usefull for debugging)
+#            self.before = incoming
+#            raise
+
+    def read(self, n, timeout = None):
+        """This reads up to n characters from the child application.
+        It includes a timeout. If the read does not complete within the
+        timeout period then a TIMEOUT exception is raised.
+        If the end of file is read then an EOF exception will be raised.
+        If a log file was set using setlog() then all data will
+        also be written to the log file.
+
+        Notice that if this method is called with timeout=None 
+        then it actually may block.
+
+        This is a non-blocking wrapper around os.read().
+        It uses select.select() to supply a timeout. 
+
+        """
+        # Note that some systems like Solaris don't seem to ever give
+        # an EOF when the child dies. In fact, you can still try to read
+        # from the child_fd -- it will block forever or until TIMEOUT.
+        # For this case, I test isalive() before doing any reading.
+        # If isalive() is false, then I pretend that this is the same as EOF.
+        if not self.isalive():
+            r, w, e = select.select([self.child_fd], [], [], 0)
+            if not r:
+                self.flag_eof = 1
+                raise EOF ('End Of File (EOF) in read(). Braindead platform.')
+        
+        r, w, e = select.select([self.child_fd], [], [], timeout)
+        if not r:
+            raise TIMEOUT('Timeout exceeded in read().')
+
+        if self.child_fd in r:
+            try:
+                s = os.read(self.child_fd, n)
+            except OSError, e:
+                self.flag_eof = 1
+                raise EOF('End Of File (EOF) in read(). Exception style platform.')
+            if s == '':
+                self.flag_eof = 1
+                raise EOF('End Of File (EOF) in read(). Empty string style platform.')
+            
+            if self.log_file != None:
+                self.log_file.write (s)
+                self.log_file.flush()
+                
+            return s
+
+        raise ExceptionPexpect('Reached an unexpected state in read().')
+
+    def write(self, text):
+        """This is an alias for send().
+        This return the number of bytes actually written.
+        """
+        return self.send (text)
+
+    def writelines (self, sequence):
+        """This calls write() for each element in the sequence.
+        This return the number of bytes actually written.
+        """
+        total_n = 0
+        for str in sequence:
+            total_n = total_n + self.write (str)
+        return total_n
+
+    def send(self, text):
+        """This sends a string to the child process.
+        This return the number of bytes actually written.
+        """
+        return os.write(self.child_fd, text)
+
+    def sendline(self, text=''):
+        """This is like send(), but it adds a line separator.
+        This return the number of bytes actually written.
+        """
+        n = self.send(text)
+        return n + self.send(os.linesep)
+
+    def send_eof(self):
+        """This sends an EOF to the child.
+
+        More precisely: this sends a character which causes the pending
+        child buffer to be sent to the waiting user program without
+        waiting for end-of-line. If it is the first character of the
+        line, the read() in the user program returns 0, which
+        signifies end-of-file.
+
+        This means: to make this work as expected a send_eof() has to be
+        called at the begining of a line. A newline character is not
+        send here to avoid problems.
+        """
+        ### Hmmm... how do I send an EOF?
+        ###C  if ((m = write(pty, *buf, p - *buf)) < 0)
+        ###C      return (errno == EWOULDBLOCK) ? n : -1;
+
+        fd = sys.stdin.fileno()
+        old = termios.tcgetattr(fd) # remember current state
+        new = termios.tcgetattr(fd)
+        new[3] = new[3] | termios.ICANON        # lflags
+        # use try/finally to ensure state gets restored
+        try:
+            # EOF is recognized when ICANON is set, thus ensure it is:
+            termios.tcsetattr(fd, termios.TCSADRAIN, new)
+            os.write(self.child_fd, termios.CEOF)
+        finally:
+            termios.tcsetattr(fd, termios.TCSADRAIN, old) # restore state
+        
+    def isalive(self):
+        """This tests if the child process is running or not.
+        This returns 1 if the child process appears to be running or 0 if not.
+        I can't figure out how to check if a process is alive on Solaris.
+        This is what is wrong. Apparently the call:
+            os.waitpid(self.pid, os.WNOHANG)
+        does not work. It always returns with (0,0) whether the process
+        is running or defunct. This works:
+            os.waitpid(self.pid, 0)
+        but it blocks if the process IS alive, so it's useless for me.
+        I don't want to use signals. Signals on UNIX suck and they
+        mess up Python pipes (setting SIGCHLD to SIGIGNORE).
+        """
+        try:
+            pid, status = os.waitpid(self.pid, os.WNOHANG)
+        except OSError, e:
+            return 0
+### This does not work on Solaris. Grrr!!!
+#        if pid == self.pid and status == 0:
+        if status == 0:
+            return 1
+
+#        if pid == 0 and status == 0: # This happens on Solaris...
+#            # This means (I think) that the Solaris process is "defunct"
+#            # and a second wait must be called without the WNOHANG.
+#            # This is dangerous because if I am wrong then this could block.
+#            pid, status = os.waitpid (self.pid, 0)
+
+        # I didn't OR this together because I want hooks for debugging.
+        if os.WIFEXITED (status):
+            return 0
+        elif os.WIFSTOPPED (status):
+            return 0
+        elif os.WIFSIGNALED (status):
+            return 0
+        else:
+            return 1
+
+    def kill(self, sig):
+        """This sends the given signal to the child application.
+        In keeping with UNIX tradition it has a misleading name.
+        It does not necessarily kill the child unless
+        you send the right signal.
+        """
+        # Same as os.kill, but the pid is given for you.
+        os.kill(self.pid, sig)
+
+    def interact(self, escape_character = chr(29)):
+        """This gives control of the child process to the interactive user.
+        Keystrokes are sent to the child process, and the stdout and stderr
+        output of the child process is printed.
+        When the user types the escape_character this method will stop.
+        The default for escape_character is ^] (ASCII 29).
+        This simply echos the child stdout and child stderr to the real
+        stdout and it echos the real stdin to the child stdin.
+        """
+        mode = tty.tcgetattr(self.STDIN_FILENO)
+        tty.setraw(self.STDIN_FILENO)
+        try:
+            self.__interact_copy(escape_character)
+        finally:
+            tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode)
+
+    def __interact_writen(self, fd, data):
+        """This is used by the interact() method.
+        """
+        while data != '' and self.isalive():
+            n = os.write(fd, data)
+            data = data[n:]
+    def __interact_read(self, fd):
+        """This is used by the interact() method.
+        """
+        return os.read(fd, 1000)
+    def __interact_copy(self, escape_character = None):
+        """This is used by the interact() method.
+        """
+        while self.isalive():
+            r, w, e = select.select([self.child_fd, self.STDIN_FILENO], [], [])
+            if self.child_fd in r:
+                data = self.__interact_read(self.child_fd)
+                os.write(self.STDOUT_FILENO, data)
+            if self.STDIN_FILENO in r:
+                data = self.__interact_read(self.STDIN_FILENO)
+                self.__interact_writen(self.child_fd, data)
+                if escape_character in data:
+                    break
+
+
+##    def send_human(self, text, delay_min = 0, delay_max = 1):
+##        pass
+##    def spawn2(self, command, args):
+##        """return pid, fd_stdio, fd_stderr
+##        """
+##        pass
+##    def expect_ex(self, string_match, local_timeout = None):
+##        """This is like expect(), except that instead of regular expression patterns
+##        it matches on exact strings.
+##        """
+##        pass
+##        # Return (data_read)
+
+
+def which (filename):
+    """This takes a given filename and tries to find it in the
+    environment path and check if it is executable.
+    """
+
+    # Special case where filename already contains a path.
+    if os.path.dirname(filename) != '':
+        if os.access (filename, os.X_OK):
+            return filename
+
+    if not os.environ.has_key('PATH') or os.environ['PATH'] == '':
+        p = os.defpath
+    else:
+        p = os.environ['PATH']
+
+    pathlist = p.split (os.pathsep)
+
+    for path in pathlist:
+        f = os.path.join(path, filename)
+        if os.access(f, os.X_OK):
+            return f
+    return None
+
+def setwinsize(r, c):
+    """This sets the windowsize of the tty for stdout.
+    This does not change the physical window size.
+    It changes the size reported to TTY-aware applications like
+    vi or curses. In other words, applications that respond to the
+    SIGWINCH signal.
+    This is used by __spawn to set the tty window size of the child.
+    """
+    # Check for buggy platforms. Some Pythons on some platforms
+    # (notably OSF1 Alpha and RedHat 7.1) truncate the value for
+    # termios.TIOCSWINSZ. It is not clear why this happens.
+    # These platforms don't seem to handle the signed int very well;
+    # yet other platforms like OpenBSD have a large negative value for
+    # TIOCSWINSZ and they don't truncate.
+    # Newer versions of Linux have totally different values for TIOCSWINSZ.
+    # Note, this fix is a hack.
+    TIOCSWINSZ = termios.TIOCSWINSZ
+    if TIOCSWINSZ == 2148037735L: # L is not required in Python 2.2.
+        TIOCSWINSZ = -2146929561 # Same number in binary, but with sign.
+
+    # Assume ws_xpixel and ws_ypixel are zero.
+    s = struct.pack("HHHH", r, c, 0, 0)
+    x = fcntl.ioctl(sys.stdout.fileno(), TIOCSWINSZ, s)
+
+def getwinsize():
+    s = struct.pack("HHHH", 0, 0, 0, 0)
+    x = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
+    return struct.unpack("HHHH", x)
+
+def split_command_line(command_line):
+    """This splits a command line into a list of arguments.
+    It splits arguments on spaces, but handles
+    embedded quotes, doublequotes, and escaped characters.
+    I couldn't do this with a regular expression, so
+    I wrote a little state machine to parse the command line.
+    """
+    arg_list = []
+    arg = ''
+    state_quote = 0
+    state_doublequote = 0
+    state_esc = 0
+    for c in command_line:
+        if c == '\\': # Escape the next character
+            state_esc = 1
+        elif c == r"'": # Handle single quote
+            if state_esc:
+                state_esc = 0
+            elif not state_quote:
+                state_quote = 1
+            else:
+                state_quote = 0
+        elif c == r'"': # Handle double quote
+            if state_esc:
+                state_esc = 0
+            elif not state_doublequote:
+                state_doublequote = 1
+            else:
+                state_doublequote = 0
+
+        # Add arg to arg_list unless in some other state.
+        elif c == ' 'and not state_quote and not state_doublequote and not state_esc:
+            arg_list.append(arg)
+            arg = ''
+        else:
+            arg = arg + c
+            if c != '\\'and state_esc: # escape mode lasts for one character.
+                state_esc = 0
+
+    # Handle last argument.        
+    if arg != '':
+        arg_list.append(arg)
+    return arg_list
+
+#
+#
+###############################################################################
+
+def die(msg):
+    print msg
+    print "Goodbye."
+    raise SystemExit, 1
+
+class ssh_session:
+    "Session with extra state including the password to be used."
+    def __init__(self, user, host, verbose=0):
+        self.user = user
+        self.host = host
+        self.verbose = verbose
+        self.password = None
+    def remote(self, command, input=None):
+        "Execute a command on the remote host.  Return the output."
+        command = "ssh -l %s %s %s"% (self.user,self.host,command)
+        if input:
+            command = input + " | " + command
+        child = spawn(command)
+        if self.verbose:
+            print "-> " + command
+        seen = child.expect(['assword:', EOF])
+        if seen == 0:
+            if not self.password:
+                self.password = getpass.getpass('Remote password: ')
+            child.sendline(self.password)
+        if self.verbose:
+            print "<- " + child.before
+        return child.before
+    def checkfile(self, file):
+        "Retrieve file permissions of specified remote file."
+        seen = session.remote("/bin/ls -ld %s" % file)
+        if string.find(seen, "No such file") > -1:
+            return None                        # File doesn't exist
+        else:
+            return seen.split()[0]     # Return permission field of listing.
+    def fixperms(self, file):
+        "Force perms of remote file to user read on, group/other write off."
+        perms = session.checkfile(file)
+        print "Remote %s permissions are %s" % (file, perms)
+        if not (perms[1] == 'r' and perms[5] == '-' and perms[8] == '-'):
+            print "User read should be on, Group and Other write permissions should be off."
+            if not check:
+                print "Correcting permissions..."
+                session.remote("/bin/chmod u+r,g-w,o-w %s" % file) 
+    def forcefile(self, file, command):
+        "Create file with command idf it does not already exist."
+        perms = session.checkfile(file)
+        if perms is None:
+            print "Remote %s doesn't exist." % file
+            if check:
+                die("Bailing out...")
+            else:
+                print "Creating remote %s" % file
+                session.remote(command)
+                perms = session.checkfile(file)
+        return perms
+
+if __name__ == '__main__': 
+    # Process options
+    (options, arguments) = getopt.getopt(sys.argv[1:], "chl:v")
+    login = os.environ.get("USER")
+    check = 0
+    verbose = 0
+    if not login:
+        login = os.environ.get("LOGNAME")
+
+    if not arguments:
+        print "No remote host specified."
+        print __doc__ % login
+        raise SystemExit, 0
+    else:
+        host = arguments[0]
+
+    for (switch, val) in options:
+       if (switch == '-c'):
+            check = 1
+       elif (switch == '-h'):
+           print __doc__ % login
+            raise SystemExit, 0
+       elif (switch == '-l'):
+           login = val
+       elif (switch == '-v'):
+           verbose = 1
+
+    # Verify that key pairs are present on the local side 
+    print "Checking your local configuration..."
+    try:
+        os.chdir(os.getenv("HOME"))
+    except:
+        die("Can't find your home directory!")
+    if not os.access(".", os.R_OK):
+        die("Your home directory is not readable by you. That's weird.")
+        die("This probabably means $HOME is incorrect.")
+    if not os.path.exists(".ssh"):
+        print "You have no .ssh directory."
+        if check:
+            print "Creating your .ssh directory."
+            os.mkdir(".ssh")
+        else:
+            print "Creation of .ssh suppressed."
+    if not os.access(".ssh", os.R_OK):
+        die("Your .ssh directory is not readable by you. That's weird.")
+        die("This probabably means $HOME is incorrect.")
+    try:
+        os.chdir(".ssh")
+    except:
+        die("Can't chdir into your .ssh directory!")
+    public_keys  = filter(lambda f: os.access(f, os.R_OK),
+                          ("id_dsa.pub", "id_rsa.pub", "identity.pub"))
+    if not public_keys:
+        if check:
+            print "I don't see any key pairs.  You need to generate some."
+            die("Try re-running this script without the check-only option.")
+        public_keys = []
+        for (keytype, file) in (("rsa1", "identity"), ("dsa", "id_dsa")):
+            print "About to generate a %s key. You will be prompted for a passphrase,"
+            print "Pressing enter at that prompt will create a key with no passphrase" 
+            os.system("ssh-keygen -t %s -f %s" % (keytype, file))
+            public_keys.append(file + ".pub")
+
+    print "I see the following public keys:", string.join(public_keys,", ")
+    for file in public_keys:
+        private_key = file[:-4]
+        legend = private_key + " corresponding to " + file + " " 
+        if os.access(private_key, os.R_OK):
+            print legend + "exists and is readable."
+        else:
+            die(legend + "does not exist or is unreadable.")
+    print "Local configuration looks correct."
+
+    # Get session to remote host.
+    print "Starting session to %s..." % host
+    session = ssh_session(login, host, verbose)
+
+    # Permissions checks
+    session.fixperms("~")
+    session.forcefile("~/.ssh", "mkdir ~/.ssh")
+    session.fixperms("~/.ssh")
+
+    # OK, install keys remotely
+    session.forcefile("~/.ssh/authorized_keys",
+                             "cat >~/.ssh/authorized_keys"
+                             "cat " + string.join(public_keys, " "))
+
+    # End
+