root/branches/gajim_0.6.1/common/jabber.py

Revision 938, 52.5 kB (checked in by asterix, 4 years ago)

big bugfix: send the correct status

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