summaryrefslogtreecommitdiff
path: root/src/xmpppy-0.5.0rc1/xmpp/transports.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/xmpppy-0.5.0rc1/xmpp/transports.py')
-rw-r--r--src/xmpppy-0.5.0rc1/xmpp/transports.py339
1 files changed, 339 insertions, 0 deletions
diff --git a/src/xmpppy-0.5.0rc1/xmpp/transports.py b/src/xmpppy-0.5.0rc1/xmpp/transports.py
new file mode 100644
index 00000000..0e3eec90
--- /dev/null
+++ b/src/xmpppy-0.5.0rc1/xmpp/transports.py
@@ -0,0 +1,339 @@
+## transports.py
+##
+## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: transports.py,v 1.35 2009/04/07 08:34:09 snakeru Exp $
+
+"""
+This module contains the low-level implementations of xmpppy connect methods or
+(in other words) transports for xmpp-stanzas.
+Currently here is three transports:
+direct TCP connect - TCPsocket class
+proxied TCP connect - HTTPPROXYsocket class (CONNECT proxies)
+TLS connection - TLS class. Can be used for SSL connections also.
+
+Transports are stackable so you - f.e. TLS use HTPPROXYsocket or TCPsocket as more low-level transport.
+
+Also exception 'error' is defined to allow capture of this module specific exceptions.
+"""
+
+import socket,select,base64,dispatcher,sys
+from simplexml import ustr
+from client import PlugIn
+from protocol import *
+
+# determine which DNS resolution library is available
+HAVE_DNSPYTHON = False
+HAVE_PYDNS = False
+try:
+ import dns.resolver # http://dnspython.org/
+ HAVE_DNSPYTHON = True
+except ImportError:
+ try:
+ import DNS # http://pydns.sf.net/
+ HAVE_PYDNS = True
+ except ImportError:
+ pass
+
+DATA_RECEIVED='DATA RECEIVED'
+DATA_SENT='DATA SENT'
+
+class error:
+ """An exception to be raised in case of low-level errors in methods of 'transports' module."""
+ def __init__(self,comment):
+ """Cache the descriptive string"""
+ self._comment=comment
+
+ def __str__(self):
+ """Serialise exception into pre-cached descriptive string."""
+ return self._comment
+
+BUFLEN=1024
+class TCPsocket(PlugIn):
+ """ This class defines direct TCP connection method. """
+ def __init__(self, server=None, use_srv=True):
+ """ Cache connection point 'server'. 'server' is the tuple of (host, port)
+ absolutely the same as standard tcp socket uses. However library will lookup for
+ ('_xmpp-client._tcp.' + host) SRV record in DNS and connect to the found (if it is)
+ server instead
+ """
+ PlugIn.__init__(self)
+ self.DBG_LINE='socket'
+ self._exported_methods=[self.send,self.disconnect]
+ self._server, self.use_srv = server, use_srv
+
+ def srv_lookup(self, server):
+ " SRV resolver. Takes server=(host, port) as argument. Returns new (host, port) pair "
+ if HAVE_DNSPYTHON or HAVE_PYDNS:
+ host, port = server
+ possible_queries = ['_xmpp-client._tcp.' + host]
+
+ for query in possible_queries:
+ try:
+ if HAVE_DNSPYTHON:
+ answers = [x for x in dns.resolver.query(query, 'SRV')]
+ if answers:
+ host = str(answers[0].target)
+ port = int(answers[0].port)
+ break
+ elif HAVE_PYDNS:
+ # ensure we haven't cached an old configuration
+ DNS.DiscoverNameServers()
+ response = DNS.Request().req(query, qtype='SRV')
+ answers = response.answers
+ if len(answers) > 0:
+ # ignore the priority and weight for now
+ _, _, port, host = answers[0]['data']
+ del _
+ port = int(port)
+ break
+ except:
+ self.DEBUG('An error occurred while looking up %s' % query, 'warn')
+ server = (host, port)
+ else:
+ self.DEBUG("Could not load one of the supported DNS libraries (dnspython or pydns). SRV records will not be queried and you may need to set custom hostname/port for some servers to be accessible.\n",'warn')
+ # end of SRV resolver
+ return server
+
+ def plugin(self, owner):
+ """ Fire up connection. Return non-empty string on success.
+ Also registers self.disconnected method in the owner's dispatcher.
+ Called internally. """
+ if not self._server: self._server=(self._owner.Server,5222)
+ if self.use_srv: server=self.srv_lookup(self._server)
+ else: server=self._server
+ if not self.connect(server): return
+ self._owner.Connection=self
+ self._owner.RegisterDisconnectHandler(self.disconnected)
+ return 'ok'
+
+ def getHost(self):
+ """ Return the 'host' value that is connection is [will be] made to."""
+ return self._server[0]
+ def getPort(self):
+ """ Return the 'port' value that is connection is [will be] made to."""
+ return self._server[1]
+
+ def connect(self,server=None):
+ """ Try to connect to the given host/port. Does not lookup for SRV record.
+ Returns non-empty string on success. """
+ try:
+ if not server: server=self._server
+ self._sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._sock.connect((server[0], int(server[1])))
+ self._send=self._sock.sendall
+ self._recv=self._sock.recv
+ self.DEBUG("Successfully connected to remote host %s"%`server`,'start')
+ return 'ok'
+ except socket.error, (errno, strerror):
+ self.DEBUG("Failed to connect to remote host %s: %s (%s)"%(`server`, strerror, errno),'error')
+ except: pass
+
+ def plugout(self):
+ """ Disconnect from the remote server and unregister self.disconnected method from
+ the owner's dispatcher. """
+ self._sock.close()
+ if self._owner.__dict__.has_key('Connection'):
+ del self._owner.Connection
+ self._owner.UnregisterDisconnectHandler(self.disconnected)
+
+ def receive(self):
+ """ Reads all pending incoming data.
+ In case of disconnection calls owner's disconnected() method and then raises IOError exception."""
+ try: received = self._recv(BUFLEN)
+ except socket.sslerror,e:
+ self._seen_data=0
+ if e[0]==socket.SSL_ERROR_WANT_READ: return ''
+ if e[0]==socket.SSL_ERROR_WANT_WRITE: return ''
+ self.DEBUG('Socket error while receiving data','error')
+ sys.exc_clear()
+ self._owner.disconnected()
+ raise IOError("Disconnected from server")
+ except: received = ''
+
+ while self.pending_data(0):
+ try: add = self._recv(BUFLEN)
+ except: add=''
+ received +=add
+ if not add: break
+
+ if len(received): # length of 0 means disconnect
+ self._seen_data=1
+ self.DEBUG(received,'got')
+ if hasattr(self._owner, 'Dispatcher'):
+ self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
+ else:
+ self.DEBUG('Socket error while receiving data','error')
+ self._owner.disconnected()
+ raise IOError("Disconnected from server")
+ return received
+
+ def send(self,raw_data):
+ """ Writes raw outgoing data. Blocks until done.
+ If supplied data is unicode string, encodes it to utf-8 before send."""
+ if type(raw_data)==type(u''): raw_data = raw_data.encode('utf-8')
+ elif type(raw_data)<>type(''): raw_data = ustr(raw_data).encode('utf-8')
+ try:
+ self._send(raw_data)
+ # Avoid printing messages that are empty keepalive packets.
+ if raw_data.strip():
+ self.DEBUG(raw_data,'sent')
+ if hasattr(self._owner, 'Dispatcher'): # HTTPPROXYsocket will send data before we have a Dispatcher
+ self._owner.Dispatcher.Event('', DATA_SENT, raw_data)
+ except:
+ self.DEBUG("Socket error while sending data",'error')
+ self._owner.disconnected()
+
+ def pending_data(self,timeout=0):
+ """ Returns true if there is a data ready to be read. """
+ return select.select([self._sock],[],[],timeout)[0]
+
+ def disconnect(self):
+ """ Closes the socket. """
+ self.DEBUG("Closing socket",'stop')
+ self._sock.close()
+
+ def disconnected(self):
+ """ Called when a Network Error or disconnection occurs.
+ Designed to be overidden. """
+ self.DEBUG("Socket operation failed",'error')
+
+DBG_CONNECT_PROXY='CONNECTproxy'
+class HTTPPROXYsocket(TCPsocket):
+ """ HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
+ redefines only connect method. Allows to use HTTP proxies like squid with
+ (optionally) simple authentication (using login and password). """
+ def __init__(self,proxy,server,use_srv=True):
+ """ Caches proxy and target addresses.
+ 'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
+ and optional keys 'user' and 'password' to use for authentication.
+ 'server' argument is a tuple of host and port - just like TCPsocket uses. """
+ TCPsocket.__init__(self,server,use_srv)
+ self.DBG_LINE=DBG_CONNECT_PROXY
+ self._proxy=proxy
+
+ def plugin(self, owner):
+ """ Starts connection. Used interally. Returns non-empty string on success."""
+ owner.debug_flags.append(DBG_CONNECT_PROXY)
+ return TCPsocket.plugin(self,owner)
+
+ def connect(self,dupe=None):
+ """ Starts connection. Connects to proxy, supplies login and password to it
+ (if were specified while creating instance). Instructs proxy to make
+ connection to the target server. Returns non-empty sting on success. """
+ if not TCPsocket.connect(self,(self._proxy['host'],self._proxy['port'])): return
+ self.DEBUG("Proxy server contacted, performing authentification",'start')
+ connector = ['CONNECT %s:%s HTTP/1.0'%self._server,
+ 'Proxy-Connection: Keep-Alive',
+ 'Pragma: no-cache',
+ 'Host: %s:%s'%self._server,
+ 'User-Agent: HTTPPROXYsocket/v0.1']
+ if self._proxy.has_key('user') and self._proxy.has_key('password'):
+ credentials = '%s:%s'%(self._proxy['user'],self._proxy['password'])
+ credentials = base64.encodestring(credentials).strip()
+ connector.append('Proxy-Authorization: Basic '+credentials)
+ connector.append('\r\n')
+ self.send('\r\n'.join(connector))
+ try: reply = self.receive().replace('\r','')
+ except IOError:
+ self.DEBUG('Proxy suddenly disconnected','error')
+ self._owner.disconnected()
+ return
+ try: proto,code,desc=reply.split('\n')[0].split(' ',2)
+ except: raise error('Invalid proxy reply')
+ if code<>'200':
+ self.DEBUG('Invalid proxy reply: %s %s %s'%(proto,code,desc),'error')
+ self._owner.disconnected()
+ return
+ while reply.find('\n\n') == -1:
+ try: reply += self.receive().replace('\r','')
+ except IOError:
+ self.DEBUG('Proxy suddenly disconnected','error')
+ self._owner.disconnected()
+ return
+ self.DEBUG("Authentification successfull. Jabber server contacted.",'ok')
+ return 'ok'
+
+ def DEBUG(self,text,severity):
+ """Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy"."""
+ return self._owner.DEBUG(DBG_CONNECT_PROXY,text,severity)
+
+class TLS(PlugIn):
+ """ TLS connection used to encrypts already estabilished tcp connection."""
+ def PlugIn(self,owner,now=0):
+ """ If the 'now' argument is true then starts using encryption immidiatedly.
+ If 'now' in false then starts encryption as soon as TLS feature is
+ declared by the server (if it were already declared - it is ok).
+ """
+ if owner.__dict__.has_key('TLS'): return # Already enabled.
+ PlugIn.PlugIn(self,owner)
+ DBG_LINE='TLS'
+ if now: return self._startSSL()
+ if self._owner.Dispatcher.Stream.features:
+ try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
+ except NodeProcessed: pass
+ else: self._owner.RegisterHandlerOnce('features',self.FeaturesHandler,xmlns=NS_STREAMS)
+ self.starttls=None
+
+ def plugout(self,now=0):
+ """ Unregisters TLS handler's from owner's dispatcher. Take note that encription
+ can not be stopped once started. You can only break the connection and start over."""
+ self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
+ self._owner.UnregisterHandler('proceed',self.StartTLSHandler,xmlns=NS_TLS)
+ self._owner.UnregisterHandler('failure',self.StartTLSHandler,xmlns=NS_TLS)
+
+ def FeaturesHandler(self, conn, feats):
+ """ Used to analyse server <features/> tag for TLS support.
+ If TLS is supported starts the encryption negotiation. Used internally"""
+ if not feats.getTag('starttls',namespace=NS_TLS):
+ self.DEBUG("TLS unsupported by remote server.",'warn')
+ return
+ self.DEBUG("TLS supported by remote server. Requesting TLS start.",'ok')
+ self._owner.RegisterHandlerOnce('proceed',self.StartTLSHandler,xmlns=NS_TLS)
+ self._owner.RegisterHandlerOnce('failure',self.StartTLSHandler,xmlns=NS_TLS)
+ self._owner.Connection.send('<starttls xmlns="%s"/>'%NS_TLS)
+ raise NodeProcessed
+
+ def pending_data(self,timeout=0):
+ """ Returns true if there possible is a data ready to be read. """
+ return self._tcpsock._seen_data or select.select([self._tcpsock._sock],[],[],timeout)[0]
+
+ def _startSSL(self):
+ """ Immidiatedly switch socket to TLS mode. Used internally."""
+ """ Here we should switch pending_data to hint mode."""
+ tcpsock=self._owner.Connection
+ tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
+ tcpsock._sslIssuer = tcpsock._sslObj.issuer()
+ tcpsock._sslServer = tcpsock._sslObj.server()
+ tcpsock._recv = tcpsock._sslObj.read
+ tcpsock._send = tcpsock._sslObj.write
+
+ tcpsock._seen_data=1
+ self._tcpsock=tcpsock
+ tcpsock.pending_data=self.pending_data
+ tcpsock._sock.setblocking(0)
+
+ self.starttls='success'
+
+ def StartTLSHandler(self, conn, starttls):
+ """ Handle server reply if TLS is allowed to process. Behaves accordingly.
+ Used internally."""
+ if starttls.getNamespace()<>NS_TLS: return
+ self.starttls=starttls.getName()
+ if self.starttls=='failure':
+ self.DEBUG("Got starttls response: "+self.starttls,'error')
+ return
+ self.DEBUG("Got starttls proceed response. Switching to TLS/SSL...",'ok')
+ self._startSSL()
+ self._owner.Dispatcher.PlugOut()
+ dispatcher.Dispatcher().PlugIn(self._owner)