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
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
30 #include "adhoccommands.h"
33 #include "google/google.h"
34 #include "google/gmail.h"
35 #include "google/jingleinfo.h"
38 #include "jingle/jingle.h"
42 #include "useravatar.h"
44 struct _jabber_disco_info_cb_data
{
46 JabberDiscoInfoCallback
*callback
;
49 struct _jabber_disco_items_cb_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); \
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",
68 if (from
&& purple_strequal(from
, sh
->jid
) && query
!= NULL
) {
69 PurpleXmlNode
*sh_node
= purple_xmlnode_get_child(query
, "streamhost");
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"));
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
);
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
;
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
);
120 purple_xmlnode_set_attrib(iq
->node
, "to", from
);
121 query
= purple_xmlnode_get_child(iq
->node
, "query");
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
);
134 purple_xmlnode_set_attrib(identity
, "xml:lang", ident
->lang
);
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);
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
);
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
);
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"));
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
);
213 purple_xmlnode_set_attrib(iq
->node
, "to", from
);
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
;
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
);
242 capabilities
= jbr
->capabilities
;
244 for(child
= query
->child
; child
; child
= child
->next
) {
245 if(child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
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
)
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")) {
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 */
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
,
274 purple_xmlnode_set_attrib(iq
->node
, "to", sh
->jid
);
275 jabber_iq_set_callback(iq
, jabber_disco_bytestream_server_cb
, sh
);
279 } else if(purple_strequal(child
->name
, "feature")) {
280 const char *var
= purple_xmlnode_get_attrib(child
, "var");
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
;
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 */
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
);
330 capabilities
= jbr
->capabilities
;
332 if (jdicd
&& jdicd
->callback
)
333 jdicd
->callback(js
, from
, capabilities
, jdicd
->data
);
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
,
348 PurpleXmlNode
*iq_query
= purple_xmlnode_get_child(iq
->node
, "query");
349 const char *node
= purple_xmlnode_get_attrib(query
, "node");
351 purple_xmlnode_set_attrib(iq_query
,"node",node
);
353 jabber_iq_set_id(iq
, id
);
356 purple_xmlnode_set_attrib(iq
->node
, "to", from
);
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
371 jabber_vcard_fetch_mine(js
);
374 jabber_avatar_fetch_mine(js
);
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
);
393 JabberBytestreamsStreamhost
*sh
;
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
]))
403 /* We used to allow specifying a port directly here; get rid of it */
404 if((tmp
= strchr(ft_proxy_list
[i
], ':')))
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
);
417 g_strfreev(ft_proxy_list
);
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
;
429 services
= g_resolver_lookup_service_finish(G_RESOLVER(sender
),
433 purple_debug_info("jabber", "Failed to look up a STUN record : %s\n", error
->message
);
440 results
= g_list_length(services
);
442 purple_debug_info("jabber", "got %d SRV responses for STUN.\n", results
);
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
);
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
);
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
);
477 query
= purple_xmlnode_get_child(packet
, "query");
480 jabber_disco_finish_server_info_result_cb(js
);
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
;
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"))
499 if (!purple_strequal(type
, "im"))
502 name
= purple_xmlnode_get_attrib(child
, "name");
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
,
525 jabber_disco_stun_srv_resolve_cb
,
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
)) {
535 var
= purple_xmlnode_get_attrib(child
, "var");
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
);
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
))
564 if (type
== JABBER_IQ_ERROR
)
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
)) {
577 if(!(jid
= purple_xmlnode_get_attrib(child
, "jid")))
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
)
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
);
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
);
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
);
607 void jabber_disco_info_do(JabberStream
*js
, const char *who
, JabberDiscoInfoCallback
*callback
, gpointer data
)
611 JabberBuddyResource
*jbr
= NULL
;
612 struct _jabber_disco_info_cb_data
*jdicd
;
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
);
621 if(jbr
&& jbr
->capabilities
& JABBER_CAP_RETRIEVED
) {
622 callback(js
, who
, jbr
->capabilities
, data
);
626 jdicd
= g_new0(struct _jabber_disco_info_cb_data
, 1);
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
);