Package spade :: Package xmpp :: Module client
[hide private]
[frames] | no frames]

Source Code for Module spade.xmpp.client

  1  ##   client.py 
  2  ## 
  3  ##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 
  4  ## 
  5  ##   This program is free software; you can redistribute it and/or modify 
  6  ##   it under the terms of the GNU General Public License as published by 
  7  ##   the Free Software Foundation; either version 2, or (at your option) 
  8  ##   any later version. 
  9  ## 
 10  ##   This program is distributed in the hope that it will be useful, 
 11  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  ##   GNU General Public License for more details. 
 14   
 15  # $Id: client.py,v 1.54 2006/02/11 15:37:09 snakeru Exp $ 
 16   
 17  """ 
 18  Provides PlugIn class functionality to develop extentions for xmpppy. 
 19  Also provides Client and Component classes implementations as the 
 20  examples of xmpppy structures usage. 
 21  These classes can be used for simple applications "AS IS" though. 
 22  """ 
 23   
 24  import socket 
 25  import debug 
 26  Debug=debug 
 27  Debug.DEBUGGING_IS_ON=1 
 28  Debug.Debug.colors['socket']=debug.color_dark_gray 
 29  Debug.Debug.colors['CONNECTproxy']=debug.color_dark_gray 
 30  Debug.Debug.colors['nodebuilder']=debug.color_brown 
 31  Debug.Debug.colors['client']=debug.color_cyan 
 32  Debug.Debug.colors['component']=debug.color_cyan 
 33  Debug.Debug.colors['dispatcher']=debug.color_green 
 34  Debug.Debug.colors['browser']=debug.color_blue 
 35  Debug.Debug.colors['auth']=debug.color_yellow 
 36  Debug.Debug.colors['roster']=debug.color_magenta 
 37  Debug.Debug.colors['ibb']=debug.color_yellow 
 38   
 39  Debug.Debug.colors['down']=debug.color_brown 
 40  Debug.Debug.colors['up']=debug.color_brown 
 41  Debug.Debug.colors['data']=debug.color_brown 
 42  Debug.Debug.colors['ok']=debug.color_green 
 43  Debug.Debug.colors['warn']=debug.color_yellow 
 44  Debug.Debug.colors['error']=debug.color_red 
 45  Debug.Debug.colors['start']=debug.color_dark_gray 
 46  Debug.Debug.colors['stop']=debug.color_dark_gray 
 47  Debug.Debug.colors['sent']=debug.color_yellow 
 48  Debug.Debug.colors['got']=debug.color_bright_cyan 
 49   
 50  DBG_CLIENT='client' 
 51  DBG_COMPONENT='component' 
 52   
