root/oss/headstock/headstock/example/simplechat/simplechat.py

Revision 674, 30.2 kB (checked in by sylvain, 7 months ago)

minor modification

Line 
1 # -*- coding: utf-8 -*-
2 """
3 This module is a simple XMPP chat client demonstrating the use of headstock.
4 Many Kamaelia components are created to manage different XMPP kind of stanzas.
5
6 * RosterHandler:
7   * querying the server for the roster list
8   * if supported by server, asking for the last activity of each contact
9
10 * DummyMessageHandler:
11   * sending a message typed into the console window
12   * printing to the console any received messages
13
14 * DiscoHandler:
15   * querying for the supported features by the server
16   * dispatching the result of the previous query to components interested in that event
17
18 * ActivityHandler:
19   * dispatching to the RosterHandler the fact the server supports the feature
20
21 * RegisterHandler:
22   * registring a new user using in-band registration if supported
23
24 The actual XMPP client is the Client component that sets up the different
25 dispatchers and handlers involved by liking each inbox to the expected outbox and
26 vcie versa.
27
28 """
29 from Axon.Component import component
30 from Kamaelia.Chassis.Graphline import Graphline
31 from Kamaelia.Chassis.Pipeline import Pipeline
32 from Kamaelia.Util.Backplane import Backplane
33 from Kamaelia.Util.Backplane import PublishTo, SubscribeTo
34 from Kamaelia.Internet.TCPClient import TCPClient
35 from Kamaelia.Util.Console import ConsoleReader
36 from Axon.Ipc import shutdownMicroprocess, producerFinished
37    
38 from headstock.protocol.core.stream import ClientStream, StreamError, SaslError
39 from headstock.protocol.core.presence import PresenceDispatcher
40 from headstock.protocol.core.roster import RosterDispatcher, RosterNull
41 from headstock.protocol.core.message import MessageDispatcher, MessageEchoer
42 from headstock.protocol.extension.register import RegisterDispatcher
43 from headstock.protocol.extension.activity import ActivityDispatcher
44 from headstock.protocol.extension.discovery import DiscoveryDispatcher
45 from headstock.protocol.extension.discovery import FeaturesDiscovery
46 from headstock.api.jid import JID
47 from headstock.api.im import Message, Body, Event
48 from headstock.api.contact import Presence, Roster, Item
49 from headstock.api import Entity
50 from headstock.api.activity import Activity
51 from headstock.api.registration import Registration
52 from headstock.lib.parser import XMLIncrParser
53 from headstock.lib.logger import Logger
54 from headstock.lib.utils import generate_unique
55
56 from bridge import Element as E
57 from bridge.common import XMPP_CLIENT_NS, XMPP_ROSTER_NS, \
58     XMPP_LAST_NS, XMPP_DISCO_INFO_NS, XMPP_IBR_NS
59
60 __all__ = ['Client']
61
62 class RosterHandler(component):   
63     Inboxes = {"inbox"        : "headstock.api.contact.Roster instance",
64                "control"      : "stops the component",
65                "pushed"       : "roster stanzas pushed by the server",
66                "jid"          : "headstock.api.jid.JID instance received from the server",
67                "ask-activity" : "request activity status to the server for each roster contact"}
68    
69     Outboxes = {"outbox"      : "UNUSED",
70                 "signal"      : "Shutdown signal",
71                 "message"     : "Message to send",
72                 "result"      : "",
73                 "activity"    : "headstock.api.activity.Activity instance to send to the server"}
74
75     def __init__(self, from_jid):
76         super(RosterHandler, self).__init__()
77         self.from_jid = from_jid
78         self.roster = None
79
80     def initComponents(self):
81         # We subscribe to the JID backplane component
82         # that will inform us when the server has
83         # returned the per-session jid
84         sub = SubscribeTo("JID")
85         self.link((sub, 'outbox'), (self, 'jid'))
86         self.addChildren(sub)
87         sub.activate()
88
89         return 1
90
91     def main(self):
92         yield self.initComponents()
93
94         while 1:
95             if self.dataReady("control"):
96                 mes = self.recv("control")
97                
98                 if isinstance(mes, shutdownMicroprocess) or isinstance(mes, producerFinished):
99                     self.send(producerFinished(), "signal")
100                     break
101
102             if self.dataReady("jid"):
103                 self.from_jid = self.recv('jid')
104            
105             if self.dataReady("pushed"):
106                 roster = self.recv('pushed')
107                 for nodeid in roster.items:
108                     self.send(Roster(from_jid=self.from_jid, to_jid=nodeid,
109                                      type=u'result', stanza_id=generate_unique()), 'result')
110                
111             if self.dataReady("inbox"):
112                 roster = self.recv("inbox")
113                 self.roster = roster
114                 print "Your contacts:"
115                 for nodeid in roster.items:
116                     contact = roster.items[nodeid]
117                     print "  ", contact.jid
118                    
119             if self.dataReady('ask-activity'):
120                 self.recv('ask-activity')
121                 if self.roster:
122                     for nodeid in self.roster.items:
123                         contact = roster.items[nodeid]
124                         a = Activity(unicode(self.from_jid), unicode(contact.jid))
125                         self.send(a, 'activity')
126
127             if not self.anyReady():
128                 self.pause()
129  
130             yield 1
131
132 class DummyMessageHandler(component):
133     Inboxes = {"inbox"    : "headstock.api.contact.Message instance received from a peer"\
134                    "or the string input in the console",
135                "jid"      : "headstock.api.jid.JID instance received from the server",
136                "control"  : "stops the component"}
137    
138     Outboxes = {"outbox"  : "headstock.api.im.Message to send to the client",
139                 "signal"  : "Shutdown signal"}
140
141     def __init__(self):
142         super(DummyMessageHandler, self).__init__()
143         self.from_jid = None
144
145     def initComponents(self):
146         sub = SubscribeTo("JID")
147         self.link((sub, 'outbox'), (self, 'jid'))
148         self.addChildren(sub)
149         sub.activate()
150
151         sub = SubscribeTo("CONSOLE")
152         self.link((sub, 'outbox'), (self, 'inbox'))
153         self.addChildren(sub)
154         sub.activate()
155
156         return 1
157
158     def main(self):
159         yield self.initComponents()
160
161         while 1:
162             if self.dataReady("control"):
163                 mes = self.recv("control")
164                 if isinstance(mes, shutdownMicroprocess) or isinstance(mes, producerFinished):
165                     self.send(producerFinished(), "signal")
166                     break
167
168             if self.dataReady("jid"):
169                 self.from_jid = self.recv('jid')
170            
171             if self.dataReady("inbox"):
172                 m = self.recv("inbox")
173                 # in this first case, we want to send the message
174                 # typed in the console.
175                 # The message is of the form:
176                 #    contant_jid message
177                 if isinstance(m, str) and m != '':
178                     try:
179                         contact_jid, message = m.split(' ', 1)
180                     except ValueError:
181                         print "Messages format: contact_jid message"
182                         continue
183                     m = Message(unicode(self.from_jid), unicode(contact_jid),
184                                 type=u'chat', stanza_id=generate_unique())
185                     m.event = Event.composing # note the composing event status
186                     m.bodies.append(Body(unicode(message)))
187                     self.send(m, "outbox")
188                    
189                     # Right after we sent the first message
190                     # we send another one reseting the event status
191                     m = Message(unicode(self.from_jid), unicode(contact_jid),
192                                 type=u'chat', stanza_id=generate_unique())
193                     self.send(m, "outbox")
194                 # In this case we actually received a message
195                 # from a contact, we print it.
196                 elif isinstance(m, Message):
197                     for body in m.bodies:
198                         print m.from_jid, ": ", str(body)
199
200             if not self.anyReady():
201                 self.pause()
202  
203             yield 1
204
205 class DiscoHandler(component):
206     Inboxes = {"inbox"          : "UNUSED",
207                "control"        : "stops the component",
208                "initiate"       : "event informing the component the client session is active",
209                "jid"            : "headstock.api.jid.JID instance received from the server",
210                "features.result": "headstock.api.discovery.FeaturesDiscovery instance from the server",}
211    
212     Outboxes = {"outbox"           : "UNUSED",
213                 "signal"           : "Shutdown signal",
214                 "features-disco"   : "headstock.api.discovery.FeaturesDiscovery query to the server"
215                 "features-announce": "headstock.api.discovery.FeaturesDiscovery informs"\
216                     "the other components about the features instance received from the server"}
217
218     def __init__(self, from_jid, to_jid):
219         super(DiscoHandler, self).__init__()
220         self.from_jid = from_jid
221         self.to_jid = to_jid
222
223     def initComponents(self):
224         sub = SubscribeTo("JID")
225         self.link((sub, 'outbox'), (self, 'jid'))
226         self.addChildren(sub)
227         sub.activate()
228
229         pub = PublishTo("DISCO_FEAT")
230         self.link((self, 'features-announce'), (pub, 'inbox'))
231         self.addChildren(pub)
232         pub.activate()
233
234         sub = SubscribeTo("BOUND")
235         self.link((sub, 'outbox'), (self, 'initiate'))
236         self.addChildren(sub)
237         sub.activate()
238
239         return 1
240
241     def main(self):
242         yield self.initComponents()
243
244         while 1:
245             if self.dataReady("control"):
246                 mes = self.recv("control")
247                
248                 if isinstance(mes, shutdownMicroprocess) or isinstance(mes, producerFinished):
249                     self.send(producerFinished(), "signal")
250                     break
251
252             if self.dataReady("jid"):
253                 self.from_jid = self.recv('jid')
254            
255             # When this box has some data, it means
256             # that the client is bound to the server
257             # Let's ask for its supported features then.
258             if self.dataReady("initiate"):
259                 self.recv("initiate")
260                 d = FeaturesDiscovery(unicode(self.from_jid), self.to_jid)
261                 self.send(d, "features-disco")
262
263             # The response to our discovery query
264             # is a a headstock.api.discovery.FeaturesDiscovery instance.
265             # What we immediatly do is to notify all handlers
266             # interested in that event about it.
267             if self.dataReady('features.result'):
268                 disco = self.recv('features.result')
269                 print "Supported features:"
270                 for feature in disco.features:
271                     print "  ", feature.var
272                 self.send(disco, 'features-announce')
273
274             if not self.anyReady():
275                 self.pause()
276  
277             yield 1
278
279 class ActivityHandler(component):
280     Inboxes = {"inbox"   : "headstock.api.discovery.FeaturesDiscovery instance",
281                "control" : "stops the component",
282                }
283    
284     Outboxes = {"outbox"            : "UNUSED",
285                 "signal"            : "Shutdown signal",
286                 "activity-supported": "when used this tells the RosterHandler it needs"\
287                     "to request the server for each contact's activity."\
288                     "This is only used when the server supports the feature",
289                 }
290
291     def __init__(self):
292         super(ActivityHandler, self).__init__()
293
294     def initComponents(self):
295         sub = SubscribeTo("DISCO_FEAT")
296         self.link((sub, 'outbox'), (self, 'inbox'))
297         self.addChildren(sub)
298         sub.activate()
299        
300         return 1
301
302     def main(self):
303         yield self.initComponents()
304
305         while 1:
306             if self.dataReady("control"):
307                 mes = self.recv("control")
308                 if isinstance(mes, shutdownMicroprocess) or isinstance(mes, producerFinished):
309                     self.send(producerFinished(), "signal")
310                     break
311
312             if self.dataReady("inbox"):
313                 disco = self.recv("inbox")
314                 support = disco.has_feature(XMPP_LAST_NS)
315                 print "Activity support: ", support
316                 if support:
317                     self.send('', "activity-supported")
318
319             if not self.anyReady():
320                 self.pause()
321  
322             yield 1
323
324 class PresenceHandler(component):
325     Inboxes = {"inbox"       : "headstock.api.contact.Presence instance",
326                "control"     : "Shutdown the client stream",
327                "subscribe"   : "",
328                "unsubscribe" : "",}
329    
330     Outboxes = {"outbox" : "headstock.api.contact.Presence instance to return to the server",
331                 "signal" : "Shutdown signal",
332                 "roster" : "",
333                 "log"    : "log",}
334    
335     def __init__(self):
336         super(PresenceHandler, self).__init__()
337
338     def main(self):
339         while 1:
340             if self.dataReady("control"):
341                 mes = self.recv("control")
342                
343                 if isinstance(mes, shutdownMicroprocess) or isinstance(mes, producerFinished):
344                     self.send(producerFinished(), "signal")
345                     break
346
347             if self.dataReady("subscribe"):
348                 p = self.recv("subscribe")
349                 p.swap_jids()
350
351                 # Automatically accept any subscription requests
352                 p = Presence(from_jid=p.from_jid, to_jid=unicode(p.to_jid),
353                              type=u'subscribed')
354                 self.send(p, "outbox")
355                
356                 # Automatically subscribe in return as well
357                 p = Presence(from_jid=p.from_jid, to_jid=unicode(p.to_jid),
358                              type=u'subscribe')
359                 self.send(p, "outbox")
360                
361             if self.dataReady("unsubscribe"):
362                 p = self.recv("unsubscribe")
363                 p.swap_jids()
364                
365                 # We stop our subscription to the other user
366                 p = Presence(from_jid=p.from_jid, to_jid=unicode(p.to_jid),
367                              type=u'unsubscribed')
368                 self.send(p, "outbox")
369                
370                 # We stop the other user's subscription
371                 p = Presence(from_jid=p.from_jid, to_jid=unicode(p.to_jid),
372                              type=u'unsubscribe')
373                 self.send(p, "outbox")
374
375                 # We remove this user from our roster list
376                 r = Roster(from_jid=p.from_jid, type=u'set')
377                 i = Item(p.to_jid)
378                 i.subscription = u'remove'
379                 r.items[unicode(p.to_jid)] = i
380                 self.send(r, 'roster')
381
382                 # We tell the other user we're not available anymore
383                 p = Presence(from_jid=p.from_jid, to_jid=unicode(p.to_jid),
384                              type=u'unavailable')
385                 self.send(p, "outbox")
386                
387             if not self.anyReady():
388                 self.pause()
389  
390             yield 1
391    
392
393 class RegistrationHandler(component):
394     Inboxes = {"inbox"   : "headstock.api.registration.Registration",
395                "error"   : "headstock.api.registration.Registration",
396                "control" : "Shutdown the client stream",}
397    
398     Outboxes = {"outbox" : "headstock.api.registration.Registration",
399                 "signal" : "Shutdown signal",
400                 "log"    : "log",}
401    
402     def __init__(self, username, password):
403         super(RegistrationHandler, self).__init__()
404         self.username = username
405         self.password = password
406         self.registration_id = None
407
408     def main(self):
409         while 1:
410             if self.dataReady("control"):
411                 mes = self.recv("control")
412                
413                 if isinstance(mes, shutdownMicroprocess) or isinstance(mes, producerFinished):
414                     self.send(producerFinished(), "signal")
415                     break
416
417             if self.dataReady("inbox"):
418                 r = self.recv('inbox')
419                 if r.registered:
420                     print "'%s' is already a registered username." % self.username
421                 elif self.registration_id == r.stanza_id:
422                     print "'%s' is now a registered user."\
423                         "Please restart the client without the register flag." % self.username
424                 else:
425                     if 'username' in r.infos and 'password' in r.infos:
426                         self.registration_id = generate_unique()
427                         r = Registration(type=u'set', stanza_id=self.registration_id)
428                         r.infos[u'username'] = self.username
429                         r.infos[u'password'] = self.password
430                         self.send(r, 'outbox')
431                
432             if self.dataReady("error"):
433                 r = self.recv('error')
434                 print r.error
435
436             if not self.anyReady():
437                 self.pause()
438  
439             yield 1
440
441 class Client(component):
442     Inboxes = {"inbox"      : "",
443                "jid"        : "",
444                "streamfeat" : "",
445                "control"    : "Shutdown the client stream"}
446    
447     Outboxes = {"outbox"  : "",
448                 "forward" : "",
449                 "log"     : "",
450                 "doauth"  : "",
451                 "signal"  : "Shutdown signal",
452                 "doregistration" : ""}
453
454     def __init__(self, username, password, domain, resource=u"headstock-client1",
455                  server=u'localhost', port=5222, usetls=False, register=False):
456         super(Client, self).__init__()
457         self.jid = JID(username, domain, resource)
458         self.username = username
459         self.password = password
460         self.server = server
461         self.port = port
462         self.client = None
463         self.graph = None
464         self.domain = domain
465         self.usetls = usetls
466         self.register = register
467
468     def passwordLookup(self, jid):
469         return self.password
470
471     def shutdown(self):
472         self.send(Presence.to_element(Presence(self.jid, type=u'unavailable')), 'forward')
473         self.send('OUTGOING : </stream:stream>', 'log')
474         self.send('</stream:stream>', 'outbox')
475
476     def abort(self):
477         self.send('OUTGOING : </stream:stream>', 'log')
478         self.send('</stream:stream>', 'outbox')
479
480     def setup(self):
481         # Backplanes are like a global entry points that
482         # can be accessible both for publishing and
483         # recieving data.
484         # In other words, a component interested
485         # in advertising to many other components that
486         # something happened may link one of its outbox
487         # to a PublishTo component's inbox.
488         # A component wishing to receive that piece of
489         # information will link one of its inbox
490         # to the SubscribeTo component's outbox.
491         # This helps greatly to make components more
492         # loosely connected but also allows for some data
493         # to be dispatched at once to many (such as when
494         # the server returns the per-session JID that
495         # is of interest for most other components).
496         Backplane("CONSOLE").activate()
497         Backplane("JID").activate()
498         # Used to inform components that the session is now active
499         Backplane("BOUND").activate()
500         # Used to inform components of the supported features
501         Backplane("DISCO_FEAT").activate()
502
503         sub = SubscribeTo("JID")
504         self.link((sub, 'outbox'), (self, 'jid'))
505         self.addChildren(sub)
506         sub.activate()
507
508         # We pipe everything typed into the console
509         # directly to the console backplane so that
510         # every components subscribed to the console
511         # backplane inbox will get the typed data and
512         # will decide it it's of concern or not.
513         Pipeline(ConsoleReader(), PublishTo('CONSOLE')).activate()
514
515         # Add two outboxes ro the ClientSteam to support specific extensions.
516         ClientStream.Outboxes["%s.query" % XMPP_IBR_NS] = "Registration"
517         ClientStream.Outboxes["%s.query" % XMPP_LAST_NS] = "Activity"
518         ClientStream.Outboxes["%s.query" % XMPP_DISCO_INFO_NS] = "Discovery"
519
520         self.client = ClientStream(self.jid, self.passwordLookup, use_tls=self.usetls)
521        
522         self.graph = Graphline(client = self,
523                                console = SubscribeTo('CONSOLE'),
524                                logger = Logger(path=None, stdout=True),
525                                tcp = TCPClient(self.server, self.port),
526                                xmlparser = XMLIncrParser(),
527                                xmpp = self.client,
528                                streamerr = StreamError(),
529                                saslerr = SaslError(),
530                                discohandler = DiscoHandler(self.jid, self.domain),
531                                activityhandler = ActivityHandler(),
532                                rosterhandler = RosterHandler(self.jid),
533                                registerhandler = RegistrationHandler(self.username, self.password),
534                                msgdummyhandler = DummyMessageHandler(),
535                                presencehandler = PresenceHandler(),
536                                presencedisp = PresenceDispatcher(),
537                                rosterdisp = RosterDispatcher(),
538                                msgdisp = MessageDispatcher(),
539                                discodisp = DiscoveryDispatcher(),
540                                activitydisp = ActivityDispatcher(),
541                                registerdisp = RegisterDispatcher(),
542                                pjid = PublishTo("JID"),
543                                pbound = PublishTo("BOUND"),
544
545                                linkages = {('xmpp', 'terminated'): ('client', 'inbox'),
546                                            ('console', 'outbox'): ('client', 'control'),
547                                            ('client', 'forward'): ('xmpp', 'forward'),
548                                            ('client', 'outbox'): ('tcp', 'inbox'),
549                                            ('client', 'signal'): ('tcp', 'control'),
550                                            ("tcp", "outbox") : ("xmlparser", "inbox"),
551                                            ("xmpp", "starttls") : ("tcp", "makessl"),
552                                            ("tcp", "sslready") : ("xmpp", "tlssuccess"),
553                                            ("xmlparser", "outbox") : ("xmpp" , "inbox"),
554                                            ("xmpp", "outbox") : ("tcp" , "inbox"),
555                                            ("xmpp", "reset"): ("xmlparser", "reset"),
556                                            ("client", "log"): ("logger", "inbox"),
557                                            ("xmpp", "log"): ("logger", "inbox"),
558                                            ("xmpp", "jid"): ("pjid", "inbox"),
559                                            ("xmpp", "bound"): ("pbound", "inbox"),
560                                            ("xmpp", "features"): ("client", "streamfeat"),
561                                            ("client", "doauth"): ("xmpp", "auth"),
562                                            
563                                            # Registration
564                                            ("xmpp", "%s.query" % XMPP_IBR_NS): ("registerdisp", "inbox"),
565                                            ("registerdisp", "log"): ('logger', "inbox"),
566                                            ("registerdisp", "xmpp.error"): ("registerhandler", "error"),
567                                            ("registerdisp", "xmpp.result"): ("registerhandler", "inbox"),
568                                            ("registerhandler", "outbox"): ("registerdisp", "forward"),
569                                            ("client", "doregistration"): ("registerdisp", "forward"),
570                                            ("registerdisp", "outbox"): ("xmpp", "forward"),
571                                            
572                                            # Presence
573                                            ("xmpp", "%s.presence" % XMPP_CLIENT_NS): ("presencedisp", "inbox"),
574                                            ("presencedisp", "log"): ('logger', "inbox"),
575                                            ("presencedisp", "xmpp.subscribe"): ("presencehandler", "subscribe"),
576                                            ("presencedisp", "xmpp.unsubscribe"): ("presencehandler", "unsubscribe"),
577                                            ("presencehandler", "outbox"): ("presencedisp", "forward"),
578                                            ("presencehandler", "roster"): ("rosterdisp", "forward"),
579                                            ("presencedisp", "outbox"): ("xmpp", "forward"),
580
581                                            # Roster
582                                            ("xmpp", "%s.query" % XMPP_ROSTER_NS): ("rosterdisp", "inbox"),
583                                            ("rosterdisp", "log"): ('logger', "inbox"),
584                                            ('rosterdisp', 'xmpp.set'): ('rosterhandler', 'pushed'),
585                                            ('rosterdisp', 'xmpp.result'): ('rosterhandler', 'inbox'),
586                                            ('rosterhandler', 'result'): ('rosterdisp', 'forward'),
587                                            ("rosterdisp", "outbox"): ("xmpp", "forward"),
588
589                                            # Discovery
590                                            ("xmpp", "%s.query" % XMPP_DISCO_INFO_NS): ("discodisp", "features.inbox"),
591                                            ("discodisp", "log"): ('logger', "inbox"),
592                                            ("discohandler", "features-disco"): ('discodisp', "features.forward"),
593                                            ("discodisp", "out.features.result"): ('discohandler', "features.result"),
594                                            ("discodisp", "outbox"): ("xmpp", "forward"),
595
596                                            # Message
597                                            ("xmpp", "%s.message" % XMPP_CLIENT_NS): ("msgdisp", "inbox"),
598                                            ("msgdisp", "log"): ('logger', "inbox"),
599                                            ("msgdisp", "xmpp.chat"): ('msgdummyhandler', 'inbox'),
600                                            ("msgdummyhandler", "outbox"): ('msgdisp', 'forward'),
601                                            ("msgdisp", "outbox"): ("xmpp", "forward"),
602
603                                            # Activity
604                                            ("xmpp", "%s.query" % XMPP_LAST_NS): ("activitydisp", "inbox"),
605                                            ("activitydisp", "log"): ('logger', "inbox"),
606                                            ("activitydisp", "outbox"): ("xmpp", "forward"),
607                                            ("activityhandler", 'activity-supported'): ('rosterhandler', 'ask-activity'),
608                                            ("rosterhandler", 'activity'): ('activitydisp', 'forward'),
609                                            }
610                                )
611         self.addChildren(self.graph)
612         self.graph.activate()
613
614         return 1
615
616     def main(self):
617         yield self.setup()
618
619         while 1:
620             if self.dataReady("control"):
621                 mes = self.recv("control")
622
623                 if isinstance(mes, str):
624                     if mes.strip() == 'quit':
625                         self.shutdown()
626                 elif isinstance(mes, shutdownMicroprocess) or isinstance(mes, producerFinished):
627                     self.send(mes, "signal")
628                     break
629
630             if self.dataReady("inbox"):
631                 msg = self.recv('inbox')
632                 if msg == "quit":
633                     self.send(shutdownMicroprocess(), "signal")
634                     yield 1
635                     break
636
637             if self.dataReady("streamfeat"):
638                 feat = self.recv('streamfeat')
639                 if feat.register and self.register:
640                     self.send(Registration(), 'doregistration')
641                 elif self.register and not feat.register:
642                     print "The server does not support in-band registration. Closing connection."
64