root/branches/gajim_0.2-2/common/jabber.py

Revision 82, 50.8 kB (checked in by asterix, 5 years ago)

Jabberpy V0.5

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
Line 
1##   jabber.py
2##
3##   Copyright (C) 2001 Matthew Allum
4##
5##   This program is free software; you can redistribute it and/or modify
6##   it under the terms of the GNU Lesser General Public License as published
7##   by 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 Lesser General Public License for more details.
14##
15
16
17"""\
18
19__intro__
20
21jabber.py is a Python module for the jabber instant messaging protocol.
22jabber.py deals with the xml parsing and socket code, leaving the programmer
23to concentrate on developing quality jabber based applications with Python.
24
25The eventual aim is to produce a fully featured easy to use library for
26creating both jabber clients and servers.
27
28jabber.py requires at least python 2.0 and the XML expat parser module
29( included in the standard Python distrubution ).
30
31It is developed on Linux but should run happily on over Unix's and win32.
32
33__Usage__
34
35jabber.py basically subclasses the xmlstream classs and provides the
36processing of jabber protocol elements into object instances as well
37'helper' functions for parts of the protocol such as authentication
38and roster management.
39
40An example of usage for a simple client would be ( only psuedo code !)
41
42<> Read documentation on jabber.org for the jabber protocol.
43
44<> Birth a jabber.Client object with your jabber servers host
45
46<> Define callback functions for the protocol elements you want to use
47   and optionally a disconnection.
48
49<> Authenticate with the server via auth method, or register via the
50   reg methods to get an account.
51
52<> Call requestRoster() and sendPresence()
53
54<> loop over process(). Send Iqs,messages and presences by birthing
55   them via there respective clients , manipulating them and using
56   the Client's send() method.
57
58<> Respond to incoming elements passed to your callback functions.
59
60<> Find bugs :)
61
62
63"""
64
65# $Id$
66
67import xmlstream
68import sha, time
69
70debug=xmlstream.debug
71
72VERSION = xmlstream.VERSION
73
74False = 0;
75True  = 1;
76
77timeout = 300
78
79DBG_INIT, DBG_ALWAYS = debug.DBG_INIT, debug.DBG_ALWAYS
80DBG_DISPATCH = 'jb-dispatch'            ; debug.debug_flags.append( DBG_DISPATCH )
81DBG_NODE = 'jb-node'                    ; debug.debug_flags.append( DBG_NODE)
82DBG_NODE_IQ = 'jb-node-iq'              ; debug.debug_flags.append( DBG_NODE_IQ )
83DBG_NODE_MESSAGE = 'jb-node-message'    ; debug.debug_flags.append( DBG_NODE_MESSAGE )
84DBG_NODE_PRESENCE = 'jb-node-pressence' ; debug.debug_flags.append( DBG_NODE_PRESENCE )
85DBG_NODE_UNKNOWN = 'jb-node-unknown'    ; debug.debug_flags.append( DBG_NODE_UNKNOWN )
86
87
88#
89# JANA core namespaces
90#  from http://www.jabber.org/jana/namespaces.php as of 2003-01-12
91#  "myname" means that namespace didnt have a name in the jabberd headers
92#
93NS_AGENT      = "jabber:iq:agent"
94NS_AGENTS     = "jabber:iq:agents"
95NS_AUTH       = "jabber:iq:auth"
96NS_CLIENT     = "jabber:client"
97NS_DELAY      = "jabber:x:delay"
98NS_OOB        = "jabber:iq:oob"
99NS_REGISTER   = "jabber:iq:register"
100NS_ROSTER     = "jabber:iq:roster"
101NS_XROSTER    = "jabber:x:roster" # myname
102NS_SERVER     = "jabber:server"
103NS_TIME       = "jabber:iq:time"
104NS_VERSION    = "jabber:iq:version"
105
106NS_COMP_ACCEPT  = "jabber:component:accept" # myname
107NS_COMP_CONNECT = "jabber:component:connect" # myname
108
109
110
111#
112# JANA JEP namespaces, ordered by JEP
113#  from http://www.jabber.org/jana/namespaces.php as of 2003-01-12
114#  all names by jaclu
115#
116_NS_PROTOCOL  = "http://jabber.org/protocol" # base for other
117NS_PASS       = "jabber:iq:pass" # JEP-0003
118NS_XDATA      = "jabber:x:data" # JEP-0004
119NS_RPC        = "jabber:iq:rpc" # JEP-0009
120NS_BROWSE     = "jabber:iq:browse" # JEP-0011
121NS_LAST       = "jabber:iq:last" #JEP-0012
122NS_PRIVACY    = "jabber:iq:privacy" # JEP-0016
123NS_XEVENT     = "jabber:x:event" # JEP-0022
124NS_XEXPIRE    = "jabber:x:expire" # JEP-0023
125NS_XENCRYPTED = "jabber:x:encrypted" # JEP-0027
126NS_XSIGNED    = "jabber:x:signed" # JEP-0027
127NS_P_MUC      = _NS_PROTOCOL + "/muc" # JEP-0045
128NS_VCARD      = "vcard-temp" # JEP-0054
129
130
131#
132# Non JANA aproved, ordered by JEP
133#  all names by jaclu
134#
135_NS_P_DISCO     = _NS_PROTOCOL + "/disco" # base for other
136NS_P_DISC_INFO  = _NS_P_DISCO + "#info" # JEP-0030
137NS_P_DISC_ITEMS = _NS_P_DISCO + "#items" # JEP-0030
138NS_P_COMMANDS   = _NS_PROTOCOL + "/commands" # JEP-0050
139
140
141"""
142 2002-01-11 jaclu
143
144 Defined in jabberd/lib/lib.h, but not JANA aproved and not used in jabber.py
145 so commented out, should/could propably be removed...
146
147 NS_ADMIN      = "jabber:iq:admin"
148 NS_AUTH_OK    = "jabber:iq:auth:0k"
149 NS_CONFERENCE = "jabber:iq:conference"
150 NS_ENVELOPE   = "jabber:x:envelope"
151 NS_FILTER     = "jabber:iq:filter"
152 NS_GATEWAY    = "jabber:iq:gateway"
153 NS_OFFLINE    = "jabber:x:offline"
154 NS_PRIVATE    = "jabber:iq:private"
155 NS_SEARCH     = "jabber:iq:search"
156 NS_XDBGINSERT = "jabber:xdb:ginsert"
157 NS_XDBNSLIST  = "jabber:xdb:nslist"
158 NS_XHTML      = "http://www.w3.org/1999/xhtml"
159 NS_XOOB       = "jabber:x:oob"
160 NS_COMP_EXECUTE = "jabber:component:execute" # myname
161"""
162
163
164## Possible constants for Roster class .... hmmm ##
165RS_SUB_BOTH    = 0
166RS_SUB_FROM    = 1
167RS_SUB_TO      = 2
168
169RS_ASK_SUBSCRIBE   = 1
170RS_ASK_UNSUBSCRIBE = 0
171
172RS_EXT_ONLINE   = 2
173RS_EXT_OFFLINE  = 1
174RS_EXT_PENDING  = 0
175
176#############################################################################
177
178def ustr(what):
179    """If sending object is already a unicode str, just
180       return it, otherwise convert it using xmlstream.ENCODING"""
181    if type(what) == type(u''):
182        r = what
183    else:
184        try: r = what.__str__()
185        except AttributeError: r = str(what)
186        # make sure __str__() didnt return a unicode
187        if type(r) <> type(u''):
188            r = unicode(r,xmlstream.ENCODING,'replace')
189    return r
190xmlstream.ustr = ustr
191
192class NodeProcessed(Exception): pass   # currently only for Connection._expectedIqHandler
193
194class Connection(xmlstream.Client):
195    """Forms the base for both Client and Component Classes"""
196    def __init__(self, host, port, namespace,
197                 debug=[], log=False, connection=xmlstream.TCP, hostIP=None, proxy=None):
198
199        xmlstream.Client.__init__(self, host, port, namespace,
200                                  debug=debug, log=log,
201                                  connection=connection,
202                                  hostIP=hostIP, proxy=proxy)
203        self.handlers={}
204        self.registerProtocol('unknown', Protocol)
205        self.registerProtocol('iq', Iq)
206        self.registerProtocol('message', Message)
207        self.registerProtocol('presence', Presence)
208
209        self.registerHandler('iq',self._expectedIqHandler,system=True)
210
211        self._expected = {}
212
213        self._id = 0;
214
215        self.lastErr = ''
216        self.lastErrCode = 0
217
218    def setMessageHandler(self, func, type='', chainOutput=False):
219        """Back compartibility method"""
220        print "WARNING! setMessageHandler(...) method is obsolette, use registerHandler('message',...) instead."
221        return self.registerHandler('message', func, type, chained=chainOutput)
222
223    def setPresenceHandler(self, func, type='', chainOutput=False):
224        """Back compartibility method"""
225        print "WARNING! setPresenceHandler(...) method is obsolette, use registerHandler('presence',...) instead."
226        return self.registerHandler('presence', func, type, chained=chainOutput)
227
228    def setIqHandler(self, func, type='', ns=''):
229        """Back compartibility method"""
230        print "WARNING! setIqHandler(...) method is obsolette, use registerHandler('iq',...) instead."
231        return self.registerHandler('iq', func, type, ns)
232
233    def header(self):
234        self.DEBUG("stream: sending initial header",DBG_INIT)
235        str = u"<?xml version='1.0' encoding='UTF-8' ?>   \
236                <stream:stream to='%s' xmlns='%s'" % ( self._host,
237                                                       self._namespace )
238
239        if self._outgoingID: str = str + " id='%s' " % self._outgoingID
240        str = str + " xmlns:stream='http://etherx.jabber.org/streams'>"
241        self.send(str)
242        self.process(timeout)
243
244    def send(self, what):
245        """Sends a jabber protocol element (Node) to the server"""
246        xmlstream.Client.write(self,ustr(what))
247
248    def _expectedIqHandler(self, conn, iq_obj):
249        if iq_obj.getAttr('id') and \
250           self._expected.has_key(iq_obj.getAttr('id')):
251            self._expected[iq_obj.getAttr('id')] = iq_obj
252            raise NodeProcessed('No need for further Iq processing.')
253
254    def dispatch(self,stanza):
255        """Called internally when a 'protocol element' is received.
256           Builds the relevant jabber.py object and dispatches it
257           to a relevant function or callback."""
258        name=stanza.getName()
259        if not self.handlers.has_key(name):
260            self.DEBUG("whats a tag -> " + name,DBG_NODE_UNKNOWN)
261            name='unknown'
262        else:
263            self.DEBUG("Got %s stanza"%name, DBG_NODE)
264
265        stanza=self.handlers[name][type](node=stanza)
266
267        typ=stanza.getType()
268        if not typ: typ=''
269        try:
270            ns=stanza.getQuery()
271            if not ns: ns=''
272        except: ns=''
273        self.DEBUG("dispatch called for: name->%s ns->%s"%(name,ns),DBG_DISPATCH)
274
275        typns=typ+ns
276        if not self.handlers[name].has_key(ns): ns=''
277        if not self.handlers[name].has_key(typ): typ=''
278        if not self.handlers[name].has_key(typns): typns=''
279        if typ==typns: typns=''
280
281        chain=[]
282        for key in ['default',typ,ns,typns]: # we will use all handlers: from very common to very particular
283            if key: chain += self.handlers[name][key]
284
285        output=''
286        user=True
287        for handler in chain:
288            try:
289                if user or handler['system']:
290                    if handler['chain']: output=handler['func'](self,stanza,output)
291                    else: handler['func'](self,stanza)
292            except NodeProcessed: user=False
293
294    def registerProtocol(self,tag_name,Proto):
295        """Registers a protocol in protocol processing chain. You MUST register
296           a protocol before you register any handler function for it.
297           First parameter, that passed to this function is the tag name that
298           belongs to all protocol elements. F.e.: message, presence, iq, xdb, ...
299           Second parameter is the [ancestor of] Protocol class, which instance will
300           built from the received node with call
301
302                if received_packet.getName()==tag_name:
303                    stanza = Proto(node = received_packet)
304        """
305        self.handlers[tag_name]={type:Proto, 'default':[]}
306
307    def registerHandler(self,name,handler,type='',ns='',chained=False, makefirst=False, system=False):
308        """Sets the callback func for processing incoming stanzas.
309           Multiple callback functions can be set which are called in
310           succession. Callback can optionally raise an NodeProcessed error to
311           stop stanza from further processing. A type and namespace attributes can
312           also be optionally passed so the callback is only called when a stanza of
313           this type is received. Namespace attribute MUST be omitted if you
314           registering an Iq processing handler.
315
316           If 'chainOutput' is set to False (the default), the given function
317           should be defined as follows:
318
319                def myCallback(c, p)
320
321           Where the first parameter is the Client object, and the second
322           parameter is the [ancestor of] Protocol object representing the stanza
323           which was received.
324
325           If 'chainOutput' is set to True, the output from the various
326           handler functions will be chained together.  In this case,
327           the given callback function should be defined like this:
328
329                def myCallback(c, p, output)
330
331           Where 'output' is the value returned by the previous
332           callback function.  For the first callback routine, 'output' will be
333           set to an empty string.
334
335           'makefirst' argument gives you control over handler prioriy in its type
336           and namespace scope. Note that handlers for particular type or namespace always
337           have lower priority that common handlers.
338        """
339        if not type and not ns: type='default'
340        if not self.handlers[name].has_key(type+ns): self.handlers[name][type+ns]=[]
341        if makefirst: self.handlers[name][type+ns].insert({'chain':chained,'func':handler,'system':system})
342        else: self.handlers[name][type+ns].append({'chain':chained,'func':handler,'system':system})
343
344    def setDisconnectHandler(self, func):
345        """Set the callback for a disconnect.
346           The given function will be called with a single parameter (the
347           connection object) when the connection is broken unexpectedly (eg,
348           in response to sending badly formed XML).  self.lastErr and
349           self.lastErrCode will be set to the error which caused the
350           disconnection, if any.
351        """
352        self.disconnectHandler = func
353
354    ## functions for sending element with ID's ##
355
356    def waitForResponse(self, ID, timeout=timeout):
357        """Blocks untils a protocol element with the given id is received.
358           If an error is received, waitForResponse returns None and
359           self.lastErr and self.lastErrCode is set to the received error.  If
360           the operation times out (which only happens if a timeout value is
361           given), waitForResponse will return None and self.lastErr will be
362           set to "Timeout".
363           Changed default from timeout=0 to timeout=300 to avoid hangs in
364           scripts and such.
365           If you _really_ want no timeout, just set it to 0"""
366        ID = ustr(ID)
367        self._expected[ID] = None
368        has_timed_out = False
369
370        abort_time = time.time() + timeout
371        if timeout:
372            self.DEBUG("waiting with timeout:%s for %s" % (timeout,ustr(ID)),DBG_NODE_IQ)
373        else:
374            self.DEBUG("waiting for %s" % ustr(ID),DBG_NODE_IQ)
375
376        while (not self._expected[ID]) and not has_timed_out:
377            if not self.process(0.2): return None
378            if timeout and (time.time() > abort_time):
379                has_timed_out = True
380        if has_timed_out:
381            self.lastErr = "Timeout"
382            return None
383        response = self._expected[ID]
384        del self._expected[ID]
385        if response.getErrorCode():
386            self.lastErr     = response.getError()
387            self.lastErrCode = response.getErrorCode()
388            return None
389        return response
390
391    def SendAndWaitForResponse(self, obj, ID=None, timeout=timeout):
392        """Sends a protocol element object and blocks until a response with
393           the same ID is received.  The received protocol object is returned
394           as the function result. """
395        if ID is None :
396            ID = obj.getID()
397            if ID is None:
398                ID = self.getAnID()
399                obj.setID(ID)
400        ID = ustr(ID)
401        self.send(obj)
402        return self.waitForResponse(ID,timeout)
403
404    def getAnID(self):
405        """Returns a unique ID"""
406        self._id = self._id + 1
407        return ustr(self._id)
408
409#############################################################################
410
411class Client(Connection):
412    """Class for managing a client connection to a jabber server."""
413    def __init__(self, host, port=5222, debug=[], log=False,
414                 connection=xmlstream.TCP, hostIP=None, proxy=None):
415
416        Connection.__init__(self, host, port, NS_CLIENT, debug, log,
417                            connection=connection, hostIP=hostIP, proxy=proxy)
418
419        self.registerHandler('iq',self._IqRosterManage,'result',NS_ROSTER,system=True)
420        self.registerHandler('iq',self._IqRosterManage,'set',NS_ROSTER,system=True)
421        self.registerHandler('iq',self._IqRegisterResult,'result',NS_REGISTER,system=True)
422        self.registerHandler('iq',self._IqAgentsResult,'result',NS_AGENTS,system=True)
423        self.registerHandler('presence',self._presenceHandler,system=True)
424
425        self._roster = Roster()
426        self._agents = {}
427        self._reg_info = {}
428        self._reg_agent = ''
429
430    def disconnect(self):
431        """Safely disconnects from the connected server"""
432        self.send(Presence(type='unavailable'))
433        xmlstream.Client.disconnect(self)
434
435    def sendPresence(self,type=None,priority=None,show=None,status=None):
436        """Sends a presence protocol element to the server.
437           Used to inform the server that you are online"""
438        self.send(Presence(type=type,priority=priority,show=show,status=status))
439
440    sendInitPresence=sendPresence
441
442    def _presenceHandler(self, conn, pres_obj):
443        who = ustr(pres_obj.getFrom())
444        type = pres_obj.getType()
445        self.DEBUG("presence type is %s" % type,DBG_NODE_PRESENCE)
446        if type == 'available' or not type:
447            self.DEBUG("roster setting %s to online" % who,DBG_NODE_PRESENCE)
448            self._roster._setOnline(who,'online')
449        elif type == 'unavailable':
450            self.DEBUG("roster setting %s to offline" % who,DBG_NODE_PRESENCE)
451            self._roster._setOnline(who,'offline')
452        self._roster._setShow(who,pres_obj.getShow())
453        self._roster._setStatus(who,pres_obj.getStatus())
454
455    def _IqRosterManage(self, conn, iq_obj):
456        "NS_ROSTER and type in [result,set]"
457        for item in iq_obj.getQueryNode().getChildren():
458            jid  = item.getAttr('jid')
459            name = item.getAttr('name')
460            sub  = item.getAttr('subscription')
461            ask  = item.getAttr('ask')
462
463            groups = []
464            for group in item.getTags("group"):
465                groups.append(group.getData())
466
467            if jid:
468                if sub == 'remove' or sub == 'none':
469                    self._roster._remove(jid)
470                else:
471                    self._roster._set(jid=jid, name=name,
472                                      groups=groups, sub=sub,
473                                      ask=ask)
474            else:
475                self.DEBUG("roster - jid not defined ?",DBG_NODE_IQ)
476
477    def _IqRegisterResult(self, conn, iq_obj):
478        "NS_REGISTER and type==result"
479        self._reg_info = {}
480        for item in iq_obj.getQueryNode().getChildren():
481            self._reg_info[item.getName()] = item.getData()
482
483    def _IqAgentsResult(self, conn, iq_obj):
484        "NS_AGENTS and type==result"
485        self.DEBUG("got agents result",DBG_NODE_IQ)
486        self._agents = {}
487        for agent in iq_obj.getQueryNode().getChildren():
488            if agent.getName() == 'agent': ## hmmm
489                self._agents[agent.getAttr('jid')] = {}
490                for info in agent.getChildren():
491                    self._agents[agent.getAttr('jid')][info.getName()] = info.getData()
492
493    def auth(self,username,passwd,resource):
494        """Authenticates and logs in to the specified jabber server
495           Automatically selects the 'best' authentication method
496           provided by the server.
497           Supports plain text, digest and zero-k authentication.
498
499           Returns True if the login was successful, False otherwise.
500        """
501        auth_get_iq = Iq(type='get')
502        auth_get_iq.setID('auth-get')
503        q = auth_get_iq.setQuery(NS_AUTH)
504        q.insertTag('username').insertData(username)
505        self.send(auth_get_iq)
506
507        auth_response = self.waitForResponse("auth-get")
508        if auth_response == None:
509            return False # Error
510        else:
511            auth_ret_node = auth_response
512
513        auth_ret_query = auth_ret_node.getTag('query')
514        self.DEBUG("auth-get node arrived!",(DBG_INIT,DBG_NODE_IQ))
515
516        auth_set_iq = Iq(type='set')
517        auth_set_iq.setID('auth-set')
518
519        q = auth_set_iq.setQuery(NS_AUTH)
520        q.insertTag('username').insertData(username)
521        q.insertTag('resource').insertData(resource)
522
523        if auth_ret_query.getTag('token'):
524
525            token = auth_ret_query.getTag('token').getData()
526            seq = auth_ret_query.getTag('sequence').getData()
527            self.DEBUG("zero-k authentication supported",(DBG_INIT,DBG_NODE_IQ))
528            hash = sha.new(sha.new(passwd).hexdigest()+token).hexdigest()
529            for foo in xrange(int(seq)): hash = sha.new(hash).hexdigest()
530            q.insertTag('hash').insertData(hash)
531
532        elif auth_ret_query.getTag('digest'):
533
534            self.DEBUG("digest authentication supported",(DBG_INIT,DBG_NODE_IQ))
535            digest = q.insertTag('digest')
536            digest.insertData(sha.new(
537                self.getIncomingID() + passwd).hexdigest() )
538        else:
539            self.DEBUG("plain text authentication supported",(DBG_INIT,DBG_NODE_IQ))
540            q.insertTag('password').insertData(passwd)
541
542        iq_result = self.SendAndWaitForResponse(auth_set_iq)
543
544        if iq_result==None:
545             return False
546        if iq_result.getError() is None:
547            return True
548        else:
549           self.lastErr     = iq_result.getError()
550           self.lastErrCode = iq_result.getErrorCode()
551           # raise error(iq_result.getError()) ?
552           return False
553        return True
554
555    ## Roster 'helper' func's - also see the Roster class ##
556
557    def requestRoster(self):
558        """Requests the roster from the server and returns a
559           Roster() class instance."""
560        rost_iq = Iq(type='get')
561        rost_iq.setQuery(NS_ROSTER)
562        self.SendAndWaitForResponse(rost_iq)
563        self.DEBUG("got roster response",DBG_NODE_IQ)
564        self.DEBUG("roster -> %s" % ustr(self._roster),DBG_NODE_IQ)
565        return self._roster
566
567
568    def getRoster(self):
569        """Returns the current Roster() class instance. Does
570           not contact the server."""
571        return self._roster
572
573
574    def addRosterItem(self, jid):
575        """ Send off a request to subscribe to the given jid.
576        """
577        self.send(Presence(to=jid, type="subscribe"))
578
579
580    def updateRosterItem(self, jid, name=None, groups=None):
581        """ Update the information stored in the roster about a roster item.
582
583            'jid' is the Jabber ID of the roster entry; 'name' is the value to
584            set the entry's name to, and 'groups' is a list of groups to which
585            this roster entry can belong.  If either 'name' or 'groups' is not
586            specified, that value is not updated in the roster.
587        """
588        iq = Iq(type='set')
589        item = iq.setQuery(NS_ROSTER).insertTag('item')
590        item.putAttr('jid', ustr(jid))
591        if name != None: item.putAttr('name', name)
592        if groups != None:
593            for group in groups:
594                item.insertTag('group').insertData(group)
595        dummy = self.SendAndWaitForResponse(iq) # Do we need to wait??
596
597
598    def removeRosterItem(self,jid):
599        """Removes an item with Jabber ID jid from both the
600           server's roster and the local internal Roster()
601           instance"""
602        rost_iq = Iq(type='set')
603        q = rost_iq.setQuery(NS_ROSTER).insertTag('item')
604        q.putAttr('jid', ustr(jid))
605        q.putAttr('subscription', 'remove')
606        self.SendAndWaitForResponse(rost_iq)
607        return self._roster
608
609    ## Registration 'helper' funcs ##
610
611    def requestRegInfo(self,agent=''):
612        """Requests registration info from the server.
613           Returns the Iq object received from the server."""
614#        if agent: agent = agent + '.'
615        self._reg_info = {}
616#        reg_iq = Iq(type='get', to = agent + self._host)
617        reg_iq = Iq(type='get', to = agent)
618        reg_iq.setQuery(NS_REGISTER)
619#        self.DEBUG("Requesting reg info from %s%s:" % (agent, self._host), DBG_NODE_IQ)
620        self.DEBUG("Requesting reg info from %s:" % agent, DBG_NODE_IQ)
621        self.DEBUG(ustr(reg_iq),DBG_NODE_IQ)
622        return self.SendAndWaitForResponse(reg_iq)
623
624
625    def getRegInfo(self):
626        """Returns a dictionary of fields requested by the server for a
627           registration attempt.  Each dictionary entry maps from the name of
628           the field to the field's current value (either as returned by the
629           server or set programmatically by calling self.setRegInfo(). """
630        return self._reg_info
631
632
633    def setRegInfo(self,key,val):
634        """Sets a name/value attribute. Note: requestRegInfo must be
635           called before setting."""
636        self._reg_info[key] = val
637
638
639    def sendRegInfo(self, agent=None):
640        """Sends the populated registration dictionary back to the server"""
641#        if agent: agent = agent + '.'
642        if agent is None: agent = ''
643#        reg_iq = Iq(to = agent + self._host, type='set')
644        reg_iq = Iq(to = agent, type='set')
645        q = reg_iq.setQuery(NS_REGISTER)
646        for info in self._reg_info.keys():
647            q.insertTag(info).putData(self._reg_info[info])
648        return self.SendAndWaitForResponse(reg_iq)
649
650
651    def deregister(self, agent=None):
652        """ Send off a request to deregister with the server or with the given
653            agent.  Returns True if successful, else False.
654
655            Note that you must be authorised before attempting to deregister.
656        """
657        if agent:
658#            agent = agent + '.'
659#            self.send(Presence(to=agent+self._host,type='unsubscribed'))       # This is enough f.e. for icqv7t or jit
660            self.send(Presence(to=agent,type='unsubscribed'))       # This is enough f.e. for icqv7t or jit
661        if agent is None: agent = ''
662        q = self.requestRegInfo()
663        kids = q.getQueryPayload()
664        keyTag = kids.getTag("key")
665
666#        iq = Iq(to=agent+self._host, type="set")
667        iq = Iq(to=agent, type="set")
668        iq.setQuery(NS_REGISTER)
669        iq.setQueryNode("")
670        q = iq.getQueryNode()
671        if keyTag != None:
672            q.insertXML("<key>" + keyTag.getData() + "</key>")
673        q.insertXML("<remove/>")
674
675        result = self.SendAndWaitForResponse(iq)
676
677        if result == None:
678            return False
679        elif result.getType() == "result":
680            return True
681        else:
682            return False
683
684    ## Agent helper funcs ##
685
686    def requestAgents(self):
687        """Requests a list of available agents.  Returns a dictionary
688           containing information about each agent; each entry in the
689           dictionary maps the agent's JID to a dictionary of attributes
690           describing what that agent can do (as returned by the
691           NS_AGENTS query)."""
692        self._agents = {}
693        agents_iq =Â