[or-cvs] r14995: Added the rest of the libs. Updated the exit node port polic (in torflow/branches/gsoc2008: . TorCtl tools tools/OpenSSL tools/pyssh)

aleksei at seul.org aleksei at seul.org
Fri Jun 6 18:35:16 UTC 2008


Author: aleksei
Date: 2008-06-06 14:35:15 -0400 (Fri, 06 Jun 2008)
New Revision: 14995

Added:
   torflow/branches/gsoc2008/tools/OpenSSL/
   torflow/branches/gsoc2008/tools/OpenSSL/SSL.so
   torflow/branches/gsoc2008/tools/OpenSSL/__init__.py
   torflow/branches/gsoc2008/tools/OpenSSL/crypto.so
   torflow/branches/gsoc2008/tools/OpenSSL/rand.so
   torflow/branches/gsoc2008/tools/OpenSSL/tsafe.py
   torflow/branches/gsoc2008/tools/OpenSSL/version.py
   torflow/branches/gsoc2008/tools/pyssh/
   torflow/branches/gsoc2008/tools/pyssh/fssa.py
   torflow/branches/gsoc2008/tools/pyssh/nbpipe.py
   torflow/branches/gsoc2008/tools/pyssh/ptyext.py
   torflow/branches/gsoc2008/tools/pyssh/pyssh.py
Modified:
   torflow/branches/gsoc2008/TorCtl/TorUtil.py
   torflow/branches/gsoc2008/soat.py
Log:
Added the rest of the libs. Updated the exit node port policies test to print results for each protocol separately. Can now (in a basic way) handle http, openssl, ssh connections.

Modified: torflow/branches/gsoc2008/TorCtl/TorUtil.py
===================================================================
--- torflow/branches/gsoc2008/TorCtl/TorUtil.py	2008-06-06 17:42:26 UTC (rev 14994)
+++ torflow/branches/gsoc2008/TorCtl/TorUtil.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -20,6 +20,9 @@
       "BufSock", "secret_to_key", "urandom_rng", "s2k_gen", "s2k_check", "plog", 
      "ListenSocket", "zprob"]
 
+tor_port = 9050
+tor_host = '127.0.0.1'
+
 meta_port = 9052
 meta_host = '127.0.0.1'
 

Modified: torflow/branches/gsoc2008/soat.py
===================================================================
--- torflow/branches/gsoc2008/soat.py	2008-06-06 17:42:26 UTC (rev 14994)
+++ torflow/branches/gsoc2008/soat.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -11,7 +11,7 @@
 
 from TorCtl import TorUtil, TorCtl, PathSupport
 
-from TorCtl.TorUtil import meta_port, meta_host, control_port, control_host
+from TorCtl.TorUtil import meta_port, meta_host, control_port, control_host, tor_port, tor_host
 from TorCtl.TorUtil import *
 from TorCtl.PathSupport import *
 from TorCtl.TorCtl import Connection
@@ -19,9 +19,15 @@
 sys.path.append("./tools/BeautifulSoup/")
 from BeautifulSoup import BeautifulSoup
 
-sys.path.append("./tools/SocksiPy")
+sys.path.append("./tools/SocksiPy/")
 import socks
 
+sys.path.append("./tools/")
+from OpenSSL import *
+
+sys.path.append("./tools/pyssh")
+import pyssh
+
 # config stuff
 
 user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1) Gecko/20061010 Firefox/2.0' 
@@ -39,19 +45,14 @@
 
 # ports to test in the consistency test
 
-''' Check pop/pops, imap/imaps, telnet/ssh, smtp/smtps, http/https'''
-common_ports = [ExitPolicyRestriction('255.255.255.255', 110),
-    ExitPolicyRestriction('255.255.255.255', 143),
-    ExitPolicyRestriction('255.255.255.255', 23),
-    ExitPolicyRestriction('255.255.255.255', 25),
-    ExitPolicyRestriction('255.255.255.255', 80)]
+ports_to_check = [
+    ["pop", ExitPolicyRestriction('255.255.255.255', 110), "pops", ExitPolicyRestriction('255.255.255.255', 995)],
+    ["imap", ExitPolicyRestriction('255.255.255.255', 143), "imaps", ExitPolicyRestriction('255.255.255.255', 993)],
+    ["telnet", ExitPolicyRestriction('255.255.255.255', 23), "ssh", ExitPolicyRestriction('255.255.255.255', 22)],
+    ["smtp", ExitPolicyRestriction('255.255.255.255', 25), "smtps", ExitPolicyRestriction('255.255.255.255', 465)],
+    ["http", ExitPolicyRestriction('255.255.255.255', 80), "https", ExitPolicyRestriction('255.255.255.255', 443)]
+]
 
-secure_ports = [ExitPolicyRestriction('255.255.255.255', 995),
-    ExitPolicyRestriction('255.255.255.255', 993),
-    ExitPolicyRestriction('255.255.255.255', 22),
-    ExitPolicyRestriction('255.255.255.255', 465),
-    ExitPolicyRestriction('255.255.255.255', 443)]
-
 # constants
 
 linebreak = '\r\n'
