--- /dev/null
+#!/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
+