53 -class PlugIn:
54 """ Common xmpppy plugins infrastructure: plugging in/out, debugging. """
55 - def __init__(self):
56 self._exported_methods=[] 57 self.DBG_LINE=self.__class__.__name__.lower()
58
59 - def PlugIn(self,owner):
60 """ Attach to main instance and register ourself and all our staff in it. """ 61 self._owner=owner 62 if self.DBG_LINE not in owner.debug_flags: 63 owner.debug_flags.append(self.DBG_LINE) 64 self.DEBUG('Plugging %s into %s'%(self,self._owner),'start') 65 if owner.__dict__.has_key(self.__class__.__name__): 66 return self.DEBUG('Plugging ignored: another instance already plugged.','error') 67 self._old_owners_methods=[] 68 for method in self._exported_methods: 69 if owner.__dict__.has_key(method.__name__): 70 self._old_owners_methods.append(owner.__dict__[method.__name__]) 71 owner.__dict__[method.__name__]=method 72 owner.__dict__[self.__class__.__name__]=self 73 if self.__class__.__dict__.has_key('plugin'): return self.plugin(owner)
74
75 - def PlugOut(self):
76 """ Unregister all our staff from main instance and detach from it. """ 77 self.DEBUG('Plugging %s out of %s.'%(self,self._owner),'stop') 78 self._owner.debug_flags.remove(self.DBG_LINE) 79 for method in self._exported_methods: 80 try: 81 del self._owner.__dict__[method.__name__] 82 except: pass 83 for method in self._old_owners_methods: self._owner.__dict__[method.__name__]=method 84 del self._owner.__dict__[self.__class__.__name__] 85 if self.__class__.__dict__.has_key('plugout'): return self.plugout()
86
87 - def DEBUG(self,text,severity='info'):
88 """ Feed a provided debug line to main instance's debug facility along with our ID string. """ 89 self._owner.DEBUG(self.DBG_LINE,text,severity)
90 91 import transports,dispatcher,auth,roster
92 -class CommonClient:
93 """ Base for Client and Component classes."""
94 - def __init__(self,server,port=5222,debug=['always', 'nodebuilder']):
95 """ Caches server name and (optionally) port to connect to. "debug" parameter specifies 96 the debug IDs that will go into debug output. You can either specifiy an "include" 97 or "exclude" list. The latter is done via adding "always" pseudo-ID to the list. 98 Full list: ['nodebuilder', 'dispatcher', 'gen_auth', 'SASL_auth', 'bind', 'socket', 99 'CONNECTproxy', 'TLS', 'roster', 'browser', 'ibb'] . """ 100 if self.__class__.__name__=='Client': self.Namespace,self.DBG='jabber:client',DBG_CLIENT 101 elif self.__class__.__name__=='Component': self.Namespace,self.DBG=dispatcher.NS_COMPONENT_ACCEPT,DBG_COMPONENT 102 self.defaultNamespace=self.Namespace 103 self.disconnect_handlers=[] 104 self.Server=server 105 self.Port=port 106 if debug and type(debug)<>list: debug=['always', 'nodebuilder'] 107 self._DEBUG=Debug.Debug(debug) 108 self.DEBUG=self._DEBUG.Show 109 self.debug_flags=self._DEBUG.debug_flags 110 self.debug_flags.append(self.DBG) 111 self._owner=self 112 self._registered_name=None 113 self.RegisterDisconnectHandler(self.DisconnectHandler) 114 self.connected='' 115 self._component=0
116
117 - def RegisterDisconnectHandler(self,handler):
118 """ Register handler that will be called on disconnect.""" 119 self.disconnect_handlers.append(handler)
120
121 - def UnregisterDisconnectHandler(self,handler):
122 """ Unregister handler that is called on disconnect.""" 123 self.disconnect_handlers.remove(handler)
124
125 - def disconnected(self):
126 """ Called on disconnection. Calls disconnect handlers and cleans things up. """ 127 self.connected='' 128 self.DEBUG(self.DBG,'Disconnect detected','stop') 129 self.disconnect_handlers.reverse() 130 for i in self.disconnect_handlers: i() 131 self.disconnect_handlers.reverse() 132 if self.__dict__.has_key('TLS'): self.TLS.PlugOut()
133
134 - def DisconnectHandler(self):
135 """ Default disconnect handler. Just raises an IOError. 136 If you choosed to use this class in your production client, 137 override this method or at least unregister it. """ 138 raise IOError('Disconnected from server.')
139
140 - def event(self,eventName,args={}):
141 """ Default event handler. To be overriden. """ 142 print "Event: ",(eventName,args)
143
144 - def isConnected(self):
145 """ Returns connection state. F.e.: None / 'tls' / 'tcp+non_sasl' . """ 146 return self.connected
147
148 - def reconnectAndReauth(self):
149 """ Example of reconnection method. In fact, it can be used to batch connection and auth as well. """ 150 handlerssave=self.Dispatcher.dumpHandlers() 151 if self.__dict__.has_key('NonSASL'): self.NonSASL.PlugOut() 152 if self.__dict__.has_key('SASL'): self.SASL.PlugOut() 153 if self.__dict__.has_key('TLS'): self.TLS.PlugOut() 154 if self.__dict__.has_key('HTTPPROXYsocket'): self.HTTPPROXYsocket.PlugOut() 155 if self.__dict__.has_key('TCPsocket'): self.TCPsocket.PlugOut() 156 self.Dispatcher.PlugOut() 157 158 if not self.connect(server=self._Server,proxy=self._Proxy): return 159 if not self.auth(self._User,self._Password,self._Resource): return 160 self.Dispatcher.restoreHandlers(handlerssave) 161 return self.connected
162
163 - def connect(self,server=None,proxy=None,ssl=None,use_srv=None):
164 """ Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream. 165 Returns None or 'tcp' or 'tls', depending on the result.""" 166 if not server: server=(self.Server,self.Port) 167 if proxy: socket=transports.HTTPPROXYsocket(proxy,server,use_srv) 168 else: socket=transports.TCPsocket(server,use_srv) 169 connected=socket.PlugIn(self) 170 if not connected: 171 socket.PlugOut() 172 return 173 self._Server,self._Proxy=server,proxy 174 self.connected='tcp' 175 if (ssl is None and self.Connection.getPort() in (5223, 443)) or ssl: 176 try: # FIXME. This should be done in transports.py 177 transports.TLS().PlugIn(self,now=1) 178 self.connected='ssl' 179 except socket.sslerror: 180 return 181 dispatcher.Dispatcher().PlugIn(self) 182 while self.Dispatcher.Stream._document_attrs is None: 183 if not self.Process(1): return 184 if self.Dispatcher.Stream._document_attrs.has_key('version') and self.Dispatcher.Stream._document_attrs['version']=='1.0': 185 while not self.Dispatcher.Stream.features and self.Process(1): pass # If we get version 1.0 stream the features tag MUST BE presented 186 return self.connected
187
188 -class Client(CommonClient):
189 """ Example client class, based on CommonClient. """ 190 # def connect(self,server=None,proxy=None,secure=None,use_srv=True):
191 - def connect(self,server=None,proxy=None,secure=None,use_srv=None):
192 """ Connect to jabber server. If you want to specify different ip/port to connect to you can 193 pass it as tuple as first parameter. If there is HTTP proxy between you and server 194 specify it's address and credentials (if needed) in the second argument. 195 If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443) 196 If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1. 197 If you want to disable tls/ssl support completely, set it to 0. 198 Example: connect(('192.168.5.5',5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'}) 199 Returns '' or 'tcp' or 'tls', depending on the result.""" 200 if not CommonClient.connect(self,server,proxy,secure,use_srv) or secure<>None and not secure: return self.connected 201 transports.TLS().PlugIn(self) 202 if not self.Dispatcher.Stream._document_attrs.has_key('version') or not self.Dispatcher.Stream._document_attrs['version']=='1.0': return self.connected 203 while not self.Dispatcher.Stream.features and self.Process(1): pass # If we get version 1.0 stream the features tag MUST BE presented 204 if not self.Dispatcher.Stream.features.getTag('starttls'): return self.connected # TLS not supported by server 205 while not self.TLS.starttls and self.Process(1): pass 206 if not hasattr(self, 'TLS') or self.TLS.starttls!='success': self.event('tls_failed'); return self.connected 207 self.connected='tls' 208 return self.connected
209
210 - def auth(self,user,password,resource='',sasl=1):
211 """ Authenticate connnection and bind resource. If resource is not provided 212 random one or library name used. """ 213 self._User,self._Password,self._Resource=user,password,resource 214 while not self.Dispatcher.Stream._document_attrs and self.Process(1): pass 215 if self.Dispatcher.Stream._document_attrs.has_key('version') and self.Dispatcher.Stream._document_attrs['version']=='1.0': 216 while not self.Dispatcher.Stream.features and self.Process(1): pass # If we get version 1.0 stream the features tag MUST BE presented 217 if sasl: auth.SASL(user,password).PlugIn(self) 218 if not sasl or self.SASL.startsasl=='not-supported': 219 if not resource: resource='xmpppy' 220 if auth.NonSASL(user,password,resource).PlugIn(self): 221 self.connected+='+old_auth' 222 return 'old_auth' 223 return 224 self.SASL.auth() 225 while self.SASL.startsasl=='in-process' and self.Process(1): pass 226 if self.SASL.startsasl=='success': 227 auth.Bind().PlugIn(self) 228 while self.Bind.bound is None and self.Process(1): pass 229 if self.Bind.Bind(resource): 230 self.connected+='+sasl' 231 return 'sasl'
232
233 - def getRoster(self):
234 """ Return the Roster instance, previously plugging it in and 235 requesting roster from server if needed. """ 236 if not self.__dict__.has_key('Roster'): roster.Roster().PlugIn(self) 237 return self.Roster.getRoster()
238
239 - def sendInitPresence(self,requestRoster=1):
240 """ Send roster request and initial <presence/>. 241 You can disable the first by setting requestRoster argument to 0. """ 242 self.sendPresence(requestRoster=requestRoster)
243
244 - def sendPresence(self,jid=None,typ=None,requestRoster=0):
245 """ Send some specific presence state. 246 Can also request roster from server if according agrument is set.""" 247 if requestRoster: roster.Roster().PlugIn(self) 248 self.send(dispatcher.Presence(to=jid, typ=typ))
249
250 -class Component(CommonClient):
251 """ Component class. The only difference from CommonClient is ability to perform component authentication. """
252 - def __init__(self,server,port=5347,typ=None,debug=['always', 'nodebuilder'],domains=None,component=0):
253 """ Init function for Components. 254 As components use a different auth mechanism which includes the namespace of the component. 255 Jabberd1.4 and Ejabberd use the default namespace then for all client messages. 256 Jabberd2 uses jabber:client. 257 'server' argument is a server name that you are connecting to (f.e. "localhost"). 258 'port' can be specified if 'server' resolves to correct IP. If it is not then you'll need to specify IP 259 and port while calling "connect()".""" 260 CommonClient.__init__(self,server,port=port,debug=debug) 261 self.typ=typ 262 self.component=component 263 if domains: 264 self.domains=domains 265 else: 266 self.domains=[server]
267
268 - def connect(self,server=None,proxy=None):
269 """ This will connect to the server, and if the features tag is found then set 270 the namespace to be jabber:client as that is required for jabberd2. 271 'server' and 'proxy' arguments have the same meaning as in xmpp.Client.connect() """ 272 if self.component: 273 self.Namespace=auth.NS_COMPONENT_1 274 self.Server=server[0] 275 CommonClient.connect(self,server=server,proxy=proxy) 276 if self.connected and (self.typ=='jabberd2' or not self.typ and self.Dispatcher.Stream.features != None): 277 self.defaultNamespace=auth.NS_CLIENT 278 self.Dispatcher.RegisterNamespace(self.defaultNamespace) 279 self.Dispatcher.RegisterProtocol('iq',dispatcher.Iq) 280 self.Dispatcher.RegisterProtocol('message',dispatcher.Message) 281 self.Dispatcher.RegisterProtocol('presence',dispatcher.Presence) 282 return self.connected
283
284 - def auth(self,name,password,dup=None,sasl=0):
285 """ Authenticate component "name" with password "password".""" 286 self._User,self._Password,self._Resource=name,password,'' 287 try: 288 if self.component: sasl=1 289 if sasl: auth.SASL(name,password).PlugIn(self) 290 if not sasl or self.SASL.startsasl=='not-supported': 291 if auth.NonSASL(name,password,'').PlugIn(self): 292 self.connected+='+old_auth' 293 return 'old_auth' 294 return 295 self.SASL.auth() 296 while self.SASL.startsasl=='in-process' and self.Process(1): pass 297 if self.SASL.startsasl=='success': 298 if self.component: 299 self._component=self.component 300 for domain in self.domains: 301 auth.ComponentBind().PlugIn(self) 302 while self.ComponentBind.bound is None: self.Process(1) 303 if (not self.ComponentBind.Bind(domain)): 304 self.ComponentBind.PlugOut() 305 return 306 self.ComponentBind.PlugOut() 307 self.connected+='+sasl' 308 return 'sasl' 309 else: 310 raise auth.NotAuthorized(self.SASL.startsasl) 311 except: 312 self.DEBUG(self.DBG,"Failed to authenticate %s"%name,'error')
313