
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

