1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
54 """ Common xmpppy plugins infrastructure: plugging in/out, debugging. """
56 self._exported_methods=[]
57 self.DBG_LINE=self.__class__.__name__.lower()
58
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
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
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
118 """ Register handler that will be called on disconnect."""
119 self.disconnect_handlers.append(handler)
120
122 """ Unregister handler that is called on disconnect."""
123 self.disconnect_handlers.remove(handler)
124
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
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
145 """ Returns connection state. F.e.: None / 'tls' / 'tcp+non_sasl' . """
146 return self.connected
147
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:
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
186 return self.connected
187
189 """ Example client class, based on CommonClient. """
190
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
204 if not self.Dispatcher.Stream.features.getTag('starttls'): return self.connected
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
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
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
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
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
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):
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