Replace functions which called once with their bodies
[pidgin-git.git] / libpurple / protocols / jabber / disco.c
blob6cbbde049f585add0dc0ce0ac99f3d359df7c088
1 /*
2 * purple - Jabber Service Discovery
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.
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.
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.
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
24 #include "internal.h"
25 #include "network.h"
26 #include "prefs.h"
27 #include "debug.h"
28 #include "request.h"
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"
44 struct _jabber_disco_info_cb_data {
45 gpointer data;
46 JabberDiscoInfoCallback *callback;
49 struct _jabber_disco_items_cb_data {
50 gpointer data;
51 JabberDiscoItemsCallback *callback;
54 #define SUPPORT_FEATURE(x) { \
55 feature = purple_xmlnode_new_child(query, "feature"); \
56 purple_xmlnode_set_attrib(feature, "var", x); \
59 static void
60 jabber_disco_bytestream_server_cb(JabberStream *js, const char *from,
61 JabberIqType type, const char *id,
62 PurpleXmlNode *packet, gpointer data)
64 JabberBytestreamsStreamhost *sh = data;
65 PurpleXmlNode *query = purple_xmlnode_get_child_with_namespace(packet, "query",
66 NS_BYTESTREAMS);
68 if (from && purple_strequal(from, sh->jid) && query != NULL) {
69 PurpleXmlNode *sh_node = purple_xmlnode_get_child(query, "streamhost");
70 if (sh_node) {
71 const char *jid = purple_xmlnode_get_attrib(sh_node, "jid");
72 const char *port = purple_xmlnode_get_attrib(sh_node, "port");
75 if (jid == NULL || !purple_strequal(jid, from))
76 purple_debug_error("jabber", "Invalid jid(%s) for bytestream.\n",
77 jid ? jid : "(null)");
79 sh->host = g_strdup(purple_xmlnode_get_attrib(sh_node, "host"));
80 sh->zeroconf = g_strdup(purple_xmlnode_get_attrib(sh_node, "zeroconf"));
81 if (port != NULL)
82 sh->port = atoi(port);
86 purple_debug_info("jabber", "Discovered bytestream proxy server: "
87 "jid='%s' host='%s' port='%d' zeroconf='%s'\n",
88 from ? from : "", sh->host ? sh->host : "",
89 sh->port, sh->zeroconf ? sh->zeroconf : "");
91 /* TODO: When we support zeroconf proxies, fix this to handle them */
92 if (!(sh->jid && sh->host && sh->port > 0)) {
93 js->bs_proxies = g_list_remove(js->bs_proxies, sh);
94 g_free(sh->jid);
95 g_free(sh->host);
96 g_free(sh->zeroconf);
97 g_free(sh);
102 void jabber_disco_info_parse(JabberStream *js, const char *from,
103 JabberIqType type, const char *id,
104 PurpleXmlNode *in_query)
106 if(type == JABBER_IQ_GET) {
107 PurpleXmlNode *query, *identity, *feature;
108 JabberIq *iq;
109 const char *node = purple_xmlnode_get_attrib(in_query, "node");
110 char *node_uri = NULL;
112 /* create custom caps node URI */
113 node_uri = g_strconcat(CAPS0115_NODE, "#", jabber_caps_get_own_hash(js), NULL);
115 iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, NS_DISCO_INFO);
117 jabber_iq_set_id(iq, id);
119 if (from)
120 purple_xmlnode_set_attrib(iq->node, "to", from);
121 query = purple_xmlnode_get_child(iq->node, "query");
123 if(node)
124 purple_xmlnode_set_attrib(query, "node", node);
126 if(!node || purple_strequal(node, node_uri)) {
127 GList *features, *identities;
128 for(identities = jabber_identities; identities; identities = identities->next) {
129 JabberIdentity *ident = (JabberIdentity*)identities->data;
130 identity = purple_xmlnode_new_child(query, "identity");
131 purple_xmlnode_set_attrib(identity, "category", ident->category);
132 purple_xmlnode_set_attrib(identity, "type", ident->type);
133 if (ident->lang)
134 purple_xmlnode_set_attrib(identity, "xml:lang", ident->lang);
135 if (ident->name)
136 purple_xmlnode_set_attrib(identity, "name", ident->name);
138 for(features = jabber_features; features; features = features->next) {
139 JabberFeature *feat = (JabberFeature*)features->data;
140 if (!feat->is_enabled || feat->is_enabled(js, feat->namespace)) {
141 feature = purple_xmlnode_new_child(query, "feature");
142 purple_xmlnode_set_attrib(feature, "var", feat->namespace);
145 #ifdef USE_VV
146 } else if (purple_strequal(node, CAPS0115_NODE "#" "voice-v1")) {
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.
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'.
156 PurpleXmlNode *feature = purple_xmlnode_new_child(query, "feature");
157 purple_xmlnode_set_attrib(feature, "var", NS_GOOGLE_VOICE);
158 } else if (purple_strequal(node, CAPS0115_NODE "#" "video-v1")) {
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.
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'.
168 PurpleXmlNode *feature = purple_xmlnode_new_child(query, "feature");
169 purple_xmlnode_set_attrib(feature, "var", NS_GOOGLE_VIDEO);
170 } else if (purple_strequal(node, CAPS0115_NODE "#" "camera-v1")) {
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.
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'.
180 PurpleXmlNode *feature = purple_xmlnode_new_child(query, "feature");
181 purple_xmlnode_set_attrib(feature, "var", NS_GOOGLE_CAMERA);
182 #endif
183 } else {
184 PurpleXmlNode *error, *inf;
186 /* XXX: gross hack, implement jabber_iq_set_type or something */
187 purple_xmlnode_set_attrib(iq->node, "type", "error");
188 iq->type = JABBER_IQ_ERROR;
190 error = purple_xmlnode_new_child(query, "error");
191 purple_xmlnode_set_attrib(error, "code", "404");
192 purple_xmlnode_set_attrib(error, "type", "cancel");
193 inf = purple_xmlnode_new_child(error, "item-not-found");
194 purple_xmlnode_set_namespace(inf, NS_XMPP_STANZAS);
196 g_free(node_uri);
197 jabber_iq_send(iq);
198 } else if (type == JABBER_IQ_SET) {
199 /* wtf? seriously. wtf‽ */
200 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
201 PurpleXmlNode *error, *bad_request;
203 /* Free the <query/> */
204 purple_xmlnode_free(purple_xmlnode_get_child(iq->node, "query"));
205 /* Add an error */
206 error = purple_xmlnode_new_child(iq->node, "error");
207 purple_xmlnode_set_attrib(error, "type", "modify");
208 bad_request = purple_xmlnode_new_child(error, "bad-request");
209 purple_xmlnode_set_namespace(bad_request, NS_XMPP_STANZAS);
211 jabber_iq_set_id(iq, id);
212 if (from)
213 purple_xmlnode_set_attrib(iq->node, "to", from);
215 jabber_iq_send(iq);
219 static void jabber_disco_info_cb(JabberStream *js, const char *from,
220 JabberIqType type, const char *id,
221 PurpleXmlNode *packet, gpointer data)
223 struct _jabber_disco_info_cb_data *jdicd = data;
224 PurpleXmlNode *query;
226 query = purple_xmlnode_get_child_with_namespace(packet, "query", NS_DISCO_INFO);
228 if (type == JABBER_IQ_RESULT && query) {
229 PurpleXmlNode *child;
230 JabberID *jid;
231 JabberBuddy *jb;
232 JabberBuddyResource *jbr = NULL;
233 JabberCapabilities capabilities = JABBER_CAP_NONE;
235 if((jid = jabber_id_new(from))) {
236 if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
237 jbr = jabber_buddy_find_resource(jb, jid->resource);
238 jabber_id_free(jid);
241 if(jbr)
242 capabilities = jbr->capabilities;
244 for(child = query->child; child; child = child->next) {
245 if(child->type != PURPLE_XMLNODE_TYPE_TAG)
246 continue;
248 if(purple_strequal(child->name, "identity")) {
249 const char *category = purple_xmlnode_get_attrib(child, "category");
250 const char *type = purple_xmlnode_get_attrib(child, "type");
251 if(!category || !type)
252 continue;
254 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 js->chat_servers = g_list_prepend(js->chat_servers, g_strdup(from));
258 } else if(purple_strequal(category, "directory") && purple_strequal(type, "user")) {
259 /* we found a JUD */
260 js->user_directories = g_list_prepend(js->user_directories, g_strdup(from));
261 } else if(purple_strequal(category, "proxy") && purple_strequal(type, "bytestreams")) {
262 /* This is a bytestream proxy */
263 JabberIq *iq;
264 JabberBytestreamsStreamhost *sh;
266 purple_debug_info("jabber", "Found bytestream proxy server: %s\n", from);
268 sh = g_new0(JabberBytestreamsStreamhost, 1);
269 sh->jid = g_strdup(from);
270 js->bs_proxies = g_list_prepend(js->bs_proxies, sh);
272 iq = jabber_iq_new_query(js, JABBER_IQ_GET,
273 NS_BYTESTREAMS);
274 purple_xmlnode_set_attrib(iq->node, "to", sh->jid);
275 jabber_iq_set_callback(iq, jabber_disco_bytestream_server_cb, sh);
276 jabber_iq_send(iq);
279 } else if(purple_strequal(child->name, "feature")) {
280 const char *var = purple_xmlnode_get_attrib(child, "var");
281 if(!var)
282 continue;
284 if(purple_strequal(var, "http://jabber.org/protocol/si"))
285 capabilities |= JABBER_CAP_SI;
286 else if(purple_strequal(var, "http://jabber.org/protocol/si/profile/file-transfer"))
287 capabilities |= JABBER_CAP_SI_FILE_XFER;
288 else if(purple_strequal(var, NS_BYTESTREAMS))
289 capabilities |= JABBER_CAP_BYTESTREAMS;
290 else if(purple_strequal(var, "jabber:iq:search"))
291 capabilities |= JABBER_CAP_IQ_SEARCH;
292 else if(purple_strequal(var, "jabber:iq:register"))
293 capabilities |= JABBER_CAP_IQ_REGISTER;
294 else if(purple_strequal(var, NS_PING))
295 capabilities |= JABBER_CAP_PING;
296 else if(purple_strequal(var, NS_DISCO_ITEMS))
297 capabilities |= JABBER_CAP_ITEMS;
298 else if(purple_strequal(var, "http://jabber.org/protocol/commands")) {
299 capabilities |= JABBER_CAP_ADHOC;
301 else if(purple_strequal(var, NS_IBB)) {
302 purple_debug_info("jabber", "remote supports IBB\n");
303 capabilities |= JABBER_CAP_IBB;
308 js->chat_servers = g_list_reverse(js->chat_servers);
310 capabilities |= JABBER_CAP_RETRIEVED;
312 if(jbr)
313 jbr->capabilities = capabilities;
315 if (jdicd && jdicd->callback)
316 jdicd->callback(js, from, capabilities, jdicd->data);
317 } else { /* type == JABBER_IQ_ERROR or query == NULL */
318 JabberID *jid;
319 JabberBuddy *jb;
320 JabberBuddyResource *jbr = NULL;
321 JabberCapabilities capabilities = JABBER_CAP_NONE;
323 if((jid = jabber_id_new(from))) {
324 if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
325 jbr = jabber_buddy_find_resource(jb, jid->resource);
326 jabber_id_free(jid);
329 if(jbr)
330 capabilities = jbr->capabilities;
332 if (jdicd && jdicd->callback)
333 jdicd->callback(js, from, capabilities, jdicd->data);
336 g_free(jdicd);
339 void jabber_disco_items_parse(JabberStream *js, const char *from,
340 JabberIqType type, const char *id,
341 PurpleXmlNode *query)
343 if(type == JABBER_IQ_GET) {
344 JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT,
345 NS_DISCO_ITEMS);
347 /* preserve node */
348 PurpleXmlNode *iq_query = purple_xmlnode_get_child(iq->node, "query");
349 const char *node = purple_xmlnode_get_attrib(query, "node");
350 if(node)
351 purple_xmlnode_set_attrib(iq_query,"node",node);
353 jabber_iq_set_id(iq, id);
355 if (from)
356 purple_xmlnode_set_attrib(iq->node, "to", from);
357 jabber_iq_send(iq);
361 static void
362 jabber_disco_finish_server_info_result_cb(JabberStream *js)
364 const char *ft_proxies;
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.
371 jabber_vcard_fetch_mine(js);
373 if (js->pep)
374 jabber_avatar_fetch_mine(js);
376 /* Yes, please! */
377 jabber_roster_request(js);
379 if (js->server_caps & JABBER_CAP_ADHOC) {
380 /* The server supports ad-hoc commands, so let's request the list */
381 jabber_adhoc_server_get_list(js);
384 /* If the server supports blocking, request the block list */
385 if (js->server_caps & JABBER_CAP_BLOCKING) {
386 jabber_request_block_list(js);
389 /* If there are manually specified bytestream proxies, query them */
390 ft_proxies = purple_account_get_string(purple_connection_get_account(js->gc), "ft_proxies", NULL);
391 if (ft_proxies) {
392 JabberIq *iq;
393 JabberBytestreamsStreamhost *sh;
394 int i;
395 char *tmp;
396 gchar **ft_proxy_list = g_strsplit(ft_proxies, ",", 0);
398 for(i = 0; ft_proxy_list[i]; i++) {
399 g_strstrip(ft_proxy_list[i]);
400 if(!(*ft_proxy_list[i]))
401 continue;
403 /* We used to allow specifying a port directly here; get rid of it */
404 if((tmp = strchr(ft_proxy_list[i], ':')))
405 *tmp = '\0';
407 sh = g_new0(JabberBytestreamsStreamhost, 1);
408 sh->jid = g_strdup(ft_proxy_list[i]);
409 js->bs_proxies = g_list_prepend(js->bs_proxies, sh);
411 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_BYTESTREAMS);
412 purple_xmlnode_set_attrib(iq->node, "to", sh->jid);
413 jabber_iq_set_callback(iq, jabber_disco_bytestream_server_cb, sh);
414 jabber_iq_send(iq);
417 g_strfreev(ft_proxy_list);
422 static void
423 jabber_disco_stun_srv_resolve_cb(GObject *sender, GAsyncResult *result, gpointer data) {
424 GError *error = NULL;
425 GList *services = NULL;
426 JabberStream *js = (JabberStream *) data;
427 gint results = 0;
429 services = g_resolver_lookup_service_finish(G_RESOLVER(sender),
430 result, &error);
432 if(error != NULL) {
433 purple_debug_info("jabber", "Failed to look up a STUN record : %s\n", error->message);
435 g_error_free(error);
437 return;
440 results = g_list_length(services);
442 purple_debug_info("jabber", "got %d SRV responses for STUN.\n", results);
444 if (results > 0) {
445 GSrvTarget *target = (GSrvTarget *)services->data;
446 const gchar *hostname = g_srv_target_get_hostname(target);
448 js->stun_ip = g_strdup(hostname);
449 js->stun_port = g_srv_target_get_port(target);
451 purple_debug_info("jabber", "set stun address to %s:%d\n",
452 hostname, js->stun_port);
455 g_resolver_free_targets(services);
459 static void
460 jabber_disco_server_info_result_cb(JabberStream *js, const char *from,
461 JabberIqType type, const char *id,
462 PurpleXmlNode *packet, gpointer data)
464 PurpleXmlNode *query, *child;
466 if (!from || !purple_strequal(from, js->user->domain)) {
467 jabber_disco_finish_server_info_result_cb(js);
468 return;
471 if (type == JABBER_IQ_ERROR) {
472 /* A common way to get here is for the server not to support xmlns http://jabber.org/protocol/disco#info */
473 jabber_disco_finish_server_info_result_cb(js);
474 return;
477 query = purple_xmlnode_get_child(packet, "query");
479 if (!query) {
480 jabber_disco_finish_server_info_result_cb(js);
481 return;
484 for (child = purple_xmlnode_get_child(query, "identity"); child;
485 child = purple_xmlnode_get_next_twin(child)) {
486 const char *category, *type, *name, *stun_ip;
487 category = purple_xmlnode_get_attrib(child, "category");
488 type = purple_xmlnode_get_attrib(child, "type");
489 if(purple_strequal(category, "pubsub") && purple_strequal(type, "pep")) {
490 PurpleConnection *gc = js->gc;
491 js->pep = TRUE;
492 purple_connection_set_flags(gc,
493 purple_connection_get_flags(gc)
494 | PURPLE_CONNECTION_FLAG_SUPPORT_MOODS
495 | PURPLE_CONNECTION_FLAG_SUPPORT_MOOD_MESSAGES);
497 if (!purple_strequal(category, "server"))
498 continue;
499 if (!purple_strequal(type, "im"))
500 continue;
502 name = purple_xmlnode_get_attrib(child, "name");
503 if (!name)
504 continue;
506 g_free(js->server_name);
507 js->server_name = g_strdup(name);
508 stun_ip = purple_network_get_stun_ip();
509 if (purple_strequal(name, "Google Talk")) {
510 purple_debug_info("jabber", "Google Talk!\n");
511 js->googletalk = TRUE;
513 /* autodiscover stun and relays */
514 if (!stun_ip || !*stun_ip) {
515 jabber_google_send_jingle_info(js);
517 } else if (!stun_ip || !*stun_ip) {
519 GResolver *resolver = g_resolver_get_default();
520 g_resolver_lookup_service_async(resolver,
521 "stun",
522 "udp",
523 js->user->domain,
524 NULL,
525 jabber_disco_stun_srv_resolve_cb,
526 js);
527 g_object_unref(resolver);
528 /* TODO: add TURN support later... */
532 for (child = purple_xmlnode_get_child(query, "feature"); child;
533 child = purple_xmlnode_get_next_twin(child)) {
534 const char *var;
535 var = purple_xmlnode_get_attrib(child, "var");
536 if (!var)
537 continue;
539 if (purple_strequal(NS_GOOGLE_MAIL_NOTIFY, var)) {
540 js->server_caps |= JABBER_CAP_GMAIL_NOTIFY;
541 jabber_gmail_init(js);
542 } else if (purple_strequal(NS_GOOGLE_ROSTER, var)) {
543 js->server_caps |= JABBER_CAP_GOOGLE_ROSTER;
544 } else if (purple_strequal("http://jabber.org/protocol/commands", var)) {
545 js->server_caps |= JABBER_CAP_ADHOC;
546 } else if (purple_strequal(NS_SIMPLE_BLOCKING, var)) {
547 js->server_caps |= JABBER_CAP_BLOCKING;
551 jabber_disco_finish_server_info_result_cb(js);
554 static void
555 jabber_disco_server_items_result_cb(JabberStream *js, const char *from,
556 JabberIqType type, const char *id,
557 PurpleXmlNode *packet, gpointer data)
559 PurpleXmlNode *query, *child;
561 if (!from || !purple_strequal(from, js->user->domain))
562 return;
564 if (type == JABBER_IQ_ERROR)
565 return;
567 g_list_free_full(js->chat_servers, g_free);
568 js->chat_servers = NULL;
570 query = purple_xmlnode_get_child(packet, "query");
572 for(child = purple_xmlnode_get_child(query, "item"); child;
573 child = purple_xmlnode_get_next_twin(child)) {
574 JabberIq *iq;
575 const char *jid;
577 if(!(jid = purple_xmlnode_get_attrib(child, "jid")))
578 continue;
580 /* we don't actually care about the specific nodes,
581 * so we won't query them */
582 if(purple_xmlnode_get_attrib(child, "node") != NULL)
583 continue;
585 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
586 purple_xmlnode_set_attrib(iq->node, "to", jid);
587 jabber_iq_set_callback(iq, jabber_disco_info_cb, NULL);
588 jabber_iq_send(iq);
592 void jabber_disco_items_server(JabberStream *js)
594 JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_ITEMS);
596 purple_xmlnode_set_attrib(iq->node, "to", js->user->domain);
598 jabber_iq_set_callback(iq, jabber_disco_server_items_result_cb, NULL);
599 jabber_iq_send(iq);
601 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
602 purple_xmlnode_set_attrib(iq->node, "to", js->user->domain);
603 jabber_iq_set_callback(iq, jabber_disco_server_info_result_cb, NULL);
604 jabber_iq_send(iq);
607 void jabber_disco_info_do(JabberStream *js, const char *who, JabberDiscoInfoCallback *callback, gpointer data)
609 JabberID *jid;
610 JabberBuddy *jb;
611 JabberBuddyResource *jbr = NULL;
612 struct _jabber_disco_info_cb_data *jdicd;
613 JabberIq *iq;
615 if((jid = jabber_id_new(who))) {
616 if(jid->resource && (jb = jabber_buddy_find(js, who, TRUE)))
617 jbr = jabber_buddy_find_resource(jb, jid->resource);
618 jabber_id_free(jid);
621 if(jbr && jbr->capabilities & JABBER_CAP_RETRIEVED) {
622 callback(js, who, jbr->capabilities, data);
623 return;
626 jdicd = g_new0(struct _jabber_disco_info_cb_data, 1);
627 jdicd->data = data;
628 jdicd->callback = callback;
630 iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
631 purple_xmlnode_set_attrib(iq->node, "to", who);
633 jabber_iq_set_callback(iq, jabber_disco_info_cb, jdicd);
634 jabber_iq_send(iq);