[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[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)
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@xxxxxxxxxxxxxxx>
+
+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@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()