import socket, string, SSLeay import nntplib, ftplib, httplib from urllib import * # Standard ports used for secure services HTTPS_PORT = 443 SNNTP_PORT = 563 class SSL_URLopener(URLopener): # Use FTP protocol def open_ftps(self, url): host, path = splithost(url) if not host: raise IOError, ('ftp error', 'no host given') host, port = splitport(host) user, host = splituser(host) if user: user, passwd = splitpasswd(user) else: passwd = None host = socket.gethostbyname(host) if not port: import ftplib port = ftplib.FTP_PORT path, attrs = splitattr(path) dirs = string.splitfields(path, '/') dirs, file = dirs[:-1], dirs[-1] if dirs and not dirs[0]: dirs = dirs[1:] key = (user, host, port, string.joinfields(dirs, '/')) try: if not self.ftpcache.has_key(key): self.ftpcache[key] = \ SSLftpwrapper(user, passwd, host, port, dirs) if not file: type = 'D' else: type = 'I' for attr in attrs: attr, value = splitvalue(attr) if string.lower(attr) == 'type' and \ value in ('a', 'A', 'i', 'I', 'd', 'D'): type = string.upper(value) return addinfourl(self.ftpcache[key].retrfile(file, type), noheaders(), self.openedurl) except ftperrors(), msg: raise IOError, ('ftp error', msg) # Use HTTP protocol -- handle proxying of SSL URLs def open_http(self, url, SSL=0): import httplib SSL_proxying=0 user_passwd='' if type(url) is type(""): host, selector = splithost(url) user_passwd, host = splituser(host) else: host, selector = url urltype, rest = splittype(selector) urltype=string.lower(urltype) if urltype in ['http', 'https']: realhost, rest = splithost(rest) user_passwd, realhost = splituser(realhost) if user_passwd: selector = "%s://%s%s" % (urltype, realhost, rest) if urltype=='https': SSL_proxying=1 else: realhost, rest = splithost(rest) user_passwd, realhost = splituser(realhost) print "proxy via http:", host, selector if not host: raise IOError, ('http error', 'no host given') if user_passwd: import base64 auth = string.strip(base64.encodestring(user_passwd)) else: auth = None h = SSL_HTTP(host, SSL=SSL) # If we're proxying an https:// URL, the standard # procedure seems to be: # * Client issues CONNECT www.rsa.com:443 HTTP/1.0 # * Proxy replies with headers, & then serves as an # intermediary # * Client & SSL server perform SSL negotiation; the # proxy simply copies bytes back and forth. # * Client sends HTTP request; server replies, etc. # XXX should the auths be sent to the proxy, or the # SSL host? Or both? if SSL_proxying: SSLtype, SSLhost=splittype(selector) selector=selector[len(SSLtype)+1:] SSLhost, selector = splithost(selector) if not ':' in SSLhost: SSLhost=SSLhost+':'+str(HTTPS_PORT) h.putrequest('CONNECT', SSLhost) else: h.putrequest('GET', selector) if auth: h.putheader('Authorization: Basic %s' % auth) for args in self.addheaders: apply(h.putheader, args) h.endheaders() errcode, errmsg, headers = h.getreply() fp = h.getfile() if errcode == 200: if not SSL_proxying: return addinfourl(fp, headers, self.openedurl) else: fp=SSLeay.fromfd(fp.fileno() ) fp.connect() # Whew! Now we go through all this again! h=SSL_HTTP() h.connect(SSLhost, sock=fp) h.putrequest('GET', selector) h.endheaders() errcode, errmsg, headers = h.getreply() fp = h.getfile() if errcode==200: return addinfourl(fp, headers, self.openedurl) else: return self.http_error(url, fp, errcode, errmsg, headers) else: return self.http_error(url, fp, errcode, errmsg, headers) # Use HTTP protocol via SSL def open_https(self, url): return self.open_http(url, SSL=1) # Helper class, needed for ftps support class SSLftpwrapper(ftpwrapper): def __init__(self, user, passwd, host, port, dirs): self.SSL=1 ftpwrapper.__init__(self, user, passwd, host, port, dirs) def init(self): self.ftp = SSL_FTP(SSL=self.SSL) self.ftp.connect(self.host, self.port) self.ftp.login(self.user, self.passwd) for dir in self.dirs: self.ftp.cwd(dir) class SSL_HTTP(httplib.HTTP): def __init__(self, host = '', port = 0, SSL = 0): self.debuglevel = 0 # XXX self.SSL=SSL self.file = None if host: self.connect(host, port) def connect(self, host, port = 0, sock=None): if not port: i = string.find(host, ':') if i >= 0: host, port = host[:i], host[i+1:] try: port = string.atoi(port) except string.atoi_error: raise socket.error, "nonnumeric port" if not port: if not self.SSL: port = httplib.HTTP_PORT else: port=HTTPS_PORT if sock==None: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.debuglevel > 0: print 'connect:', (host, port) self.sock.connect(host, port) else: self.sock=sock # Once the connection is made, activate SSL if desired if self.SSL: import SSLeay self.sock=SSLeay.fromfd(self.sock.fileno()) if self.debuglevel > 0: print 'SSL connect' self.sock.connect() if self.debuglevel > 0: print 'Cipher algorithm:', self.sock.cipher x=self.sock.peer_cert print "Peer's Issuer:", x.oneline_issuer_name print "Peer's Subject:", x.oneline_subject class SSL_NNTP(nntplib.NNTP): # Initialize an instance. Arguments: # - host: hostname to connect to # - port: port to connect to (default the standard NNTP port) def __init__(self, host, port = -1, SSL=0): self.host = host ; self.SSL=SSL if port==-1: if not SSL: self.port=nntplib.NNTP_PORT else: self.port=SNNTP_PORT else: self.port=port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect(self.host, self.port) if SSL: # Perform the SSL negotiation import SSLeay self.sock=SSLeay.fromfd(self.sock.fileno()) self.sock.connect() self.file = self.sock.makefile('rb') self.debugging = 0 self.welcome = self.getresp() # This code works with SSLftpd, which is the only SSL FTP server I # know of. (Is there an RFC anywhere for SSL-FTP?) class SSL_FTP(ftplib.FTP): # Initialization method (called by class instantiation). # Initialize host to localhost, port to standard ftp port # Optional arguments are host (for connect()), # and user, passwd, acct (for login()) def __init__(self, host = '', user = '', passwd = '', acct = '', SSL = 0): self.SSL=SSL ftplib.FTP.__init__(self, host, user, passwd, acct) def login(self, user = '', passwd = '', acct = ''): ftplib.FTP.login(self, user, passwd, acct) self.__sockname=self.sock.getsockname() if self.SSL: try: if self.debugging: print 'sending AUTH SSL' self.putcmd('AUTH SSL') resp=self.getresp() # OK... SSL encryption is supported if self.debugging: print 'SSL accepted; negotiating' self.file=self.sock=SSLeay.fromfd(self.sock.fileno()) self.sock.connect() if self.debugging: print 'Cipher algorithm:', self.sock.cipher x=self.sock.peer_cert print "Peer's Issuer:", x.oneline_issuer_name print "Peer's Subject:", x.oneline_subject # self.set_pasv(1) except ftplib.error_perm, resp: # The server doesn't support SSL self.SSL=0 if self.debugging: print 'ftp server doesn\'t support SSL' def transfercmd(self, cmd): '''Initiate a transfer over the data connection. If the transfer is active, send a port command and the transfer command, and accept the connection. If the server is passive, send a pasv command, connect to it, and start the transfer command. Either way, return the socket for the connection''' if self.passiveserver: host, port = ftplib.parse227(self.sendcmd('PASV')) conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) conn.connect(host, port) if self.SSL: if self.debugging: print 'SSL connect()' conn=SSLeay.fromfd(conn.fileno()) if self.debugging: print 'SSL connect2()' conn.connect() if self.debugging: print 'SSL connect3()' if self.debugging: print 'SSL cipher=', conn.cipher resp = self.sendcmd(cmd) if resp[0] <> '1': raise ftplib.error_reply, resp else: sock = self.makeport() resp = self.sendcmd(cmd) if resp[0] <> '1': raise ftplib.error_reply, resp conn, sockaddr = sock.accept() if self.SSL: conn=SSLeay.fromfd(conn.fileno()) # XXX The following won't work, since we # haven't defined a certificate to use. if self.debugging: print 'SSL connect()' conn.connect() if self.debugging: print 'SSL cipher=', conn.cipher file=conn.makefile() return conn def makeport(self): '''Create a new socket and send a PORT command for it.''' global nextport sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('', 0)) sock.listen(1) dummyhost, port = sock.getsockname() # Get proper port host, dummyport = self.__sockname # Get proper host resp = self.sendport(host, port) return sock