2 * purple - Jabber Protocol Plugin
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
31 #include "google/gmail.h"
32 #include "google/google.h"
33 #include "google/jingleinfo.h"
34 #include "google/google_session.h"
36 #include "jingle/jingle.h"
41 #include "adhoccommands.h"
45 static GHashTable
*iq_handlers
= NULL
;
46 static GHashTable
*signal_iq_handlers
= NULL
;
48 struct _JabberIqCallbackData
{
49 JabberIqCallback
*callback
;
54 void jabber_iq_callbackdata_free(JabberIqCallbackData
*jcd
)
56 jabber_id_free(jcd
->to
);
60 JabberIq
*jabber_iq_new(JabberStream
*js
, JabberIqType type
)
64 iq
= g_new0(JabberIq
, 1);
68 iq
->node
= purple_xmlnode_new("iq");
71 purple_xmlnode_set_attrib(iq
->node
, "type", "set");
74 purple_xmlnode_set_attrib(iq
->node
, "type", "get");
77 purple_xmlnode_set_attrib(iq
->node
, "type", "error");
79 case JABBER_IQ_RESULT
:
80 purple_xmlnode_set_attrib(iq
->node
, "type", "result");
83 /* this shouldn't ever happen */
89 if(type
== JABBER_IQ_GET
|| type
== JABBER_IQ_SET
) {
90 iq
->id
= jabber_get_next_id(js
);
91 purple_xmlnode_set_attrib(iq
->node
, "id", iq
->id
);
97 JabberIq
*jabber_iq_new_query(JabberStream
*js
, JabberIqType type
,
100 JabberIq
*iq
= jabber_iq_new(js
, type
);
101 PurpleXmlNode
*query
;
103 query
= purple_xmlnode_new_child(iq
->node
, "query");
104 purple_xmlnode_set_namespace(query
, xmlns
);
110 jabber_iq_set_callback(JabberIq
*iq
, JabberIqCallback
*callback
, gpointer data
)
112 iq
->callback
= callback
;
113 iq
->callback_data
= data
;
116 void jabber_iq_set_id(JabberIq
*iq
, const char *id
)
121 purple_xmlnode_set_attrib(iq
->node
, "id", id
);
122 iq
->id
= g_strdup(id
);
124 purple_xmlnode_remove_attrib(iq
->node
, "id");
129 void jabber_iq_send(JabberIq
*iq
)
131 JabberIqCallbackData
*jcd
;
132 g_return_if_fail(iq
!= NULL
);
134 jabber_send(iq
->js
, iq
->node
);
136 if(iq
->id
&& iq
->callback
) {
137 jcd
= g_new0(JabberIqCallbackData
, 1);
138 jcd
->callback
= iq
->callback
;
139 jcd
->data
= iq
->callback_data
;
140 jcd
->to
= jabber_id_new(purple_xmlnode_get_attrib(iq
->node
, "to"));
142 g_hash_table_insert(iq
->js
->iq_callbacks
, g_strdup(iq
->id
), jcd
);
148 void jabber_iq_free(JabberIq
*iq
)
150 g_return_if_fail(iq
!= NULL
);
153 purple_xmlnode_free(iq
->node
);
157 static void jabber_iq_last_parse(JabberStream
*js
, const char *from
,
158 JabberIqType type
, const char *id
,
159 PurpleXmlNode
*packet
)
162 PurpleXmlNode
*query
;
165 if(type
== JABBER_IQ_GET
) {
166 iq
= jabber_iq_new_query(js
, JABBER_IQ_RESULT
, NS_LAST_ACTIVITY
);
167 jabber_iq_set_id(iq
, id
);
169 purple_xmlnode_set_attrib(iq
->node
, "to", from
);
171 query
= purple_xmlnode_get_child(iq
->node
, "query");
173 idle_time
= g_strdup_printf("%ld", js
->idle
? time(NULL
) - js
->idle
: 0);
174 purple_xmlnode_set_attrib(query
, "seconds", idle_time
);
181 static void jabber_time_parse(JabberStream
*js
, const char *from
,
182 JabberIqType type
, const char *id
,
183 PurpleXmlNode
*child
)
187 if(type
== JABBER_IQ_GET
) {
188 PurpleXmlNode
*tzo
, *utc
;
189 GDateTime
*now
, *now_utc
;
192 iq
= jabber_iq_new(js
, JABBER_IQ_RESULT
);
193 jabber_iq_set_id(iq
, id
);
195 purple_xmlnode_set_attrib(iq
->node
, "to", from
);
197 child
= purple_xmlnode_new_child(iq
->node
, child
->name
);
198 purple_xmlnode_set_namespace(child
, NS_ENTITY_TIME
);
200 /* <tzo>-06:00</tzo> */
201 now
= g_date_time_new_now_local();
202 tz
= g_date_time_format(now
, "%:z");
203 tzo
= purple_xmlnode_new_child(child
, "tzo");
204 purple_xmlnode_insert_data(tzo
, tz
, -1);
207 /* <utc>2006-12-19T17:58:35Z</utc> */
208 now_utc
= g_date_time_to_utc(now
);
209 date
= g_date_time_format(now_utc
, "%FT%TZ");
210 utc
= purple_xmlnode_new_child(child
, "utc");
211 purple_xmlnode_insert_data(utc
, date
, -1);
214 g_date_time_unref(now
);
215 g_date_time_unref(now_utc
);
223 static void jabber_iq_version_parse(JabberStream
*js
, const char *from
,
224 JabberIqType type
, const char *id
,
225 PurpleXmlNode
*packet
)
228 PurpleXmlNode
*query
;
230 if(type
== JABBER_IQ_GET
) {
232 const char *ui_name
= NULL
, *ui_version
= NULL
;
234 iq
= jabber_iq_new_query(js
, JABBER_IQ_RESULT
, "jabber:iq:version");
236 purple_xmlnode_set_attrib(iq
->node
, "to", from
);
237 jabber_iq_set_id(iq
, id
);
239 query
= purple_xmlnode_get_child(iq
->node
, "query");
241 ui_info
= purple_core_get_ui_info();
243 if(NULL
!= ui_info
) {
244 ui_name
= g_hash_table_lookup(ui_info
, "name");
245 ui_version
= g_hash_table_lookup(ui_info
, "version");
248 if(NULL
!= ui_name
&& NULL
!= ui_version
) {
249 char *version_complete
= g_strdup_printf("%s (libpurple " VERSION
")", ui_version
);
250 purple_xmlnode_insert_data(purple_xmlnode_new_child(query
, "name"), ui_name
, -1);
251 purple_xmlnode_insert_data(purple_xmlnode_new_child(query
, "version"), version_complete
, -1);
252 g_free(version_complete
);
254 purple_xmlnode_insert_data(purple_xmlnode_new_child(query
, "name"), "libpurple", -1);
255 purple_xmlnode_insert_data(purple_xmlnode_new_child(query
, "version"), VERSION
, -1);
262 void jabber_iq_remove_callback_by_id(JabberStream
*js
, const char *id
)
264 g_hash_table_remove(js
->iq_callbacks
, id
);
268 * Verify that the 'from' attribute of an IQ reply is a valid match for
269 * a given IQ request. The expected behavior is outlined in section
270 * 8.1.2.1 of the XMPP CORE spec (RFC 6120). We consider the reply to
271 * be a valid match if any of the following is true:
272 * - Request 'to' matches reply 'from' (including the case where
274 * - Request 'to' was my JID (bare or full) and reply 'from' is empty.
275 * - Request 'to' was empty and reply 'from' is my JID. The spec says
276 * we should only allow bare JID, but we also allow full JID for
277 * compatibility with some servers.
278 * - Request 'to' was empty and reply 'from' is server JID. Not allowed by
279 * any spec, but for compatibility with some servers.
281 * These rules should allow valid IQ replies while preventing spoofed
284 * For more discussion see the "Spoofing of iq ids and misbehaving
285 * servers" email thread from January 2014 on the jdev and security
286 * mailing lists. Also see https://developer.pidgin.im/ticket/15879
288 * @return TRUE if this reply is valid for the given request.
290 static gboolean
does_reply_from_match_request_to(JabberStream
*js
, JabberID
*to
, JabberID
*from
)
292 if (jabber_id_equal(to
, from
)) {
293 /* Request 'to' matches reply 'from' */
297 if (!from
&& purple_strequal(to
->node
, js
->user
->node
)
298 && purple_strequal(to
->domain
, js
->user
->domain
)) {
299 /* Request 'to' was my JID (bare or full) and reply 'from' is empty */
303 if (!to
&& purple_strequal(from
->domain
, js
->user
->domain
)) {
304 /* Request 'to' is empty and reply 'from' domain matches our domain */
306 if (!from
->node
&& !from
->resource
) {
307 /* Reply 'from' is server bare JID */
311 if (purple_strequal(from
->node
, js
->user
->node
)
312 && (!from
->resource
|| purple_strequal(from
->resource
, js
->user
->resource
))) {
313 /* Reply 'from' is my full or bare JID */
321 void jabber_iq_parse(JabberStream
*js
, PurpleXmlNode
*packet
)
323 JabberIqCallbackData
*jcd
;
324 PurpleXmlNode
*child
, *error
, *x
;
326 const char *iq_type
, *id
, *from
;
327 JabberIqType type
= JABBER_IQ_NONE
;
328 gboolean signal_return
;
331 from
= purple_xmlnode_get_attrib(packet
, "from");
332 id
= purple_xmlnode_get_attrib(packet
, "id");
333 iq_type
= purple_xmlnode_get_attrib(packet
, "type");
336 * Ensure the 'from' attribute is valid. No point in handling a stanza
337 * of which we don't understand where it came from.
339 from_id
= jabber_id_new(from
);
341 if (from
&& !from_id
) {
342 purple_debug_error("jabber", "Received an iq with an invalid from: %s\n", from
);
347 * child will be either the first tag child or NULL if there is no child.
348 * Historically, we used just the 'query' subchild, but newer XEPs use
349 * differently named children. Grabbing the first child is (for the time
352 for (child
= packet
->child
; child
; child
= child
->next
) {
353 if (child
->type
== PURPLE_XMLNODE_TYPE_TAG
)
358 if (purple_strequal(iq_type
, "get"))
359 type
= JABBER_IQ_GET
;
360 else if (purple_strequal(iq_type
, "set"))
361 type
= JABBER_IQ_SET
;
362 else if (purple_strequal(iq_type
, "result"))
363 type
= JABBER_IQ_RESULT
;
364 else if (purple_strequal(iq_type
, "error"))
365 type
= JABBER_IQ_ERROR
;
368 if (type
== JABBER_IQ_NONE
) {
369 purple_debug_error("jabber", "IQ with invalid type ('%s') - ignoring.\n",
370 iq_type
? iq_type
: "(null)");
371 jabber_id_free(from_id
);
375 /* All IQs must have an ID, so send an error for a set/get that doesn't */
378 if(type
== JABBER_IQ_SET
|| type
== JABBER_IQ_GET
) {
379 JabberIq
*iq
= jabber_iq_new(js
, JABBER_IQ_ERROR
);
381 purple_xmlnode_free(iq
->node
);
382 iq
->node
= purple_xmlnode_copy(packet
);
384 purple_xmlnode_set_attrib(iq
->node
, "to", from
);
385 purple_xmlnode_remove_attrib(iq
->node
, "from");
388 purple_xmlnode_set_attrib(iq
->node
, "type", "error");
389 /* This id is clearly not useful, but we must put something there for a valid stanza */
390 iq
->id
= jabber_get_next_id(js
);
391 purple_xmlnode_set_attrib(iq
->node
, "id", iq
->id
);
392 error
= purple_xmlnode_new_child(iq
->node
, "error");
393 purple_xmlnode_set_attrib(error
, "type", "modify");
394 x
= purple_xmlnode_new_child(error
, "bad-request");
395 purple_xmlnode_set_namespace(x
, NS_XMPP_STANZAS
);
399 purple_debug_error("jabber", "IQ of type '%s' missing id - ignoring.\n",
402 jabber_id_free(from_id
);
406 signal_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_protocol(js
->gc
),
407 "jabber-receiving-iq", js
->gc
, iq_type
, id
, from
, packet
));
409 jabber_id_free(from_id
);
413 /* First, lets see if a special callback got registered */
414 if(type
== JABBER_IQ_RESULT
|| type
== JABBER_IQ_ERROR
) {
415 jcd
= g_hash_table_lookup(js
->iq_callbacks
, id
);
417 if (does_reply_from_match_request_to(js
, jcd
->to
, from_id
)) {
418 jcd
->callback(js
, from
, type
, id
, packet
, jcd
->data
);
419 jabber_iq_remove_callback_by_id(js
, id
);
420 jabber_id_free(from_id
);
426 expected_to
= jabber_id_get_full_jid(jcd
->to
);
428 expected_to
= jabber_id_get_bare_jid(js
->user
);
431 purple_debug_error("jabber", "Got a result iq with id %s from %s instead of expected %s!\n", id
, from
? from
: "(null)", expected_to
);
439 * Apparently not, so let's see if we have a pre-defined handler
440 * or if an outside plugin is interested.
442 if(child
&& (xmlns
= purple_xmlnode_get_namespace(child
))) {
443 char *key
= g_strdup_printf("%s %s", child
->name
, xmlns
);
444 JabberIqHandler
*jih
= g_hash_table_lookup(iq_handlers
, key
);
445 int signal_ref
= GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers
, key
));
448 if (signal_ref
> 0) {
449 signal_return
= GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_protocol(js
->gc
), "jabber-watched-iq",
450 js
->gc
, iq_type
, id
, from
, child
));
452 jabber_id_free(from_id
);
458 jih(js
, from
, type
, id
, child
);
459 jabber_id_free(from_id
);
464 purple_debug_misc("jabber", "Unhandled IQ with id %s\n", id
);
466 /* If we get here, send the default error reply mandated by XMPP-CORE */
467 if(type
== JABBER_IQ_SET
|| type
== JABBER_IQ_GET
) {
468 JabberIq
*iq
= jabber_iq_new(js
, JABBER_IQ_ERROR
);
470 purple_xmlnode_free(iq
->node
);
471 iq
->node
= purple_xmlnode_copy(packet
);
473 purple_xmlnode_set_attrib(iq
->node
, "to", from
);
474 purple_xmlnode_remove_attrib(iq
->node
, "from");
477 purple_xmlnode_set_attrib(iq
->node
, "type", "error");
478 error
= purple_xmlnode_new_child(iq
->node
, "error");
479 purple_xmlnode_set_attrib(error
, "type", "cancel");
480 purple_xmlnode_set_attrib(error
, "code", "501");
481 x
= purple_xmlnode_new_child(error
, "feature-not-implemented");
482 purple_xmlnode_set_namespace(x
, NS_XMPP_STANZAS
);
487 jabber_id_free(from_id
);
490 void jabber_iq_register_handler(const char *node
, const char *xmlns
, JabberIqHandler
*handlerfunc
)
493 * This is valid because nodes nor namespaces cannot have spaces in them
494 * (see http://www.w3.org/TR/2006/REC-xml-20060816/ and
495 * http://www.w3.org/TR/REC-xml-names/)
497 char *key
= g_strdup_printf("%s %s", node
, xmlns
);
498 g_hash_table_replace(iq_handlers
, key
, handlerfunc
);
501 void jabber_iq_signal_register(const gchar
*node
, const gchar
*xmlns
)
506 g_return_if_fail(node
!= NULL
&& *node
!= '\0');
507 g_return_if_fail(xmlns
!= NULL
&& *xmlns
!= '\0');
509 key
= g_strdup_printf("%s %s", node
, xmlns
);
510 ref
= GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers
, key
));
512 g_hash_table_insert(signal_iq_handlers
, key
, GINT_TO_POINTER(1));
514 g_hash_table_insert(signal_iq_handlers
, key
, GINT_TO_POINTER(ref
+ 1));
519 void jabber_iq_signal_unregister(const gchar
*node
, const gchar
*xmlns
)
524 g_return_if_fail(node
!= NULL
&& *node
!= '\0');
525 g_return_if_fail(xmlns
!= NULL
&& *xmlns
!= '\0');
527 key
= g_strdup_printf("%s %s", node
, xmlns
);
528 ref
= GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers
, key
));
531 g_hash_table_remove(signal_iq_handlers
, key
);
532 } else if (ref
> 1) {
533 g_hash_table_insert(signal_iq_handlers
, key
, GINT_TO_POINTER(ref
- 1));
539 void jabber_iq_init(void)
541 iq_handlers
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
542 signal_iq_handlers
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
544 jabber_iq_register_handler("jingle", JINGLE
, jingle_parse
);
545 jabber_iq_register_handler("mailbox", NS_GOOGLE_MAIL_NOTIFY
,
547 jabber_iq_register_handler("new-mail", NS_GOOGLE_MAIL_NOTIFY
,
549 jabber_iq_register_handler("ping", NS_PING
, jabber_ping_parse
);
550 jabber_iq_register_handler("query", NS_GOOGLE_JINGLE_INFO
,
551 jabber_google_handle_jingle_info
);
552 jabber_iq_register_handler("query", NS_BYTESTREAMS
,
553 jabber_bytestreams_parse
);
554 jabber_iq_register_handler("query", NS_DISCO_INFO
, jabber_disco_info_parse
);
555 jabber_iq_register_handler("query", NS_DISCO_ITEMS
, jabber_disco_items_parse
);
556 jabber_iq_register_handler("query", NS_LAST_ACTIVITY
, jabber_iq_last_parse
);
557 jabber_iq_register_handler("query", NS_OOB_IQ_DATA
, jabber_oob_parse
);
558 jabber_iq_register_handler("query", "jabber:iq:register",
559 jabber_register_parse
);
560 jabber_iq_register_handler("query", "jabber:iq:roster",
561 jabber_roster_parse
);
562 jabber_iq_register_handler("query", "jabber:iq:version",
563 jabber_iq_version_parse
);
565 jabber_iq_register_handler("session", NS_GOOGLE_SESSION
,
566 jabber_google_session_parse
);
568 jabber_iq_register_handler("block", NS_SIMPLE_BLOCKING
, jabber_blocklist_parse_push
);
569 jabber_iq_register_handler("unblock", NS_SIMPLE_BLOCKING
, jabber_blocklist_parse_push
);
570 jabber_iq_register_handler("time", NS_ENTITY_TIME
, jabber_time_parse
);
574 void jabber_iq_uninit(void)
576 g_hash_table_destroy(iq_handlers
);
577 g_hash_table_destroy(signal_iq_handlers
);
578 iq_handlers
= signal_iq_handlers
= NULL
;