1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Basic Jabber client functionality implementation.
17
18 Extends `pyxmpp.client` interface with legacy authentication
19 and basic Service Discovery handling.
20
21 Normative reference:
22 - `JEP 78 <http://www.jabber.org/jeps/jep-0078.html>`__
23 - `JEP 30 <http://www.jabber.org/jeps/jep-0030.html>`__
24 """
25
26 __revision__="$Id: client.py 714 2010-04-05 10:20:10Z jajcus $"
27 __docformat__="restructuredtext en"
28
29 import logging
30
31 from pyxmpp.jabber.clientstream import LegacyClientStream
32 from pyxmpp.jabber.disco import DISCO_ITEMS_NS,DISCO_INFO_NS
33 from pyxmpp.jabber.disco import DiscoInfo,DiscoItems,DiscoIdentity
34 from pyxmpp.jabber import disco
35 from pyxmpp.client import Client
36 from pyxmpp.stanza import Stanza
37 from pyxmpp.cache import CacheSuite
38 from pyxmpp.utils import from_utf8
39 from pyxmpp.interfaces import IFeaturesProvider
40
42 """Base class for a Jabber client.
43
44 :Ivariables:
45 - `disco_items`: default Disco#items reply for a query to an empty node.
46 - `disco_info`: default Disco#info reply for a query to an empty node --
47 provides information about the client and its supported fetures.
48 - `disco_identity`: default identity of the default `disco_info`.
49 - `register`: when `True` than registration will be started instead of authentication.
50 :Types:
51 - `disco_items`: `DiscoItems`
52 - `disco_info`: `DiscoInfo`
53 - `register`: `bool`
54 """
55 - def __init__(self,jid=None, password=None, server=None, port=5222,
56 auth_methods=("sasl:DIGEST-MD5","digest"),
57 tls_settings=None, keepalive=0,
58 disco_name=u"pyxmpp based Jabber client", disco_category=u"client",
59 disco_type=u"pc"):
60 """Initialize a JabberClient object.
61
62 :Parameters:
63 - `jid`: user full JID for the connection.
64 - `password`: user password.
65 - `server`: server to use. If not given then address will be derived form the JID.
66 - `port`: port number to use. If not given then address will be derived form the JID.
67 - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms
68 in the list should be prefixed with "sasl:" string.
69 - `tls_settings`: settings for StartTLS -- `TLSSettings` instance.
70 - `keepalive`: keepalive output interval. 0 to disable.
71 - `disco_name`: name of the client identity in the disco#info
72 replies.
73 - `disco_category`: category of the client identity in the disco#info
74 replies. The default of u'client' should be the right choice in
75 most cases.
76 - `disco_type`: type of the client identity in the disco#info
77 replies. Use `the types registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
78 :Types:
79 - `jid`: `pyxmpp.JID`
80 - `password`: `unicode`
81 - `server`: `unicode`
82 - `port`: `int`
83 - `auth_methods`: sequence of `str`
84 - `tls_settings`: `pyxmpp.TLSSettings`
85 - `keepalive`: `int`
86 - `disco_name`: `unicode`
87 - `disco_category`: `unicode`
88 - `disco_type`: `unicode`
89 """
90
91 Client.__init__(self,jid,password,server,port,auth_methods,tls_settings,keepalive)
92 self.stream_class = LegacyClientStream
93 self.disco_items=DiscoItems()
94 self.disco_info=DiscoInfo()
95 self.disco_identity=DiscoIdentity(self.disco_info,
96 disco_name, disco_category, disco_type)
97 self.register_feature(u"dnssrv")
98 self.register_feature(u"stringprep")
99 self.register_feature(u"urn:ietf:params:xml:ns:xmpp-sasl#c2s")
100 self.cache = CacheSuite(max_items = 1000)
101 self.__logger = logging.getLogger("pyxmpp.jabber.JabberClient")
102
103
104
105 - def connect(self, register = False):
106 """Connect to the server and set up the stream.
107
108 Set `self.stream` and notify `self.state_changed` when connection
109 succeeds. Additionally, initialize Disco items and info of the client.
110 """
111 Client.connect(self, register)
112 if register:
113 self.stream.registration_callback = self.process_registration_form
114
116 """Register a feature to be announced by Service Discovery.
117
118 :Parameters:
119 - `feature_name`: feature namespace or name.
120 :Types:
121 - `feature_name`: `unicode`"""
122 self.disco_info.add_feature(feature_name)
123
125 """Unregister a feature to be announced by Service Discovery.
126
127 :Parameters:
128 - `feature_name`: feature namespace or name.
129 :Types:
130 - `feature_name`: `unicode`"""
131 self.disco_info.remove_feature(feature_name)
132
141
142
144 """Handle a disco#info request.
145
146 `self.disco_get_info` method will be used to prepare the query response.
147
148 :Parameters:
149 - `iq`: the IQ stanza received.
150 :Types:
151 - `iq`: `pyxmpp.iq.Iq`"""
152 q=iq.get_query()
153 if q.hasProp("node"):
154 node=from_utf8(q.prop("node"))
155 else:
156 node=None
157 info=self.disco_get_info(node,iq)
158 if isinstance(info,DiscoInfo):
159 resp=iq.make_result_response()
160 self.__logger.debug("Disco-info query: %s preparing response: %s with reply: %s"
161 % (iq.serialize(),resp.serialize(),info.xmlnode.serialize()))
162 resp.set_content(info.xmlnode.copyNode(1))
163 elif isinstance(info,Stanza):
164 resp=info
165 else:
166 resp=iq.make_error_response("item-not-found")
167 self.__logger.debug("Disco-info response: %s" % (resp.serialize(),))
168 self.stream.send(resp)
169
196
207
208
209
217
219 """Return Disco#info data for a node.
220
221 :Parameters:
222 - `node`: the node queried.
223 - `iq`: the request stanza received.
224 :Types:
225 - `node`: `unicode`
226 - `iq`: `pyxmpp.iq.Iq`
227
228 :return: self.disco_info if `node` is empty or `None` otherwise.
229 :returntype: `DiscoInfo`"""
230 to=iq.get_to()
231 if to and to!=self.jid:
232 return iq.make_error_response("recipient-unavailable")
233 if not node and self.disco_info:
234 return self.disco_info
235 return None
236
238 """Return Disco#items data for a node.
239
240 :Parameters:
241 - `node`: the node queried.
242 - `iq`: the request stanza received.
243 :Types:
244 - `node`: `unicode`
245 - `iq`: `pyxmpp.iq.Iq`
246
247 :return: self.disco_info if `node` is empty or `None` otherwise.
248 :returntype: `DiscoInfo`"""
249 to=iq.get_to()
250 if to and to!=self.jid:
251 return iq.make_error_response("recipient-unavailable")
252 if not node and self.disco_items:
253 return self.disco_items
254 return None
255
286
287
288