/src/pidgin/libpurple/protocols/jabber/disco.c
Line | Count | Source |
1 | | /* |
2 | | * purple - Jabber Service Discovery |
3 | | * |
4 | | * Purple is the legal property of its developers, whose names are too numerous |
5 | | * to list here. Please refer to the COPYRIGHT file distributed with this |
6 | | * source distribution. |
7 | | * |
8 | | * This program is free software; you can redistribute it and/or modify |
9 | | * it under the terms of the GNU General Public License as published by |
10 | | * the Free Software Foundation; either version 2 of the License, or |
11 | | * (at your option) any later version. |
12 | | * |
13 | | * This program is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU General Public License |
19 | | * along with this program; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA |
21 | | * |
22 | | */ |
23 | | |
24 | | #include "internal.h" |
25 | | #include "network.h" |
26 | | #include "prefs.h" |
27 | | #include "debug.h" |
28 | | #include "request.h" |
29 | | |
30 | | #include "adhoccommands.h" |
31 | | #include "buddy.h" |
32 | | #include "disco.h" |
33 | | #include "google/google.h" |
34 | | #include "google/gmail.h" |
35 | | #include "google/jingleinfo.h" |
36 | | #include "iq.h" |
37 | | #include "jabber.h" |
38 | | #include "jingle/jingle.h" |
39 | | #include "pep.h" |
40 | | #include "presence.h" |
41 | | #include "roster.h" |
42 | | #include "useravatar.h" |
43 | | |
44 | | struct _jabber_disco_info_cb_data { |
45 | | gpointer data; |
46 | | JabberDiscoInfoCallback *callback; |
47 | | }; |
48 | | |
49 | | struct _jabber_disco_items_cb_data { |
50 | | gpointer data; |
51 | | JabberDiscoItemsCallback *callback; |
52 | | }; |
53 | | |
54 | | #define SUPPORT_FEATURE(x) { \ |
55 | | feature = xmlnode_new_child(query, "feature"); \ |
56 | | xmlnode_set_attrib(feature, "var", x); \ |
57 | | } |
58 | | |
59 | | static void |
60 | | jabber_disco_bytestream_server_cb(JabberStream *js, const char *from, |
61 | | JabberIqType type, const char *id, |
62 | | xmlnode *packet, gpointer data) |
63 | 0 | { |
64 | 0 | JabberBytestreamsStreamhost *sh = data; |
65 | 0 | xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", |
66 | 0 | NS_BYTESTREAMS); |
67 | |
|
68 | 0 | if (from && purple_strequal(from, sh->jid) && query != NULL) { |
69 | 0 | xmlnode *sh_node = xmlnode_get_child(query, "streamhost"); |
70 | 0 | if (sh_node) { |
71 | 0 | const char *jid = xmlnode_get_attrib(sh_node, "jid"); |
72 | 0 | const char *port = xmlnode_get_attrib(sh_node, "port"); |
73 | | |
74 | |
|
75 | 0 | if (jid == NULL || !purple_strequal(jid, from)) |
76 | 0 | purple_debug_error("jabber", "Invalid jid(%s) for bytestream.\n", |
77 | 0 | jid ? jid : "(null)"); |
78 | |
|
79 | 0 | sh->host = g_strdup(xmlnode_get_attrib(sh_node, "host")); |
80 | 0 | sh->zeroconf = g_strdup(xmlnode_get_attrib(sh_node, "zeroconf")); |
81 | 0 | if (port != NULL) |
82 | 0 | sh->port = atoi(port); |
83 | 0 | } |
84 | 0 | } |
85 | |
|
86 | 0 | purple_debug_info("jabber", "Discovered bytestream proxy server: " |
87 | 0 | "jid='%s' host='%s' port='%d' zeroconf='%s'\n", |
88 | 0 | from ? from : "", sh->host ? sh->host : "", |
89 | 0 | sh->port, sh->zeroconf ? sh->zeroconf : ""); |
90 | | |
91 | | /* TODO: When we support zeroconf proxies, fix this to handle them */ |
92 | 0 | if (!(sh->jid && sh->host && sh->port > 0)) { |
93 | 0 | js->bs_proxies = g_list_remove(js->bs_proxies, sh); |
94 | 0 | g_free(sh->jid); |
95 | 0 | g_free(sh->host); |
96 | 0 | g_free(sh->zeroconf); |
97 | 0 | g_free(sh); |
98 | 0 | } |
99 | 0 | } |
100 | | |
101 | | |
102 | | void jabber_disco_info_parse(JabberStream *js, const char *from, |
103 | | JabberIqType type, const char *id, |
104 | | xmlnode *in_query) |
105 | 0 | { |
106 | 0 | if(type == JABBER_IQ_GET) { |
107 | 0 | xmlnode *query, *identity, *feature; |
108 | 0 | JabberIq *iq; |
109 | 0 | const char *node = xmlnode_get_attrib(in_query, "node"); |
110 | 0 | char *node_uri = NULL; |
111 | | |
112 | | /* create custom caps node URI */ |
113 | 0 | node_uri = g_strconcat(CAPS0115_NODE, "#", jabber_caps_get_own_hash(js), NULL); |
114 | |
|
115 | 0 | iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, NS_DISCO_INFO); |
116 | |
|
117 | 0 | jabber_iq_set_id(iq, id); |
118 | |
|
119 | 0 | if (from) |
120 | 0 | xmlnode_set_attrib(iq->node, "to", from); |
121 | 0 | query = xmlnode_get_child(iq->node, "query"); |
122 | |
|
123 | 0 | if(node) |
124 | 0 | xmlnode_set_attrib(query, "node", node); |
125 | |
|
126 | 0 | if(!node || purple_strequal(node, node_uri)) { |
127 | 0 | GList *features, *identities; |
128 | 0 | for(identities = jabber_identities; identities; identities = identities->next) { |
129 | 0 | JabberIdentity *ident = (JabberIdentity*)identities->data; |
130 | 0 | identity = xmlnode_new_child(query, "identity"); |
131 | 0 | xmlnode_set_attrib(identity, "category", ident->category); |
132 | 0 | xmlnode_set_attrib(identity, "type", ident->type); |
133 | 0 | if (ident->lang) |
134 | 0 | xmlnode_set_attrib(identity, "xml:lang", ident->lang); |
135 | 0 | if (ident->name) |
136 | 0 | xmlnode_set_attrib(identity, "name", ident->name); |
137 | 0 | } |
138 | 0 | for(features = jabber_features; features; features = features->next) { |
139 | 0 | JabberFeature *feat = (JabberFeature*)features->data; |
140 | 0 | if (!feat->is_enabled || feat->is_enabled(js, feat->namespace)) { |
141 | 0 | feature = xmlnode_new_child(query, "feature"); |
142 | 0 | xmlnode_set_attrib(feature, "var", feat->namespace); |
143 | 0 | } |
144 | 0 | } |
145 | | #ifdef USE_VV |
146 | | } else if (purple_strequal(node, CAPS0115_NODE "#" "voice-v1")) { |
147 | | /* |
148 | | * HUGE HACK! We advertise this ext (see jabber_presence_create_js |
149 | | * where we add <c/> to the <presence/>) for the Google Talk |
150 | | * clients that don't actually check disco#info features. |
151 | | * |
152 | | * This specific feature is redundant but is what |
153 | | * node='http://mail.google.com/xmpp/client/caps', ver='1.1' |
154 | | * advertises as 'voice-v1'. |
155 | | */ |
156 | | xmlnode *feature = xmlnode_new_child(query, "feature"); |
157 | | xmlnode_set_attrib(feature, "var", NS_GOOGLE_VOICE); |
158 | | } else if (purple_strequal(node, CAPS0115_NODE "#" "video-v1")) { |
159 | | /* |
160 | | * HUGE HACK! We advertise this ext (see jabber_presence_create_js |
161 | | * where we add <c/> to the <presence/>) for the Google Talk |
162 | | * clients that don't actually check disco#info features. |
163 | | * |
164 | | * This specific feature is redundant but is what |
165 | | * node='http://mail.google.com/xmpp/client/caps', ver='1.1' |
166 | | * advertises as 'video-v1'. |
167 | | */ |
168 | | xmlnode *feature = xmlnode_new_child(query, "feature"); |
169 | | xmlnode_set_attrib(feature, "var", NS_GOOGLE_VIDEO); |
170 | | } else if (purple_strequal(node, CAPS0115_NODE "#" "camera-v1")) { |
171 | | /* |
172 | | * HUGE HACK! We advertise this ext (see jabber_presence_create_js |
173 | | * where we add <c/> to the <presence/>) for the Google Talk |
174 | | * clients that don't actually check disco#info features. |
175 | | * |
176 | | * This specific feature is redundant but is what |
177 | | * node='http://mail.google.com/xmpp/client/caps', ver='1.1' |
178 | | * advertises as 'camera-v1'. |
179 | | */ |
180 | | xmlnode *feature = xmlnode_new_child(query, "feature"); |
181 | | xmlnode_set_attrib(feature, "var", NS_GOOGLE_CAMERA); |
182 | | #endif |
183 | 0 | } else { |
184 | 0 | xmlnode *error, *inf; |
185 | | |
186 | | /* XXX: gross hack, implement jabber_iq_set_type or something */ |
187 | 0 | xmlnode_set_attrib(iq->node, "type", "error"); |
188 | 0 | iq->type = JABBER_IQ_ERROR; |
189 | |
|
190 | 0 | error = xmlnode_new_child(query, "error"); |
191 | 0 | xmlnode_set_attrib(error, "code", "404"); |
192 | 0 | xmlnode_set_attrib(error, "type", "cancel"); |
193 | 0 | inf = xmlnode_new_child(error, "item-not-found"); |
194 | 0 | xmlnode_set_namespace(inf, NS_XMPP_STANZAS); |
195 | 0 | } |
196 | 0 | g_free(node_uri); |
197 | 0 | jabber_iq_send(iq); |
198 | 0 | } else if (type == JABBER_IQ_SET) { |
199 | | /* wtf? seriously. wtf‽ */ |
200 | 0 | JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR); |
201 | 0 | xmlnode *error, *bad_request; |
202 | | |
203 | | /* Free the <query/> */ |
204 | 0 | xmlnode_free(xmlnode_get_child(iq->node, "query")); |
205 | | /* Add an error */ |
206 | 0 | error = xmlnode_new_child(iq->node, "error"); |
207 | 0 | xmlnode_set_attrib(error, "type", "modify"); |
208 | 0 | bad_request = xmlnode_new_child(error, "bad-request"); |
209 | 0 | xmlnode_set_namespace(bad_request, NS_XMPP_STANZAS); |
210 | |
|
211 | 0 | jabber_iq_set_id(iq, id); |
212 | 0 | if (from) |
213 | 0 | xmlnode_set_attrib(iq->node, "to", from); |
214 | |
|
215 | 0 | jabber_iq_send(iq); |
216 | 0 | } |
217 | 0 | } |
218 | | |
219 | | static void jabber_disco_info_cb(JabberStream *js, const char *from, |
220 | | JabberIqType type, const char *id, |
221 | | xmlnode *packet, gpointer data) |
222 | 0 | { |
223 | 0 | struct _jabber_disco_info_cb_data *jdicd = data; |
224 | 0 | xmlnode *query; |
225 | |
|
226 | 0 | query = xmlnode_get_child_with_namespace(packet, "query", NS_DISCO_INFO); |
227 | |
|
228 | 0 | if (type == JABBER_IQ_RESULT && query) { |
229 | 0 | xmlnode *child; |
230 | 0 | JabberID *jid; |
231 | 0 | JabberBuddy *jb; |
232 | 0 | JabberBuddyResource *jbr = NULL; |
233 | 0 | JabberCapabilities capabilities = JABBER_CAP_NONE; |
234 | |
|
235 | 0 | if((jid = jabber_id_new(from))) { |
236 | 0 | if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) |
237 | 0 | jbr = jabber_buddy_find_resource(jb, jid->resource); |
238 | 0 | jabber_id_free(jid); |
239 | 0 | } |
240 | |
|
241 | 0 | if(jbr) |
242 | 0 | capabilities = jbr->capabilities; |
243 | |
|
244 | 0 | for(child = query->child; child; child = child->next) { |
245 | 0 | if(child->type != XMLNODE_TYPE_TAG) |
246 | 0 | continue; |
247 | | |
248 | 0 | if(purple_strequal(child->name, "identity")) { |
249 | 0 | const char *category = xmlnode_get_attrib(child, "category"); |
250 | 0 | const char *type = xmlnode_get_attrib(child, "type"); |
251 | 0 | if(!category || !type) |
252 | 0 | continue; |
253 | | |
254 | 0 | if(purple_strequal(category, "conference") && purple_strequal(type, "text")) { |
255 | | /* we found a groupchat or MUC server, add it to the list */ |
256 | | /* XXX: actually check for protocol/muc or gc-1.0 support */ |
257 | 0 | js->chat_servers = g_list_prepend(js->chat_servers, g_strdup(from)); |
258 | 0 | } else if(purple_strequal(category, "directory") && purple_strequal(type, "user")) { |
259 | | /* we found a JUD */ |
260 | 0 | js->user_directories = g_list_prepend(js->user_directories, g_strdup(from)); |
261 | 0 | } else if(purple_strequal(category, "proxy") && purple_strequal(type, "bytestreams")) { |
262 | | /* This is a bytestream proxy */ |
263 | 0 | JabberIq *iq; |
264 | 0 | JabberBytestreamsStreamhost *sh; |
265 | |
|
266 | 0 | purple_debug_info("jabber", "Found bytestream proxy server: %s\n", from); |
267 | |
|
268 | 0 | sh = g_new0(JabberBytestreamsStreamhost, 1); |
269 | 0 | sh->jid = g_strdup(from); |
270 | 0 | js->bs_proxies = g_list_prepend(js->bs_proxies, sh); |
271 | |
|
272 | 0 | iq = jabber_iq_new_query(js, JABBER_IQ_GET, |
273 | 0 | NS_BYTESTREAMS); |
274 | 0 | xmlnode_set_attrib(iq->node, "to", sh->jid); |
275 | 0 | jabber_iq_set_callback(iq, jabber_disco_bytestream_server_cb, sh); |
276 | 0 | jabber_iq_send(iq); |
277 | 0 | } |
278 | |
|
279 | 0 | } else if(purple_strequal(child->name, "feature")) { |
280 | 0 | const char *var = xmlnode_get_attrib(child, "var"); |
281 | 0 | if(!var) |
282 | 0 | continue; |
283 | | |
284 | 0 | if(purple_strequal(var, "http://jabber.org/protocol/si")) |
285 | 0 | capabilities |= JABBER_CAP_SI; |
286 | 0 | else if(purple_strequal(var, "http://jabber.org/protocol/si/profile/file-transfer")) |
287 | 0 | capabilities |= JABBER_CAP_SI_FILE_XFER; |
288 | 0 | else if(purple_strequal(var, NS_BYTESTREAMS)) |
289 | 0 | capabilities |= JABBER_CAP_BYTESTREAMS; |
290 | 0 | else if(purple_strequal(var, "jabber:iq:search")) |
291 | 0 | capabilities |= JABBER_CAP_IQ_SEARCH; |
292 | 0 | else if(purple_strequal(var, "jabber:iq:register")) |
293 | 0 | capabilities |= JABBER_CAP_IQ_REGISTER; |
294 | 0 | else if(purple_strequal(var, NS_PING)) |
295 | 0 | capabilities |= JABBER_CAP_PING; |
296 | 0 | else if(purple_strequal(var, NS_DISCO_ITEMS)) |
297 | 0 | capabilities |= JABBER_CAP_ITEMS; |
298 | 0 | else if(purple_strequal(var, "http://jabber.org/protocol/commands")) { |
299 | 0 | capabilities |= JABBER_CAP_ADHOC; |
300 | 0 | } |
301 | 0 | else if(purple_strequal(var, NS_IBB)) { |
302 | 0 | purple_debug_info("jabber", "remote supports IBB\n"); |
303 | 0 | capabilities |= JABBER_CAP_IBB; |
304 | 0 | } |
305 | 0 | } |
306 | 0 | } |
307 | |
|
308 | 0 | js->chat_servers = g_list_reverse(js->chat_servers); |
309 | |
|
310 | 0 | capabilities |= JABBER_CAP_RETRIEVED; |
311 | |
|
312 | 0 | if(jbr) |
313 | 0 | jbr->capabilities = capabilities; |
314 | |
|
315 | 0 | if (jdicd && jdicd->callback) |
316 | 0 | jdicd->callback(js, from, capabilities, jdicd->data); |
317 | 0 | } else { /* type == JABBER_IQ_ERROR or query == NULL */ |
318 | 0 | JabberID *jid; |
319 | 0 | JabberBuddy *jb; |
320 | 0 | JabberBuddyResource *jbr = NULL; |
321 | 0 | JabberCapabilities capabilities = JABBER_CAP_NONE; |
322 | |
|
323 | 0 | if((jid = jabber_id_new(from))) { |
324 | 0 | if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) |
325 | 0 | jbr = jabber_buddy_find_resource(jb, jid->resource); |
326 | 0 | jabber_id_free(jid); |
327 | 0 | } |
328 | |
|
329 | 0 | if(jbr) |
330 | 0 | capabilities = jbr->capabilities; |
331 | |
|
332 | 0 | if (jdicd && jdicd->callback) |
333 | 0 | jdicd->callback(js, from, capabilities, jdicd->data); |
334 | 0 | } |
335 | |
|
336 | 0 | g_free(jdicd); |
337 | 0 | } |
338 | | |
339 | | void jabber_disco_items_parse(JabberStream *js, const char *from, |
340 | | JabberIqType type, const char *id, |
341 | | xmlnode *query) |
342 | 0 | { |
343 | 0 | if(type == JABBER_IQ_GET) { |
344 | 0 | JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, |
345 | 0 | NS_DISCO_ITEMS); |
346 | | |
347 | | /* preserve node */ |
348 | 0 | xmlnode *iq_query = xmlnode_get_child(iq->node, "query"); |
349 | 0 | const char *node = xmlnode_get_attrib(query, "node"); |
350 | 0 | if(node) |
351 | 0 | xmlnode_set_attrib(iq_query,"node",node); |
352 | |
|
353 | 0 | jabber_iq_set_id(iq, id); |
354 | |
|
355 | 0 | if (from) |
356 | 0 | xmlnode_set_attrib(iq->node, "to", from); |
357 | 0 | jabber_iq_send(iq); |
358 | 0 | } |
359 | 0 | } |
360 | | |
361 | | static void |
362 | | jabber_disco_finish_server_info_result_cb(JabberStream *js) |
363 | 0 | { |
364 | 0 | const char *ft_proxies; |
365 | | |
366 | | /* |
367 | | * This *should* happen only if the server supports vcard-temp, but there |
368 | | * are apparently some servers that don't advertise it even though they |
369 | | * support it. |
370 | | */ |
371 | 0 | jabber_vcard_fetch_mine(js); |
372 | |
|
373 | 0 | if (js->pep) |
374 | 0 | jabber_avatar_fetch_mine(js); |
375 | | |
376 | | /* Yes, please! */ |
377 | 0 | jabber_roster_request(js); |
378 | |
|
379 | 0 | if (js->server_caps & JABBER_CAP_ADHOC) { |
380 | | /* The server supports ad-hoc commands, so let's request the list */ |
381 | 0 | jabber_adhoc_server_get_list(js); |
382 | 0 | } |
383 | | |
384 | | /* If the server supports blocking, request the block list */ |
385 | 0 | if (js->server_caps & JABBER_CAP_BLOCKING) { |
386 | 0 | jabber_request_block_list(js); |
387 | 0 | } |
388 | | |
389 | | /* If there are manually specified bytestream proxies, query them */ |
390 | 0 | ft_proxies = purple_account_get_string(js->gc->account, "ft_proxies", NULL); |
391 | 0 | if (ft_proxies) { |
392 | 0 | JabberIq *iq; |
393 | 0 | JabberBytestreamsStreamhost *sh; |
394 | 0 | int i; |
395 | 0 | char *tmp; |
396 | 0 | gchar **ft_proxy_list = g_strsplit(ft_proxies, ",", 0); |
397 | |
|
398 | 0 | for(i = 0; ft_proxy_list[i]; i++) { |
399 | 0 | g_strstrip(ft_proxy_list[i]); |
400 | 0 | if(!(*ft_proxy_list[i])) |
401 | 0 | continue; |
402 | | |
403 | | /* We used to allow specifying a port directly here; get rid of it */ |
404 | 0 | if((tmp = strchr(ft_proxy_list[i], ':'))) |
405 | 0 | *tmp = '\0'; |
406 | |
|
407 | 0 | sh = g_new0(JabberBytestreamsStreamhost, 1); |
408 | 0 | sh->jid = g_strdup(ft_proxy_list[i]); |
409 | 0 | js->bs_proxies = g_list_prepend(js->bs_proxies, sh); |
410 | |
|
411 | 0 | iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_BYTESTREAMS); |
412 | 0 | xmlnode_set_attrib(iq->node, "to", sh->jid); |
413 | 0 | jabber_iq_set_callback(iq, jabber_disco_bytestream_server_cb, sh); |
414 | 0 | jabber_iq_send(iq); |
415 | 0 | } |
416 | |
|
417 | 0 | g_strfreev(ft_proxy_list); |
418 | 0 | } |
419 | |
|
420 | 0 | } |
421 | | |
422 | | /* should probably share this code with google.c, or maybe from 2.7.0 |
423 | | introduce an abstracted hostname -> IP function in dns.c */ |
424 | | static void |
425 | | jabber_disco_stun_lookup_cb(GSList *hosts, gpointer data, |
426 | | const char *error_message) |
427 | 0 | { |
428 | 0 | JabberStream *js = (JabberStream *) data; |
429 | |
|
430 | 0 | if (error_message) { |
431 | 0 | purple_debug_error("jabber", "STUN lookup failed: %s\n", |
432 | 0 | error_message); |
433 | 0 | g_slist_free(hosts); |
434 | 0 | js->stun_query = NULL; |
435 | 0 | return; |
436 | 0 | } |
437 | | |
438 | 0 | if (hosts && g_slist_next(hosts)) { |
439 | 0 | struct sockaddr *addr = g_slist_next(hosts)->data; |
440 | 0 | char dst[INET6_ADDRSTRLEN]; |
441 | 0 | int port; |
442 | |
|
443 | 0 | if (addr->sa_family == AF_INET6) { |
444 | 0 | inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, |
445 | 0 | dst, sizeof(dst)); |
446 | 0 | port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port); |
447 | 0 | } else { |
448 | 0 | inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, |
449 | 0 | dst, sizeof(dst)); |
450 | 0 | port = ntohs(((struct sockaddr_in *) addr)->sin_port); |
451 | 0 | } |
452 | |
|
453 | 0 | if (js->stun_ip) |
454 | 0 | g_free(js->stun_ip); |
455 | 0 | js->stun_ip = g_strdup(dst); |
456 | 0 | js->stun_port = port; |
457 | |
|
458 | 0 | purple_debug_info("jabber", "set STUN IP/port address: " |
459 | 0 | "%s:%d\n", dst, port); |
460 | | |
461 | | /* unmark ongoing query */ |
462 | 0 | js->stun_query = NULL; |
463 | 0 | } |
464 | |
|
465 | 0 | while (hosts != NULL) { |
466 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
467 | | /* Free the address */ |
468 | 0 | g_free(hosts->data); |
469 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
470 | 0 | } |
471 | 0 | } |
472 | | |
473 | | |
474 | | static void |
475 | | jabber_disco_stun_srv_resolve_cb(PurpleSrvResponse *resp, int results, gpointer data) |
476 | 0 | { |
477 | 0 | JabberStream *js = (JabberStream *) data; |
478 | |
|
479 | 0 | purple_debug_info("jabber", "got %d SRV responses for STUN.\n", results); |
480 | 0 | js->srv_query_data = NULL; |
481 | |
|
482 | 0 | if (results > 0) { |
483 | 0 | PurpleAccount *account; |
484 | 0 | purple_debug_info("jabber", "looking up IP for %s:%d\n", |
485 | 0 | resp[0].hostname, resp[0].port); |
486 | 0 | account = purple_connection_get_account(js->gc); |
487 | 0 | js->stun_query = |
488 | 0 | purple_dnsquery_a_account(account, resp[0].hostname, resp[0].port, |
489 | 0 | jabber_disco_stun_lookup_cb, js); |
490 | 0 | } |
491 | 0 | } |
492 | | |
493 | | |
494 | | static void |
495 | | jabber_disco_server_info_result_cb(JabberStream *js, const char *from, |
496 | | JabberIqType type, const char *id, |
497 | | xmlnode *packet, gpointer data) |
498 | 0 | { |
499 | 0 | xmlnode *query, *child; |
500 | |
|
501 | 0 | if (!from || !purple_strequal(from, js->user->domain)) { |
502 | 0 | jabber_disco_finish_server_info_result_cb(js); |
503 | 0 | return; |
504 | 0 | } |
505 | | |
506 | 0 | if (type == JABBER_IQ_ERROR) { |
507 | | /* A common way to get here is for the server not to support xmlns http://jabber.org/protocol/disco#info */ |
508 | 0 | jabber_disco_finish_server_info_result_cb(js); |
509 | 0 | return; |
510 | 0 | } |
511 | | |
512 | 0 | query = xmlnode_get_child(packet, "query"); |
513 | |
|
514 | 0 | if (!query) { |
515 | 0 | jabber_disco_finish_server_info_result_cb(js); |
516 | 0 | return; |
517 | 0 | } |
518 | | |
519 | 0 | for (child = xmlnode_get_child(query, "identity"); child; |
520 | 0 | child = xmlnode_get_next_twin(child)) { |
521 | 0 | const char *category, *type, *name; |
522 | 0 | const char *stun_ip; |
523 | 0 | category = xmlnode_get_attrib(child, "category"); |
524 | 0 | type = xmlnode_get_attrib(child, "type"); |
525 | 0 | if(purple_strequal(category, "pubsub") && purple_strequal(type, "pep")) { |
526 | 0 | PurpleConnection *gc = js->gc; |
527 | 0 | js->pep = TRUE; |
528 | 0 | gc->flags |= PURPLE_CONNECTION_SUPPORT_MOODS | |
529 | 0 | PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES; |
530 | 0 | } |
531 | 0 | if (!purple_strequal(category, "server")) |
532 | 0 | continue; |
533 | 0 | if (!purple_strequal(type, "im")) |
534 | 0 | continue; |
535 | | |
536 | 0 | name = xmlnode_get_attrib(child, "name"); |
537 | 0 | if (!name) |
538 | 0 | continue; |
539 | | |
540 | 0 | g_free(js->server_name); |
541 | 0 | js->server_name = g_strdup(name); |
542 | 0 | stun_ip = purple_network_get_stun_ip(); |
543 | 0 | if (purple_strequal(name, "Google Talk")) { |
544 | 0 | purple_debug_info("jabber", "Google Talk!\n"); |
545 | 0 | js->googletalk = TRUE; |
546 | | |
547 | | /* autodiscover stun and relays */ |
548 | 0 | if (!stun_ip || !*stun_ip) { |
549 | 0 | jabber_google_send_jingle_info(js); |
550 | 0 | } |
551 | 0 | } else if (!stun_ip || !*stun_ip) { |
552 | 0 | js->srv_query_data = |
553 | 0 | purple_srv_resolve_account( |
554 | 0 | purple_connection_get_account(js->gc), "stun", "udp", |
555 | 0 | js->user->domain, |
556 | 0 | jabber_disco_stun_srv_resolve_cb, js); |
557 | | /* TODO: add TURN support later... */ |
558 | 0 | } |
559 | 0 | } |
560 | |
|
561 | 0 | for (child = xmlnode_get_child(query, "feature"); child; |
562 | 0 | child = xmlnode_get_next_twin(child)) { |
563 | 0 | const char *var; |
564 | 0 | var = xmlnode_get_attrib(child, "var"); |
565 | 0 | if (!var) |
566 | 0 | continue; |
567 | | |
568 | 0 | if (purple_strequal(NS_GOOGLE_MAIL_NOTIFY, var)) { |
569 | 0 | js->server_caps |= JABBER_CAP_GMAIL_NOTIFY; |
570 | 0 | jabber_gmail_init(js); |
571 | 0 | } else if (purple_strequal(NS_GOOGLE_ROSTER, var)) { |
572 | 0 | js->server_caps |= JABBER_CAP_GOOGLE_ROSTER; |
573 | 0 | } else if (purple_strequal("http://jabber.org/protocol/commands", var)) { |
574 | 0 | js->server_caps |= JABBER_CAP_ADHOC; |
575 | 0 | } else if (purple_strequal(NS_SIMPLE_BLOCKING, var)) { |
576 | 0 | js->server_caps |= JABBER_CAP_BLOCKING; |
577 | 0 | } |
578 | 0 | } |
579 | |
|
580 | 0 | jabber_disco_finish_server_info_result_cb(js); |
581 | 0 | } |
582 | | |
583 | | static void |
584 | | jabber_disco_server_items_result_cb(JabberStream *js, const char *from, |
585 | | JabberIqType type, const char *id, |
586 | | xmlnode *packet, gpointer data) |
587 | 0 | { |
588 | 0 | xmlnode *query, *child; |
589 | |
|
590 | 0 | if (!from || !purple_strequal(from, js->user->domain)) |
591 | 0 | return; |
592 | | |
593 | 0 | if (type == JABBER_IQ_ERROR) |
594 | 0 | return; |
595 | | |
596 | 0 | while(js->chat_servers) { |
597 | 0 | g_free(js->chat_servers->data); |
598 | 0 | js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers); |
599 | 0 | } |
600 | |
|
601 | 0 | query = xmlnode_get_child(packet, "query"); |
602 | |
|
603 | 0 | for(child = xmlnode_get_child(query, "item"); child; |
604 | 0 | child = xmlnode_get_next_twin(child)) { |
605 | 0 | JabberIq *iq; |
606 | 0 | const char *jid; |
607 | |
|
608 | 0 | if(!(jid = xmlnode_get_attrib(child, "jid"))) |
609 | 0 | continue; |
610 | | |
611 | | /* we don't actually care about the specific nodes, |
612 | | * so we won't query them */ |
613 | 0 | if(xmlnode_get_attrib(child, "node") != NULL) |
614 | 0 | continue; |
615 | | |
616 | 0 | iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO); |
617 | 0 | xmlnode_set_attrib(iq->node, "to", jid); |
618 | 0 | jabber_iq_set_callback(iq, jabber_disco_info_cb, NULL); |
619 | 0 | jabber_iq_send(iq); |
620 | 0 | } |
621 | 0 | } |
622 | | |
623 | | void jabber_disco_items_server(JabberStream *js) |
624 | 0 | { |
625 | 0 | JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_ITEMS); |
626 | |
|
627 | 0 | xmlnode_set_attrib(iq->node, "to", js->user->domain); |
628 | |
|
629 | 0 | jabber_iq_set_callback(iq, jabber_disco_server_items_result_cb, NULL); |
630 | 0 | jabber_iq_send(iq); |
631 | |
|
632 | 0 | iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO); |
633 | 0 | xmlnode_set_attrib(iq->node, "to", js->user->domain); |
634 | 0 | jabber_iq_set_callback(iq, jabber_disco_server_info_result_cb, NULL); |
635 | 0 | jabber_iq_send(iq); |
636 | 0 | } |
637 | | |
638 | | void jabber_disco_info_do(JabberStream *js, const char *who, JabberDiscoInfoCallback *callback, gpointer data) |
639 | 0 | { |
640 | 0 | JabberID *jid; |
641 | 0 | JabberBuddy *jb; |
642 | 0 | JabberBuddyResource *jbr = NULL; |
643 | 0 | struct _jabber_disco_info_cb_data *jdicd; |
644 | 0 | JabberIq *iq; |
645 | |
|
646 | 0 | if((jid = jabber_id_new(who))) { |
647 | 0 | if(jid->resource && (jb = jabber_buddy_find(js, who, TRUE))) |
648 | 0 | jbr = jabber_buddy_find_resource(jb, jid->resource); |
649 | 0 | jabber_id_free(jid); |
650 | 0 | } |
651 | |
|
652 | 0 | if(jbr && jbr->capabilities & JABBER_CAP_RETRIEVED) { |
653 | 0 | callback(js, who, jbr->capabilities, data); |
654 | 0 | return; |
655 | 0 | } |
656 | | |
657 | 0 | jdicd = g_new0(struct _jabber_disco_info_cb_data, 1); |
658 | 0 | jdicd->data = data; |
659 | 0 | jdicd->callback = callback; |
660 | |
|
661 | 0 | iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO); |
662 | 0 | xmlnode_set_attrib(iq->node, "to", who); |
663 | |
|
664 | 0 | jabber_iq_set_callback(iq, jabber_disco_info_cb, jdicd); |
665 | 0 | jabber_iq_send(iq); |
666 | 0 | } |
667 | | |