[Checkins] SVN: zope.app.fssync/trunk/ - The ssh transport now looks for known_hosts in an application specific file,

Aaron Lehmann aaron at zope.com
Tue Jan 5 10:34:32 EST 2010


Log message for revision 107705:
  - The ssh transport now looks for known_hosts in an application specific file,
    as well as the normal known_hosts file and in the user's Agent.  This file is
    ~/.ssh/fssync_known_hosts if POSIX and ~/ssh/fssync_known_hosts if win32.
  
  - BUGFIX: The ssh transport now will prompt the user if he wishes to use an
    unrecognized hostkey.  If he says 'yes', it will be added to the fssync
    known_hosts file.  if he says 'no', an exception is raised.
  
  - BUGFIX: If the user's public key is encrypted, fssync will prompt for a
    password.
  
  

Changed:
  U   zope.app.fssync/trunk/CHANGES.txt
  U   zope.app.fssync/trunk/setup.py
  U   zope.app.fssync/trunk/src/zope/app/fssync/ssh.py

-=-
Modified: zope.app.fssync/trunk/CHANGES.txt
===================================================================
--- zope.app.fssync/trunk/CHANGES.txt	2010-01-05 14:02:42 UTC (rev 107704)
+++ zope.app.fssync/trunk/CHANGES.txt	2010-01-05 15:34:31 UTC (rev 107705)
@@ -1,6 +1,20 @@
 Changes
 =======
 
+3.6
+---
+
+- The ssh transport now looks for known_hosts in an application specific file,
+  as well as the normal known_hosts file and in the user's Agent.  This file is
+  ~/.ssh/fssync_known_hosts if POSIX and ~/ssh/fssync_known_hosts if win32.
+
+- BUGFIX: The ssh transport now will prompt the user if he wishes to use an
+  unrecognized hostkey.  If he says 'yes', it will be added to the fssync
+  known_hosts file.  if he says 'no', an exception is raised.
+
+- BUGFIX: If the user's public key is encrypted, fssync will prompt for a
+  password.
+
 3.5
 ---
 