@@ -143,53 +144,126 @@
 
         plog('INFO', 'Connection to control port established')
 
+        # get the structure
         routers = c.read_routers(c.get_network_status())
-        bad_exits = []
+        bad_exits = Set([])
+        specific_bad_exits = [None]*len(ports_to_check)
+        for i in range(len(ports_to_check)):
+            specific_bad_exits[i] = []
 
+        # check exit policies
         for router in routers:
-            for i in range(0,len(common_ports)):
-                if common_ports[i].r_is_ok(router) and not secure_ports[i].r_is_ok(router):
-                    bad_exits.append(router)
-                    plog('INFO', 'Router ' + router.nickname + ' allows ' + `common_ports[i].to_port` + ' but not ' + `secure_ports[i].to_port`)
-                    break
+            for i in range(len(ports_to_check)):
+                [common_protocol, common_restriction, secure_protocol, secure_restriction] = ports_to_check[i]
+                if common_restriction.r_is_ok(router) and not secure_restriction.r_is_ok(router):
+                    bad_exits.add(router)
+                    specific_bad_exits[i].append(router)
+                    plog('INFO', 'Router ' + router.nickname + ' allows ' + common_protocol + ' but not ' + secure_protocol)
     
+        # report results
         plog('INFO', 'Total exits: ' + `len(routers)`)
-        plog('INFO', 'Good exits: ' + `(len(routers) - len(bad_exits))`)
-        plog('INFO', 'Bad exits: ' + `len(bad_exits)` + ' (~' + `(len(bad_exits) * 100 / len(routers))` + '%)')
+        for i in range(len(ports_to_check)):
+            [common_protocol, _, secure_protocol, _] = ports_to_check[i]
+            plog('INFO', 'Exits with ' + common_protocol + ' / ' + secure_protocol + ' problem: ' + `len(specific_bad_exits[i])` + ' (~' + `(len(specific_bad_exits[i]) * 100 / len(routers))` + '%)')
+        plog('INFO', 'Total bad exits: ' + `len(bad_exits)` + ' (~' + `(len(bad_exits) * 100 / len(routers))` + '%)')
 
     def check_http(self, address):
         request = urllib2.Request(address)
         request.add_header('User-Agent', user_agent)
         
+        plog('INFO', 'Opening ' + address + ' using the direct connection')
         try:
             f = urllib2.urlopen(request)
-        except urllib2.URLError:
-            plog('ERROR', 'The requested page ' + address + ' doesn\'t exist')
+        except Exception, e:
+            plog('ERROR', 'Opening ' + address + ' directly failed')
+            plog('ERROR', e)
             return 0
-        except:
-            plog('ERROR', 'Opening ' + address + ' failed')
-            return 0
 
         content = f.read()
         content = content.decode('ascii', 'ignore')
 
-        socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1:9050")
-        socks.socket = socks.socksocket
+        print content
 
+        defaultsocket = socket.socket
+        socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
+        socket.socket = socks.socksocket
+
+        plog('INFO', 'Opening ' + address + ' using exit node ' + self.get_exit_node())
         try:
             g = urllib2.urlopen(request)
-        except:
+        except Exception, e:
             plog('ERROR', 'Opening ' + address + ' via tor failed')
+            plog('ERROR', e)
             return 0
 
         pcontent = g.read()
 
+        print pcontent
+
+        # reset the default connection
+        socket.socket = defaultsocket
+
         return 0
 
     def check_openssh(self, address):
-        return 0
+        ssh = pyssh.Ssh('username', 'host', 22)
+        ssh.set_sshpath(pyssh.SSH_PATH)
 
+        #response = self.ssh.sendcmd('ls')
+        #print response
+
+        return 0 
+
     def check_openssl(self, address):
+
+        # specify the context
+        ctx = SSL.Context(SSL.SSLv3_METHOD)
+        ctx.set_verify_depth(1)
+
+        # ready the certificate request
+        request = crypto.X509Req()
+
+        # open a direct ssl connection
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        c = SSL.Connection(ctx, s)
+        c.set_connect_state()
+
+        plog('INFO', 'Opening a direct ssl connection to ' + address)
+
+        c.connect((address, 443))
+        c.send(crypto.dump_certificate_request(crypto.FILETYPE_ASN1,request))
+
+        cert = c.get_peer_certificate()
+
+        print 'Issuer: ', cert.get_issuer()
+        print 'Public key: ', cert.get_pubkey()
+        print 'Subject: ', cert.get_subject()
+        print 'Version: ', cert.get_version()
+
+        # open a connection via tor
+        defaultsocket = socket.socket
+        socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
+        socket.socket = socks.socksocket
+
+        s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        c2 = SSL.Connection(ctx, s2)
+        c2.set_connect_state()
+
+        plog('INFO', 'Opening an ssl connection to ' + address + ' using exit node ' + self.get_exit_node())
+
+        c2.connect((address, 443))
+        c2.send(crypto.dump_certificate_request(crypto.FILETYPE_ASN1,request))
+
+        cert2 = c2.get_peer_certificate()
+
+        print 'Issuer: ', cert2.get_issuer()
+        print 'Public key: ', cert2.get_pubkey()
+        print 'Subject: ', cert2.get_subject()
+        print 'Version: ', cert2.get_version()
+        
+        # reset the default connection
+        socket.socket = defaultsocket
+
         return 0
 
 
