From: Eric S. Raymond Date: Fri, 22 Nov 2002 11:26:34 +0000 (+0000) Subject: Initial revision X-Git-Tag: 1-1~16 X-Git-Url: http://thyrsus.com/gitweb/?a=commitdiff_plain;h=8017d5bfafb8f99ad62075465dbc04ba23bdd93d;p=ssh-installkeys.git Initial revision --- 8017d5bfafb8f99ad62075465dbc04ba23bdd93d diff --git a/ssh-installkeys b/ssh-installkeys new file mode 100755 index 0000000..d3a71a2 --- /dev/null +++ b/ssh-installkeys @@ -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 +