Modified: zope.app.fssync/trunk/setup.py
===================================================================
--- zope.app.fssync/trunk/setup.py	2010-01-05 14:02:42 UTC (rev 107704)
+++ zope.app.fssync/trunk/setup.py	2010-01-05 15:34:31 UTC (rev 107705)
@@ -6,7 +6,7 @@
     return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
 
 setup(name='zope.app.fssync',
-      version = '3.5',
+      version = '3.6dev',
       url = 'http://pypi.python.org/pypi/zope.app.fssync',
       license = 'ZPL 2.1',
       description = "Filesystem synchronization utility for Zope 3.",

Modified: zope.app.fssync/trunk/src/zope/app/fssync/ssh.py
===================================================================
--- zope.app.fssync/trunk/src/zope/app/fssync/ssh.py	2010-01-05 14:02:42 UTC (rev 107704)
+++ zope.app.fssync/trunk/src/zope/app/fssync/ssh.py	2010-01-05 15:34:31 UTC (rev 107705)
@@ -15,8 +15,8 @@
 import httplib
 import os.path
 import socket
-
 import paramiko
+import sys
 
 
 class FileSocket(object):
@@ -30,92 +30,78 @@
         return self.file
 
 
+class ConfirmationPolicy(paramiko.WarningPolicy, paramiko.RejectPolicy, paramiko.AutoAddPolicy):
+    def _add_key(self, client, hostname, key):
+        paramiko.AutoAddPolicy.missing_host_key(self, client, hostname, key)
+        client.save_host_keys(SSHConnection.key_file_name)
+
+    def missing_host_key(self, client, hostname, key):
+        paramiko.WarningPolicy.missing_host_key(self, client, hostname, key)
+        answer = raw_input("Are you sure you want to continue connecting (yes/no)? ")
+        yes_no = {'no': paramiko.RejectPolicy.missing_host_key,
+                  'yes': ConfirmationPolicy._add_key,}
+        while answer.lower() not in yes_no:
+            print "Please type 'yes' or 'no'."
+            answer = raw_input("Are you sure you want to continue connecting (yes/no)? ")
+        return yes_no[answer.lower()](self, client, hostname, key)
+
+
 class SSHConnection(object):
     """
     SSH connection that implements parts of the httplib.HTTPConnection
     interface
     """
-    def __init__(self, host_port, user_passwd=None):
-        self.headers = {}
-        self.host, self.port = host_port.split(':')
-        self.port = int(self.port)
+    if sys.platform == 'win32':
+        sys_key_file_name = os.path.expanduser('~/ssh/known_hosts')
+        key_file_name = os.path.expanduser('~/ssh/fssync_known_hosts')
+    else:
+        sys_key_file_name = os.path.expanduser('~/.ssh/known_hosts')
+        key_file_name = os.path.expanduser('~/.ssh/fssync_known_hosts')
 
-        # if username is specified in URL then use it, otherwise
-        # default to local userid
-        if user_passwd:
-            self.remote_user_name = user_passwd.split(':')[0]
+    # This and the __new__ method are to ensure that the SSHConnection doesn't
+    # get garbage collected by paramiko too early.
+    clients = {}
+
+    def __new__(cls, host_port, user_passwd=None):
+        if host_port in cls.clients:
+            return cls.clients[host_port]
         else:
-            self.remote_user_name = getpass.getuser()
+            cls.clients[host_port] = object.__new__(
+                cls, host_port, user_passwd)
+            return cls.clients[host_port]
 
-    def putrequest(self, method, path):
-        # open connection to server
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        sock.connect((self.host, self.port))
-        self.transport = paramiko.Transport(sock)
-        self.transport.start_client()
+    def __init__(self, host_port, user_passwd=None):
+        if not hasattr(self, 'headers'):
+            self.headers = {}
+            self.host, self.port = host_port.split(':')
+            self.port = int(self.port)
 
-        # try to get public key from ssh agent
-        agent = paramiko.Agent()
-        for key in agent.get_keys():
+            # if username is specified in URL then use it, otherwise
+            # default to local userid
+            if user_passwd:
+                self.remote_user_name = user_passwd.split(':')[0]
+            else:
+                self.remote_user_name = getpass.getuser()
+            self.client = paramiko.SSHClient()
+            self.client.set_missing_host_key_policy(ConfirmationPolicy())
+            self.client.load_system_host_keys(self.sys_key_file_name)
             try:
-                self.transport.auth_publickey(self.remote_user_name, key)
-                break
-            except paramiko.SSHException:
+                self.client.load_host_keys(self.key_file_name)
+            except IOError, ioe:
+                # This will get handled later in the ConfirmationPolicy.  We can
+                # pass for now.
                 pass
-
-        # try to get public key from fs
-        if not self.transport.is_authenticated():
-            path = os.path.expanduser('~/.ssh/id_rsa')
             try:
-                key = paramiko.RSAKey.from_private_key_file(path)
-            except paramiko.PasswordRequiredException:
-                password = getpass.getpass('RSA key password: ')
-                key = paramiko.RSAKey.from_private_key_file(path, password)
-            try:
-                self.transport.auth_publickey(self.remote_user_name, key)
-            except paramiko.SSHException:
-                pass
+                self.client.connect(self.host, self.port, self.remote_user_name)
+            except paramiko.PasswordRequiredException, pre:
+                password = getpass.getpass("Password to unlock password-protected key? ")
+                self.client.connect(
+                    self.host, self.port, self.remote_user_name, password=password)
 
-        if not self.transport.is_authenticated():
-            path = os.path.expanduser('~/.ssh/id_dsa')
-            try:
-                key = paramiko.DSSKey.from_private_key_file(path)
-            except paramiko.PasswordRequiredException:
-                password = getpass.getpass('DSS key password: ')
-                key = paramiko.DSSKey.from_private_key_file(path, password)
-            try:
-                self.transport.auth_publickey(self.remote_user_name, key)
-            except paramiko.SSHException:
-                raise Exception('No valid public key found')
 
-        # try to get host key
-        hostkeytype = None
-        hostkey = None
-        try:
-            host_keys = paramiko.util.load_host_keys(
-                os.path.expanduser('~/.ssh/known_hosts'))
-        except IOError:
-            try:
-                # try ~/ssh/ too, because windows can't have a folder
-                # named ~/.ssh/
-                host_keys = paramiko.util.load_host_keys(
-                    os.path.expanduser('~/ssh/known_hosts'))
-            except IOError:
-                host_keys = {}
-
-        if host_keys.has_key(self.host):
-            hostkeytype = host_keys[self.host].keys()[0]
-            hostkey = host_keys[self.host][hostkeytype]
-
-        # verify host key
-        if hostkey:
-            server_key = self.transport.get_server_key()
-            if server_key != hostkey:
-                raise Exception(
-                    "Remote host key doesn't match value in known_hosts")
-
+    def putrequest(self, method, path):
         # start zsync subsystem on server
-        self.channel = self.transport.open_session()
+        self.channel = self.client.get_transport().open_session()
         self.channel.invoke_subsystem('zsync')
         self.channelr = self.channel.makefile('rb')
         self.channelw = self.channel.makefile('wb')
@@ -123,6 +109,7 @@
         # start sending request
         self.channelw.write('%s %s\r\n' % (method, path))
 
+
     def putheader(self, name, value):
         self.channelw.write('%s: %s\r\n' % (name, value))
 



More information about the checkins mailing list