@@ -270,11 +344,16 @@
 
 def main(argv):
     scanner = ExitNodeScanner(meta_host, meta_port)
+    '''
     scanner.check_all_exits_port_consistency()
-
+    scanner.get_exit_node()
+    scanner.check_http("http://math.ut.ee/~aleksei/ip.php")
+    
+    scanner.check_openssh("http://math.ut.ee/~aleksei/ip.php")
+   
     '''
-    scanner.get_exit_node()
-    scanner.check_http("http://www.ee.ee")
+    scanner.check_openssl("mail.google.com")
+    '''
 
     global doc_urls
     doc_urls.extend(load_url_list())

Added: torflow/branches/gsoc2008/tools/OpenSSL/SSL.so
===================================================================
(Binary files differ)


Property changes on: torflow/branches/gsoc2008/tools/OpenSSL/SSL.so
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:mime-type
   + application/octet-stream

Added: torflow/branches/gsoc2008/tools/OpenSSL/__init__.py
===================================================================
--- torflow/branches/gsoc2008/tools/OpenSSL/__init__.py	                        (rev 0)
+++ torflow/branches/gsoc2008/tools/OpenSSL/__init__.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -0,0 +1,12 @@
+#
+# __init__.py
+#
+# Copyright (C) AB Strakt 2001, All rights reserved
+#
+# $Id: __init__.py,v 1.4 2004/07/22 12:01:25 martin Exp $
+#
+"""
+pyOpenSSL - A simple wrapper around the OpenSSL library
+"""
+import rand, crypto, SSL, tsafe
+from version import __version__

Added: torflow/branches/gsoc2008/tools/OpenSSL/crypto.so
===================================================================
(Binary files differ)


Property changes on: torflow/branches/gsoc2008/tools/OpenSSL/crypto.so
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:mime-type
   + application/octet-stream

Added: torflow/branches/gsoc2008/tools/OpenSSL/rand.so
===================================================================
(Binary files differ)


Property changes on: torflow/branches/gsoc2008/tools/OpenSSL/rand.so
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:mime-type
   + application/octet-stream

Added: torflow/branches/gsoc2008/tools/OpenSSL/tsafe.py
===================================================================
--- torflow/branches/gsoc2008/tools/OpenSSL/tsafe.py	                        (rev 0)
+++ torflow/branches/gsoc2008/tools/OpenSSL/tsafe.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -0,0 +1,28 @@
+from OpenSSL import SSL
+_ssl = SSL
+del SSL
+
+import threading
+_RLock = threading.RLock
+del threading
+
+class Connection:
+    def __init__(self, *args):
+        self._ssl_conn = apply(_ssl.Connection, args)
+        self._lock = _RLock()
+
+    for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
+              'renegotiate', 'bind', 'listen', 'connect', 'accept',
+              'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
+              'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
+              'makefile', 'get_app_data', 'set_app_data', 'state_string',
+              'sock_shutdown', 'get_peer_certificate', 'want_read',
+              'want_write', 'set_connect_state', 'set_accept_state',
+              'connect_ex', 'sendall'):
+        exec """def %s(self, *args):
+            self._lock.acquire()
+            try:
+                return apply(self._ssl_conn.%s, args)
+            finally:
+                self._lock.release()\n""" % (f, f)
+

Added: torflow/branches/gsoc2008/tools/OpenSSL/version.py
===================================================================
--- torflow/branches/gsoc2008/tools/OpenSSL/version.py	                        (rev 0)
+++ torflow/branches/gsoc2008/tools/OpenSSL/version.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -0,0 +1,8 @@
+# Copyright (C) AB Strakt 2001-2004, All rights reserved
+# Copyright (C) Jean-Paul Calderone 2008, All rights reserved
+
+"""
+pyOpenSSL - A simple wrapper around the OpenSSL library
+"""
+
+__version__ = '0.7'

Added: torflow/branches/gsoc2008/tools/pyssh/fssa.py
===================================================================
--- torflow/branches/gsoc2008/tools/pyssh/fssa.py	                        (rev 0)
+++ torflow/branches/gsoc2008/tools/pyssh/fssa.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -0,0 +1,75 @@
+# vi:et:ts=4:tw=0
+""" fssa.py
+
+    Search for an ssh-agent for the calling user and attach to it
+    if found.
+
+    Tested on poxix only
+"""
+# This is a Python port of Steve Allen's fsa.sh script found
+# at http://www.ucolick.org/~sla/ssh/sshcron.html
+# Ported by Mark W. Alexander <slash at dotnetslash.net>
+
+import os
+if os.name == 'posix':
+    import pwd, stat, sys
+    from commands import getoutput
+    def fssa(key=None):
+        """ fssa(key=None)
+
+        Searches /tmp/ssh-* owned by the calling user for ssh-agent
+        sockets. If key is provided, only sockets matching the key will be
+        considered. If key is not provided, the calling users username
+        will be used instead.
+        """
+        user, pw, uid, gid, gecos, home, shell = pwd.getpwuid(os.getuid())
+        if key is None:
+            key = user
+
+        # Find /tmp/ssh-* dirs owned by this user
+        candidate_dirs=[]
+        for filename in os.listdir('/tmp'):
+            file_stat = os.stat("/tmp/%s" % (filename))
+            if file_stat[stat.ST_UID] == os.getuid() \
+            and stat.S_ISDIR(file_stat[stat.ST_MODE]) \
+            and filename.find('ssh-') == 0:
+                candidate_dirs.append("/tmp/%s" % filename)
+
+        candidate_sockets=[]
+        for d in candidate_dirs:
+            for f in os.listdir(d):
+                file_stat = os.stat("%s/%s" % (d, f))
+                if file_stat[stat.ST_UID] == os.getuid() \
+                and stat.S_ISSOCK(file_stat[stat.ST_MODE]) \
+                and f.find('agent.') == 0:
+                    candidate_sockets.append("%s/%s" % (d, f))
+        alive = None
+        # order by pid, prefering sockets where the parent pid
+        # is gone. This gives preference to agents running in the
+        # background and reaped by init (maybe). Only works on
+        # systems with a /proc filesystem
+        if stat.S_ISDIR(os.stat("/proc")[stat.ST_MODE]):
+            reorder=[]
+            for s in candidate_sockets:
+                pid = s[s.find('.')+1:]
+                try:
+                    stat.S_ISDIR(os.stat("/proc/%s" % pid)[stat.ST_MODE])
+                    reorder.append(s)
+                except:
+                    reorder.insert(0,s)
+            candidate_sockets = reorder
+
+        for s in candidate_sockets:
+            os.environ['SSH_AUTH_SOCK'] = s
+            try:
+                pubkey = getoutput("ssh-add -l 2>/dev/null")
+            except:
+                continue
+            if pubkey.find(key):
+                alive = 1
+                break
+            os.environ.pop('SSH_AUTH_SOCK')
+        if alive:
+            return pubkey
+        else:
+            return None

Added: torflow/branches/gsoc2008/tools/pyssh/nbpipe.py
===================================================================
--- torflow/branches/gsoc2008/tools/pyssh/nbpipe.py	                        (rev 0)
+++ torflow/branches/gsoc2008/tools/pyssh/nbpipe.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -0,0 +1,119 @@
+"""Implements a non-blocking pipe class."""
+
+# Since it uses thread rather than select, it is portable to at least
+# posix and windows environments.
+
+# Author: Rasjid Wilcox, copyright (c) 2002
+# Ideas taken from the Python 2.2 telnetlib.py library.
+#
+# Last modified: 3 August 2002
+# Licence: Python 2.2 Style License.  See license.txt.
+
+# TO DO:
+#     * Handle excpetions better, particularly Keyboard Interupts.
+#     * Possibly do a threadless version for posix environments
+#       where we can use select (is probably more efficient).
+#     * A test function.
+
+import Queue
+import thread
+import os
+import time
+import types
+
+#INT_TYPE = type(1)
+MIN_TIMEOUT = 0.01
+
+class nbpipe:
+    def __init__(self, readfile, timeout=1, pipesize=0, blocksize=1024):
+        """Initialise a non-blocking pipe object, given a real file or file-descriptor.
+        timeout = the default timeout (in seconds) at which read_lazy will decide
+                  that there is no more data in this read
+        pipesize = the size (in blocks) of the queue used to buffer the blocks read
+        blocksize = the maximum block size for a raw read."""
+        if type(readfile) == types.IntType:
+            self.fd = readfile
+        else:
+            self.fd = readfile.fileno()
+        self.timeout = timeout  # default timeout allowed between blocks
+        self.pipesize = pipesize
+        self.blocksize = blocksize
+        self.eof = 0
+        self._q = Queue.Queue(self.pipesize)
+        thread.start_new_thread(self._readtoq, ())
+    def _readtoq(self):
+        finish = 0
+        while (1):
+            try:
+                item = os.read(self.fd, self.blocksize)
+            except (IOError, OSError):
+                finish = 1
+            if (item == '') or finish:
+                # Wait until everything has been read from the queue before
+                # setting eof = 1 and exiting.
+                while self.has_data():
+                    time.sleep(MIN_TIMEOUT)
+                self.eof = 1
+                thread.exit()
+            else:
+                self._q.put(item)
+    def has_data(self):
+        return not self._q.empty()
+    def eof(self):
+        return self.eof
+    def read_very_lazy(self, maxblocks=0):
+        """Read data from the queue, to a maximum of maxblocks (0 = infinite).
+        Does not block."""
+        data = ''
+        blockcount = 0
+        while self.has_data():
+            data += self._q.get()
+            blockcount += 1
+            if blockcount == maxblocks:
+                break
+        return data
+    def read_lazy(self, maxblocks=0, timeout=None):
+        """Read data from the queue, allowing timeout seconds between block arrival.
+        if timeout = None, then use the objects (default) timeout.
+        Returns '' if we are at the EOF, or no data turns up within the timeout.
+        Reads at most maxblocks (0 = infinite).
+        Does not block."""
+        if self.eof:
+            return ''
+        if timeout == None:
+            timeout = self.timeout
+        maxwait = timeout / MIN_TIMEOUT
+        data = ''
+        blockcount = 0
+        waitcount = 0
+        while waitcount < maxwait:
+            block = self.read_very_lazy(1)
+            if block != '':
+                blockcount += 1
+                data += block
+                waitcount = 0  # reset the wait count
+                if blockcount == maxblocks:
+                    break
+            else:
+                time.sleep(MIN_TIMEOUT)
+                waitcount += 1
+        return data
+    def read_some(self, maxblocks=0, timeout=None):
+        """As for read_lazy, but always read a single block of data.
+        May block."""
+        if timeout == None:
+            timeout = self.timeout
+        data = ''
+        while not self.eof and data == '':
+            data = self.read_lazy()
+        if maxblocks != 1:
+            data += self.read_lazy(maxblocks - 1, timeout)
+        return data
+    def read_all(self):
+        """Read until the EOF. May block."""
+        data = ''
+        while not self.eof:
+            data += self.read_very_lazy()
+            time.sleep(MIN_TIMEOUT)
+        return data
+    

Added: torflow/branches/gsoc2008/tools/pyssh/ptyext.py
===================================================================
--- torflow/branches/gsoc2008/tools/pyssh/ptyext.py	                        (rev 0)
+++ torflow/branches/gsoc2008/tools/pyssh/ptyext.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -0,0 +1,209 @@
+"""Pseudo terminal utilities."""
+
+# Bugs: No signal handling.  Doesn't set slave termios and window size.
+#       Only tested on Linux.
+# See:  W. Richard Stevens. 1992.  Advanced Programming in the
+#       UNIX Environment.  Chapter 19.
+# Author: Steen Lumholt -- with additions by Guido.
+
+from select import select, error
+import os
+
+# Absurd:  import termios and then delete it.  This is to force an attempt
+# to import pty to raise an ImportError on platforms that lack termios.
+# Without this explicit import of termios here, some other module may
+# import tty first, which in turn imports termios and dies with an
+# ImportError then.  But since tty *does* exist across platforms, that
+# leaves a damaged module object for tty in sys.modules, and the import
+# of tty here then appears to work despite that the tty imported is junk.
+import termios
+del termios
+
+import tty
+
+__all__ = ["openpty","fork","spawn","th_spawn","popen2"]
+
+STDIN_FILENO = 0
+STDOUT_FILENO = 1
+STDERR_FILENO = 2
+
+CHILD = 0
+
+def openpty():
+    """openpty() -> (master_fd, slave_fd)
+    Open a pty master/slave pair, using os.openpty() if possible."""
+
+    try:
+        return os.openpty()
+    except (AttributeError, OSError):
+        pass
+    master_fd, slave_name = _open_terminal()
+    slave_fd = slave_open(slave_name)
+    return master_fd, slave_fd
+
+def master_open():
+    """master_open() -> (master_fd, slave_name)
+    Open a pty master and return the fd, and the filename of the slave end.
+    Deprecated, use openpty() instead."""
+
+    try:
+        master_fd, slave_fd = os.openpty()
+    except (AttributeError, OSError):
+        pass
+    else:
+        slave_name = os.ttyname(slave_fd)
+        os.close(slave_fd)
+        return master_fd, slave_name
+
+    return _open_terminal()
+
+def _open_terminal():
+    """Open pty master and return (master_fd, tty_name).
+    SGI and generic BSD version, for when openpty() fails."""
+    try:
+        import sgi
+    except ImportError:
+        pass
+    else:
+        try:
+            tty_name, master_fd = sgi._getpty(os.O_RDWR, 0666, 0)
+        except IOError, msg:
+            raise os.error, msg
+        return master_fd, tty_name
+    for x in 'pqrstuvwxyzPQRST':
+        for y in '0123456789abcdef':
+            pty_name = '/dev/pty' + x + y
+            try:
+                fd = os.open(pty_name, os.O_RDWR)
+            except os.error:
+                continue
+            return (fd, '/dev/tty' + x + y)
+    raise os.error, 'out of pty devices'
+
+def slave_open(tty_name):
+    """slave_open(tty_name) -> slave_fd
+    Open the pty slave and acquire the controlling terminal, returning
+    opened filedescriptor.
+    Deprecated, use openpty() instead."""
+
+    return os.open(tty_name, os.O_RDWR)
+
+def fork():
+    """fork() -> (pid, master_fd)
+    Fork and make the child a session leader with a controlling terminal."""
+
+    try:
+        pid, fd = os.forkpty()
+    except (AttributeError, OSError):
+        pass
+    else:
+        if pid == CHILD:
+            try:
+                os.setsid()
+            except OSError:
+                # os.forkpty() already set us session leader
+                pass
+        return pid, fd
+
+    master_fd, slave_fd = openpty()
+    pid = os.fork()
+    if pid == CHILD:
+        # Establish a new session.
+        os.setsid()
+        os.close(master_fd)
+
+        # Slave becomes stdin/stdout/stderr of child.
+        os.dup2(slave_fd, STDIN_FILENO)
+        os.dup2(slave_fd, STDOUT_FILENO)
+        os.dup2(slave_fd, STDERR_FILENO)
+        if (slave_fd > STDERR_FILENO):
+            os.close (slave_fd)
+
+    # Parent and child process.
+    return pid, master_fd
+
+def _writen(fd, data):
+    """Write all the data to a descriptor."""
+    while data != '':
+        n = os.write(fd, data)
+        data = data[n:]
+
+def _read(fd):
+    """Default read function."""
+    return os.read(fd, 1024)
+
+def _copy(master_fd, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,
+             stdout_fd=STDOUT_FILENO):
+    """Parent copy loop.
+    Copies
+            pty master -> stdout_fd     (master_read)
+            stdin_fd   -> pty master    (stdin_read)"""
+    try:
+        mode = tty.tcgetattr(stdin_fd)
+        tty.setraw(stdin_fd)
+        restore = 1
+    except tty.error:    # This is the same as termios.error
+        restore = 0
+    try:
+        while 1:
+            rfds, wfds, xfds = select(
+                    [master_fd, stdin_fd], [], [])
+            if master_fd in rfds:
+                data = master_read(master_fd)
+                os.write(stdout_fd, data)
+            if stdin_fd in rfds:
+                data = stdin_read(stdin_fd)
+                _writen(master_fd, data)
+    except (IOError, OSError, error):  # The last entry is select.error
+        if restore:
+            tty.tcsetattr(stdin_fd, tty.TCSAFLUSH, mode)
+        if stdin_fd > STDERR_FILENO:
+            os.close(stdin_fd)
+        if stdout_fd > STDERR_FILENO:
+            os.close(stdout_fd)
+
+def spawn(argv, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,
+            stdout_fd=STDOUT_FILENO):
+    """Create a spawned process. The controlling terminal reads and
+    writes its data to stdin_fd and stdout_fd respectively.
+    
+    NOTE: This function does not return until one of the input or output file
+    descriptors are closed, or the child process exits."""
+    if type(argv) == type(''):
+        argv = (argv,)
+    pid, master_fd = fork()
+    if pid == CHILD:
+        apply(os.execlp, (argv[0],) + argv)
+    _copy(master_fd, master_read, stdin_read, stdin_fd, stdout_fd)
+
+def th_spawn(argv, master_read=_read, stdin_read=_read, stdin_fd=STDIN_FILENO,
+                stdout_fd=STDOUT_FILENO):
+    """Create a spawned process. The controlling terminal reads and
+    writes its data to stdin_fd and stdout_fd respectively. The function
+    returns the pid of the spawned process.  (It returns immediately.)"""
+    import thread
+    if type(argv) == type(''):
+        argv = (argv,)
+    pid, master_fd = fork()
+    if pid == CHILD:
+        apply(os.execlp, (argv[0],) + argv)
+    thread.start_new_thread(_copy, (master_fd, master_read, stdin_read, \
+                                            stdin_fd, stdout_fd))
+    return pid
+
+def popen2(cmd, bufsize=1024, master_read=_read, stdin_read=_read):
+    """Execute the shell command 'cmd' in a sub-process.
+    
+    If 'bufsize' is specified, it sets the buffer size for the I/O pipes.
+    The function returns (child_stdin, child_stdout, child_pid), where the
+    file objects are pipes connected to the spawned process's controling
+    terminal, and the child_pid is the pid of the child process.
+    """
+    argv = ('/bin/sh', '-c', cmd)
+    child_stdin_rfd, child_stdin_wfd = os.pipe()
+    child_stdout_rfd, child_stdout_wfd = os.pipe()
+    child_pid = th_spawn(argv, master_read, stdin_read, child_stdin_rfd, \
+                            child_stdout_wfd)
+    child_stdin = os.fdopen(child_stdin_wfd, 'w', bufsize)
+    child_stdout = os.fdopen(child_stdout_rfd, 'r', bufsize)
+    return child_stdin, child_stdout, child_pid

Added: torflow/branches/gsoc2008/tools/pyssh/pyssh.py
===================================================================
--- torflow/branches/gsoc2008/tools/pyssh/pyssh.py	                        (rev 0)
+++ torflow/branches/gsoc2008/tools/pyssh/pyssh.py	2008-06-06 18:35:15 UTC (rev 14995)
@@ -0,0 +1,320 @@
+"""A SSH Interface class.
+
+An interface to ssh on posix systems, and plink (part of the Putty
+suite) on Win32 systems.
+
+By Rasjid Wilcox.
+Copyright (c) 2002.
+
+Version: 0.2
+Last modified 4 September 2002.
+
+Drawing on ideas from work by Julian Schaefer-Jasinski, Guido's telnetlib and
+version 0.1 of pyssh (http://pyssh.sourceforge.net) by Chuck Esterbrook.
+
+Licenced under a Python 2.2 style license.  See License.txt.
+"""
+
+DEBUG_LEVEL = 0
+
+import os, getpass
+import signal    # should cause all KeyboardInterrupts to go to the main thread
+                 # try for Linux, does not seem to be try under Cygwin
+import nbpipe
+import time
+
+# Constants
+SSH_PORT=22
+SSH_PATH=''
+
+CTRL_C=chr(3)
+
+READ_LAZY=0
+READ_SOME=1
+READ_ALL=2
+
+# set the path to ssh / plink, and chose the popen2 funciton to use
+if os.name=='posix':
+    import fssa    # we can look for ssh-agent on posix
+                   # XXX Can we on Win32/others?
+    import ptyext  # if my patch gets accepted, change this to check for a
+                   # sufficiently high version of python, and assign ptyext=pty
+                   # if sufficient.
+    sshpopen2=ptyext.popen2
+    CLOSE_STR='~.'
+    tp=os.popen('/usr/bin/which ssh')
+    SSH_PATH=tp.read().strip()
+    try:
+        tp.close()
+    except IOError:
+        # probably no child process
+        pass
+    if SSH_PATH == '':
+        tp=os.popen('command -v ssh')  # works in bash, ash etc, not csh etc.
+        SSH_PATH=tp.read().strip()
+        tp.close()
+    if SSH_PATH == '':
+        check = ['/usr/bin/ssh', '/usr/local/bin/ssh', '/bin/ssh']
+        for item in check:
+            if os.path.isfile(item):
+                SSH_PATH=item
+                break
+    PORT_STR='-p '
+else:
+    sshpopen2=os.popen2
+    CLOSE_STR=CTRL_C        # FIX-ME: This does not work.
+                            # I think I need to implement a 'kill' component
+                            # to the close function using win32api.
+    SSH_PATH=''
+    PORT_STR='-P '
+
+class mysshError(Exception):
+    """Error class for myssh."""
+    pass
+
+# Helper functions
+def _prompt(prompt):
+    """Print the message as the prompt for input.
+    Return the text entered."""
+    noecho = (prompt.lower().find('password:') >= 0) or \
+        (prompt.lower().find('passphrase:') >=0)
+    print """User input required for ssh connection.
+    (Type Ctrl-C to abort connection.)"""
+    abort = 0
+    try:
+        if noecho:
+            response = getpass.getpass(prompt)
+        else:
+            response = raw_input(prompt)
+    except KeyboardInterrupt:
+        response = ''
+        abort = 1
+    return response, abort
+
+class Ssh:
+    """A SSH connection class."""
+    def __init__(self, username=None, host='localhost', port=None):
+        """Constructor.  This does not try to connect."""
+        self.debuglevel = DEBUG_LEVEL
+        self.sshpath = SSH_PATH
+        self.username = username
+        self.host = host
+        self.port = port
+        self.isopen = 0
+        self.sshpid = 0  # perhaps merge this with isopen
+        self.old_handler = signal.getsignal(signal.SIGCHLD)
+        sig_handler = signal.signal(signal.SIGCHLD, self.sig_handler)
+        
+    def __del__(self):
+        """Destructor -- close the connection."""
+        if self.isopen:
+            self.close()
+    
+    def sig_handler(self, signum, stack):
+        """ Handle SIGCHLD signal """
+        if signum == signal.SIGCHLD:
+            try:
+                os.waitpid(self.sshpid, 0)
+            except:
+                pass
+        if self.old_handler != signal.SIG_DFL:
+            self.old_handler(signum, stack)
+
+    def attach_agent(self, key=None):
+        if os.name != 'posix':
+            # only posix support at this time
+            return
+        if 'SSH_AUTH_SOCK' not in os.environ.keys():
+            fssa.fssa(key)
+
+    def set_debuglevel(self, debuglevel):
+        """Set the debug level."""
+        self.debuglevel = debuglevel
+        
+    def set_sshpath(self, sshpath):
+        """Set the ssh path."""
+        self.sshpath=sshpath
+    
+    # Low level functions
+    def open(self, cmd=None):
+        """Opens a ssh connection.
+        
+        Raises an mysshError if myssh.sshpath is not a file.
+        Raises an error if attempting to open an already open connection.
+        """
+        self.attach_agent()
+        if not os.path.isfile(self.sshpath):
+            raise mysshError, \
+            "Path to ssh or plink is not defined or invalid.\nsshpath='%s'" \
+             % self.sshpath
+        if self.isopen:
+            raise mysshError, "Connection already open."
+        sshargs = ''
+        if self.sshpath.lower().find('plink') != -1:
+            sshargs = '-ssh '
+        if self.port and self.port != '':
+            sshargs += PORT_STR + `self.port` + ' '
+        if self.username and self.username !='':
+            sshargs += self.username + '@'
+        sshargs += self.host
+        if cmd:
+            sshargs += ' ' + cmd
+        if self.debuglevel:
+            print ">> Running %s %s." % (self.sshpath, sshargs)
+        # temporary workaround until I get pid's working under win32
+        if os.name == 'posix':
+            self.sshin, self.sshoutblocking, self.sshpid = \
+                                sshpopen2(self.sshpath + ' ' + sshargs)
+        else:
+            self.sshin, self.sshoutblocking = \
+                                sshpopen2(self.sshpath + ' ' + sshargs)
+        self.sshout = nbpipe.nbpipe(self.sshoutblocking)
+        self.isopen = 1
+        if self.debuglevel:
+            print ">> ssh pid is %s." % self.sshpid
+        
+    def close(self, addnewline=1):
+        """Close the ssh connection by closing the input and output pipes.
+        Returns the closing messages.
+        
+        On Posix systems, by default it adds a newline before sending the
+        disconnect escape sequence.   Turn this off by setting addnewline=0.
+        """
+        if os.name == 'posix':
+            try:
+                if addnewline:
+                    self.write('\n')
+                self.write(CLOSE_STR)
+            except (OSError, IOError, mysshError):
+                pass
+        output = self.read_lazy()
+        try:
+            self.sshin.close()
+            self.sshoutblocking.close()
+        except:
+            pass
+        if os.name == 'posix':
+            try:
+                os.kill(self.sshpid, signal.SIGHUP)
+            except:
+                pass
+        self.isopen = 0
+        if self.debuglevel:
+            print ">> Connection closed."
+        return output
+        
+    def write(self, text):
+        """Send text to the ssh process."""
+        # May block?? Probably not in practice, as ssh has a large input buffer.
+        if self.debuglevel:
+            print ">> Sending %s" % text
+        if self.isopen:
+            while len(text):
+                numtaken = os.write(self.sshin.fileno(),text)
+                if self.debuglevel:
+                    print ">> %s characters taken" % numtaken
+                text = text[numtaken:]
+        else:
+            raise mysshError, "Attempted to write to closed connection."
+    
+    # There is a question about what to do with connections closed by the other
+    # end.  Should write and read check for this, and force proper close?
+    def read_very_lazy(self):
+        """Very lazy read from sshout. Just reads from text already queued."""
+        return self.sshout.read_very_lazy()
+    
+    def read_lazy(self):
+        """Lazy read from sshout.  Waits a little, but does not block."""
+        return self.sshout.read_lazy()
+    
+    def read_some(self):
+        """Always read at least one block, unless the connection is closed.
+        My block."""
+        if self.isopen:
+            return self.sshout.read_some()
+        else:
+            return self.sshout.read_very_lazy()    
+        
+    def read_all(self):
+        """Reads until end of file hit.  May block."""
+        if self.isopen:
+            return self.sshout.read_all()
+        else:
+            return self.sshout.read_very_lazy()
+        
+    # High level funcitons
+    def login(self, logintext='Last login:', prompt_callback=_prompt):
+        """Logs in to the ssh host.  Checks for standard prompts, and calls
+        the function passed as promptcb to process them.
+        Returns the login banner, or 'None' if login process aborted.
+        """
+        self.open()
+        banner = self.read_some()
+        if self.debuglevel:
+            print ">> 1st banner read is: %s" % banner
+        while banner.find(logintext) == -1:
+            response, abort = prompt_callback(banner)
+            if abort:
+                return self.close()
+            self.write(response + '\n')
+            banner = self.read_some()
+        return banner
+    
+    def logout(self):
+        """Logs out the session."""
+        self.close()
+        
+    def sendcmd(self, cmd, readtype=READ_SOME):
+        """Sends the command 'cmd' over the ssh connection, and returns the
+        result.  By default it uses read_some, which may block.
+        """
+        if cmd[-1] != '\n':
+            cmd += '\n'
+        self.write(cmd)
+        if readtype == READ_ALL:
+            return self.read_all()
+        elif readtype == READ_LAZY:
+            return self.read_lazy()
+        else:
+            return self.read_some()
+    
+def test():
+    """Test routine for myssh.
+    
+    Usage: python myssh.py [-d] [-sshp path-to-ssh] [username at host | host] [port]
+    
+    Default host is localhost, default port is 22.
+    """
+    import sys
+    debug = 0
+    if sys.argv[1:] and sys.argv[1] == '-d':
+        debug = 1
+        del sys.argv[1]
+    testsshpath = SSH_PATH
+    if sys.argv[1:] and sys.argv[1] == '-sshp':
+        testsshpath = sys.argv[2]
+        del sys.argv[1]
+        del sys.argv[1]
+    testusername = None
+    testhost = 'localhost'
+    testport = '22'
+    if sys.argv[1:]:
+        testhost = sys.argv[1]
+        if testhost.find('@') != -1:
+            testusername, testhost = testhost.split('@')
+    if sys.argv[2:]:
+        testport = sys.argv[2]
+        
+    testcon = Ssh(testusername, testhost, testport)
+    testcon.set_debuglevel(debug)
+    testcon.set_sshpath(testsshpath)
+    testcon.login()
+    
+    cmd = None
+    while (cmd != 'exit') and testcon.isopen:
+        cmd = raw_input("Enter command to send: ")
+        print testcon.sendcmd(cmd)
+    testcon.close()
+
+if __name__ == '__main__':
+    test()



More information about the tor-commits mailing list