rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / jabber / jabber.c
blob7755a7fda0b1f120b75008afb898b145f7dfbcda
1 /*
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
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
23 #include "internal.h"
25 #include "account.h"
26 #include "buddylist.h"
27 #include "core.h"
28 #include "cmds.h"
29 #include "connection.h"
30 #include "conversation.h"
31 #include "debug.h"
32 #include "http.h"
33 #include "message.h"
34 #include "notify.h"
35 #include "pluginpref.h"
36 #include "proxy.h"
37 #include "protocol.h"
38 #include "purpleaccountoption.h"
39 #include "request.h"
40 #include "server.h"
41 #include "status.h"
42 #include "util.h"
43 #include "version.h"
44 #include "xmlnode.h"
46 #include "auth.h"
47 #include "buddy.h"
48 #include "caps.h"
49 #include "chat.h"
50 #include "data.h"
51 #include "disco.h"
52 #include "google/google.h"
53 #include "google/google_p2p.h"
54 #include "google/google_roster.h"
55 #include "google/google_session.h"
56 #include "ibb.h"
57 #include "iq.h"
58 #include "jutil.h"
59 #include "message.h"
60 #include "parser.h"
61 #include "presence.h"
62 #include "jabber.h"
63 #include "roster.h"
64 #include "ping.h"
65 #include "si.h"
66 #include "usermood.h"
67 #include "xdata.h"
68 #include "pep.h"
69 #include "adhoccommands.h"
70 #include "xmpp.h"
71 #include "gtalk.h"
73 #include "jingle/jingle.h"
74 #include "jingle/content.h"
75 #include "jingle/iceudp.h"
76 #include "jingle/rawudp.h"
77 #include "jingle/rtp.h"
78 #include "jingle/session.h"
80 #define PING_TIMEOUT 60
81 /* Send a whitespace keepalive to the server if we haven't sent
82 * anything in the last 120 seconds
84 #define DEFAULT_INACTIVITY_TIME 120
86 GList *jabber_features = NULL;
87 GList *jabber_identities = NULL;
89 static PurpleProtocol *xmpp_protocol = NULL;
90 static PurpleProtocol *gtalk_protocol = NULL;
92 static GHashTable *jabber_cmds = NULL; /* PurpleProtocol * => GSList of ids */
94 static gint plugin_ref = 0;
96 static void jabber_unregister_account_cb(JabberStream *js);
98 static void jabber_stream_init(JabberStream *js)
100 char *open_stream;
102 g_free(js->stream_id);
103 js->stream_id = NULL;
105 open_stream = g_strdup_printf("<stream:stream to='%s' "
106 "xmlns='" NS_XMPP_CLIENT "' "
107 "xmlns:stream='" NS_XMPP_STREAMS "' "
108 "version='1.0'>",
109 js->user->domain);
110 /* setup the parser fresh for each stream */
111 jabber_parser_setup(js);
112 jabber_send_raw(js, open_stream, -1);
113 js->reinit = FALSE;
114 g_free(open_stream);
117 static void
118 jabber_session_initialized_cb(JabberStream *js, const char *from,
119 JabberIqType type, const char *id,
120 PurpleXmlNode *packet, gpointer data)
122 if (type == JABBER_IQ_RESULT) {
123 jabber_disco_items_server(js);
124 if(js->unregistration)
125 jabber_unregister_account_cb(js);
126 } else {
127 purple_connection_error(js->gc,
128 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
129 ("Error initializing session"));
133 static void jabber_session_init(JabberStream *js)
135 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
136 PurpleXmlNode *session;
138 jabber_iq_set_callback(iq, jabber_session_initialized_cb, NULL);
140 session = purple_xmlnode_new_child(iq->node, "session");
141 purple_xmlnode_set_namespace(session, NS_XMPP_SESSION);
143 jabber_iq_send(iq);
146 static void jabber_bind_result_cb(JabberStream *js, const char *from,
147 JabberIqType type, const char *id,
148 PurpleXmlNode *packet, gpointer data)
150 PurpleXmlNode *bind;
152 if (type == JABBER_IQ_RESULT &&
153 (bind = purple_xmlnode_get_child_with_namespace(packet, "bind", NS_XMPP_BIND))) {
154 PurpleXmlNode *jid;
155 char *full_jid;
156 if((jid = purple_xmlnode_get_child(bind, "jid")) && (full_jid = purple_xmlnode_get_data(jid))) {
157 jabber_id_free(js->user);
159 js->user = jabber_id_new(full_jid);
160 if (js->user == NULL) {
161 purple_connection_error(js->gc,
162 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
163 _("Invalid response from server"));
164 g_free(full_jid);
165 return;
168 js->user_jb = jabber_buddy_find(js, full_jid, TRUE);
169 js->user_jb->subscription |= JABBER_SUB_BOTH;
171 purple_connection_set_display_name(js->gc, full_jid);
173 g_free(full_jid);
175 } else {
176 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
177 char *msg = jabber_parse_error(js, packet, &reason);
178 purple_connection_error(js->gc, reason, msg);
179 g_free(msg);
181 return;
184 jabber_session_init(js);
187 static char *jabber_prep_resource(char *input) {
188 char hostname[256], /* current hostname */
189 *dot = NULL;
191 /* Empty resource == don't send any */
192 if (input == NULL || *input == '\0')
193 return NULL;
195 if (strstr(input, "__HOSTNAME__") == NULL)
196 return g_strdup(input);
198 /* Replace __HOSTNAME__ with hostname */
199 if (gethostname(hostname, sizeof(hostname) - 1)) {
200 purple_debug_warning("jabber", "gethostname: %s\n", g_strerror(errno));
201 /* according to glibc doc, the only time an error is returned
202 is if the hostname is longer than the buffer, in which case
203 glibc 2.2+ would still fill the buffer with partial
204 hostname, so maybe we want to detect that and use it
205 instead
207 g_strlcpy(hostname, "localhost", sizeof(hostname));
209 hostname[sizeof(hostname) - 1] = '\0';
211 /* We want only the short hostname, not the FQDN - this will prevent the
212 * resource string from being unreasonably long on systems which stuff the
213 * whole FQDN in the hostname */
214 if((dot = strchr(hostname, '.')))
215 *dot = '\0';
217 return purple_strreplace(input, "__HOSTNAME__", hostname);
220 static gboolean
221 jabber_process_starttls(JabberStream *js, PurpleXmlNode *packet)
223 PurpleAccount *account = NULL;
224 PurpleXmlNode *starttls = NULL;
226 /* It's a secure BOSH connection, just return FALSE and skip, without doing anything extra.
227 * XEP-0206 (XMPP Over BOSH): The client SHOULD ignore any Transport Layer Security (TLS)
228 * feature since BOSH channel encryption SHOULD be negotiated at the HTTP layer.
230 * Note: we are already receiving STARTTLS at this point from a SSL/TLS BOSH connection,
231 * so it is not necessary to check if purple_ssl_is_supported().
233 if (js->bosh && jabber_bosh_connection_is_ssl(js->bosh)) {
234 return FALSE;
237 /* Otherwise, it's a standard XMPP connection, or a HTTP (insecure) BOSH connection.
238 * We request STARTTLS for standard XMPP connections, but we do nothing for insecure
239 * BOSH connections, per XEP-0206. */
240 if(!js->bosh) {
241 jabber_send_raw(js,
242 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1);
243 return TRUE;
246 /* It's an insecure standard XMPP connection, or an insecure BOSH connection, let's
247 * ignore STARTTLS even it's required by the server to prevent disabling HTTP BOSH
248 * entirely (sysadmin is responsible to provide HTTPS-only BOSH if security is required),
249 * and emit errors if encryption is required by the user. */
250 starttls = purple_xmlnode_get_child(packet, "starttls");
251 if(!js->bosh && purple_xmlnode_get_child(starttls, "required")) {
252 purple_connection_error(js->gc,
253 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
254 _("Server requires TLS/SSL, but no TLS/SSL support was found."));
255 return TRUE;
258 account = purple_connection_get_account(js->gc);
259 if (purple_strequal("require_tls", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
260 purple_connection_error(js->gc,
261 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
262 _("You require encryption, but no TLS/SSL support was found."));
263 return TRUE;
266 return FALSE;
269 void jabber_stream_features_parse(JabberStream *js, PurpleXmlNode *packet)
271 PurpleAccount *account = purple_connection_get_account(js->gc);
272 const char *connection_security =
273 purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS);
275 if (purple_xmlnode_get_child(packet, "starttls")) {
276 if (jabber_process_starttls(js, packet)) {
277 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
278 return;
280 } else if (purple_strequal(connection_security, "require_tls") && !jabber_stream_is_ssl(js)) {
281 purple_connection_error(js->gc,
282 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
283 _("You require encryption, but it is not available on this server."));
284 return;
287 if(js->registration) {
288 jabber_register_start(js);
289 } else if(purple_xmlnode_get_child(packet, "mechanisms")) {
290 jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
291 jabber_auth_start(js, packet);
292 } else if(purple_xmlnode_get_child(packet, "bind")) {
293 PurpleXmlNode *bind, *resource;
294 char *requested_resource;
295 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
296 bind = purple_xmlnode_new_child(iq->node, "bind");
297 purple_xmlnode_set_namespace(bind, NS_XMPP_BIND);
298 requested_resource = jabber_prep_resource(js->user->resource);
300 if (requested_resource != NULL) {
301 resource = purple_xmlnode_new_child(bind, "resource");
302 purple_xmlnode_insert_data(resource, requested_resource, -1);
303 g_free(requested_resource);
306 jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL);
308 jabber_iq_send(iq);
309 } else if (purple_xmlnode_get_child_with_namespace(packet, "ver", NS_ROSTER_VERSIONING)) {
310 js->server_caps |= JABBER_CAP_ROSTER_VERSIONING;
311 } else /* if(purple_xmlnode_get_child_with_namespace(packet, "auth")) */ {
312 /* If we get an empty stream:features packet, or we explicitly get
313 * an auth feature with namespace http://jabber.org/features/iq-auth
314 * we should revert back to iq:auth authentication, even though we're
315 * connecting to an XMPP server. */
316 jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
317 jabber_auth_start_old(js);
321 static void jabber_stream_handle_error(JabberStream *js, PurpleXmlNode *packet)
323 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
324 char *msg = jabber_parse_error(js, packet, &reason);
326 purple_connection_error(js->gc, reason, msg);
328 g_free(msg);
331 static void tls_init(JabberStream *js);
333 void jabber_process_packet(JabberStream *js, PurpleXmlNode **packet)
335 const char *name;
336 const char *xmlns;
338 purple_signal_emit(purple_connection_get_protocol(js->gc), "jabber-receiving-xmlnode", js->gc, packet);
340 /* if the signal leaves us with a null packet, we're done */
341 if(NULL == *packet)
342 return;
344 name = (*packet)->name;
345 xmlns = purple_xmlnode_get_namespace(*packet);
347 if(purple_strequal((*packet)->name, "iq")) {
348 jabber_iq_parse(js, *packet);
349 } else if(purple_strequal((*packet)->name, "presence")) {
350 jabber_presence_parse(js, *packet);
351 } else if(purple_strequal((*packet)->name, "message")) {
352 jabber_message_parse(js, *packet);
353 } else if (purple_strequal(xmlns, NS_XMPP_STREAMS)) {
354 if (purple_strequal(name, "features"))
355 jabber_stream_features_parse(js, *packet);
356 else if (purple_strequal(name, "error"))
357 jabber_stream_handle_error(js, *packet);
358 } else if (purple_strequal(xmlns, NS_XMPP_SASL)) {
359 if (js->state != JABBER_STREAM_AUTHENTICATING)
360 purple_debug_warning("jabber", "Ignoring spurious SASL stanza %s\n", name);
361 else {
362 if (purple_strequal(name, "challenge"))
363 jabber_auth_handle_challenge(js, *packet);
364 else if (purple_strequal(name, "success"))
365 jabber_auth_handle_success(js, *packet);
366 else if (purple_strequal(name, "failure"))
367 jabber_auth_handle_failure(js, *packet);
369 } else if (purple_strequal(xmlns, NS_XMPP_TLS)) {
370 if (js->state != JABBER_STREAM_INITIALIZING_ENCRYPTION || js->gsc)
371 purple_debug_warning("jabber", "Ignoring spurious %s\n", name);
372 else {
373 if (purple_strequal(name, "proceed"))
374 tls_init(js);
375 /* TODO: Handle <failure/>, I guess? */
377 } else {
378 purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet)->name);
382 static int jabber_do_send(JabberStream *js, const char *data, int len)
384 int ret;
386 if (js->gsc)
387 ret = purple_ssl_write(js->gsc, data, len);
388 else
389 ret = write(js->fd, data, len);
391 return ret;
394 static void jabber_send_cb(gpointer data, gint source, PurpleInputCondition cond)
396 JabberStream *js = data;
397 const gchar *output = NULL;
398 int ret, writelen;
400 writelen = purple_circular_buffer_get_max_read(js->write_buffer);
401 output = purple_circular_buffer_get_output(js->write_buffer);
403 if (writelen == 0) {
404 purple_input_remove(js->writeh);
405 js->writeh = 0;
406 return;
409 ret = jabber_do_send(js, output, writelen);
411 if (ret < 0 && errno == EAGAIN)
412 return;
413 else if (ret <= 0) {
414 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
415 g_strerror(errno));
416 purple_connection_error(js->gc,
417 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
418 g_free(tmp);
419 return;
422 purple_circular_buffer_mark_read(js->write_buffer, ret);
425 static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len)
427 int ret;
428 gboolean success = TRUE;
430 g_return_val_if_fail(len > 0, FALSE);
432 if (js->state == JABBER_STREAM_CONNECTED)
433 jabber_stream_restart_inactivity_timer(js);
435 if (js->writeh == 0)
436 ret = jabber_do_send(js, data, len);
437 else {
438 ret = -1;
439 errno = EAGAIN;
442 if (ret < 0 && errno != EAGAIN) {
443 PurpleAccount *account = purple_connection_get_account(js->gc);
445 * The server may have closed the socket (on a stream error), so if
446 * we're disconnecting, don't generate (possibly another) error that
447 * (for some UIs) would mask the first.
449 if (!purple_account_is_disconnecting(account)) {
450 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
451 g_strerror(errno));
452 purple_connection_error(js->gc,
453 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
454 g_free(tmp);
457 success = FALSE;
458 } else if (ret < len) {
459 if (ret < 0)
460 ret = 0;
461 if (js->writeh == 0)
462 js->writeh = purple_input_add(
463 js->gsc ? js->gsc->fd : js->fd,
464 PURPLE_INPUT_WRITE, jabber_send_cb, js);
465 purple_circular_buffer_append(js->write_buffer,
466 data + ret, len - ret);
469 return success;
472 void jabber_send_raw(JabberStream *js, const char *data, int len)
474 PurpleConnection *gc;
475 PurpleAccount *account;
477 gc = js->gc;
478 account = purple_connection_get_account(gc);
480 g_return_if_fail(data != NULL);
482 /* because printing a tab to debug every minute gets old */
483 if (!purple_strequal(data, "\t")) {
484 const char *username;
485 char *text = NULL, *last_part = NULL, *tag_start = NULL;
487 /* Because debug logs with plaintext passwords make me sad */
488 if (!purple_debug_is_unsafe() && js->state != JABBER_STREAM_CONNECTED &&
489 /* Either <auth> or <query><password>... */
490 (((tag_start = strstr(data, "<auth ")) &&
491 strstr(data, "xmlns='" NS_XMPP_SASL "'")) ||
492 ((tag_start = strstr(data, "<query ")) &&
493 strstr(data, "xmlns='jabber:iq:auth'>") &&
494 (tag_start = strstr(tag_start, "<password>"))))) {
495 char *data_start, *tag_end = strchr(tag_start, '>');
496 text = g_strdup(data);
498 /* Better to print out some wacky debugging than crash
499 * due to a plugin sending bad xml */
500 if (tag_end == NULL)
501 tag_end = tag_start;
503 data_start = text + (tag_end - data) + 1;
505 last_part = strchr(data_start, '<');
506 *data_start = '\0';
509 username = purple_connection_get_display_name(gc);
510 if (!username)
511 username = purple_account_get_username(account);
513 purple_debug_misc("jabber", "Sending%s (%s): %s%s%s\n",
514 jabber_stream_is_ssl(js) ? " (ssl)" : "", username,
515 text ? text : data,
516 last_part ? "password removed" : "",
517 last_part ? last_part : "");
519 g_free(text);
522 purple_signal_emit(purple_connection_get_protocol(gc), "jabber-sending-text", gc, &data);
523 if (data == NULL)
524 return;
526 if (len == -1)
527 len = strlen(data);
529 /* If we've got a security layer, we need to encode the data,
530 * splitting it on the maximum buffer length negotiated */
531 #ifdef HAVE_CYRUS_SASL
532 if (js->sasl_maxbuf>0) {
533 int pos = 0;
535 if (!js->gsc && js->fd<0)
536 g_return_if_reached();
538 while (pos < len) {
539 int towrite;
540 const char *out;
541 unsigned olen;
542 int rc;
544 towrite = MIN((len - pos), js->sasl_maxbuf);
546 rc = sasl_encode(js->sasl, &data[pos], towrite,
547 &out, &olen);
548 if (rc != SASL_OK) {
549 gchar *error =
550 g_strdup_printf(_("SASL error: %s"),
551 sasl_errdetail(js->sasl));
552 purple_debug_error("jabber",
553 "sasl_encode error %d: %s\n", rc,
554 sasl_errdetail(js->sasl));
555 purple_connection_error(gc,
556 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
557 error);
558 g_free(error);
559 return;
561 pos += towrite;
563 /* do_jabber_send_raw returns FALSE when it throws a
564 * connection error.
566 if (!do_jabber_send_raw(js, out, olen))
567 break;
569 return;
571 #endif
573 if (js->bosh)
574 jabber_bosh_connection_send(js->bosh, data);
575 else
576 do_jabber_send_raw(js, data, len);
579 int jabber_protocol_send_raw(PurpleConnection *gc, const char *buf, int len)
581 JabberStream *js = purple_connection_get_protocol_data(gc);
583 g_return_val_if_fail(js != NULL, -1);
584 /* TODO: It's probably worthwhile to restrict this to when the account
585 * state is CONNECTED, but I can /almost/ envision reasons for wanting
586 * to do things during the connection process.
589 jabber_send_raw(js, buf, len);
590 return (len < 0 ? (int)strlen(buf) : len);
593 void jabber_send_signal_cb(PurpleConnection *pc, PurpleXmlNode **packet,
594 gpointer unused)
596 JabberStream *js;
597 char *txt;
598 int len;
600 if (NULL == packet)
601 return;
603 PURPLE_ASSERT_CONNECTION_IS_VALID(pc);
605 js = purple_connection_get_protocol_data(pc);
607 if (NULL == js)
608 return;
610 if (js->bosh)
611 if (purple_strequal((*packet)->name, "message") ||
612 purple_strequal((*packet)->name, "iq") ||
613 purple_strequal((*packet)->name, "presence"))
614 purple_xmlnode_set_namespace(*packet, NS_XMPP_CLIENT);
615 txt = purple_xmlnode_to_str(*packet, &len);
616 jabber_send_raw(js, txt, len);
617 g_free(txt);
620 void jabber_send(JabberStream *js, PurpleXmlNode *packet)
622 purple_signal_emit(purple_connection_get_protocol(js->gc), "jabber-sending-xmlnode", js->gc, &packet);
625 static gboolean jabber_keepalive_timeout(PurpleConnection *gc)
627 JabberStream *js = purple_connection_get_protocol_data(gc);
628 purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
629 _("Ping timed out"));
630 js->keepalive_timeout = 0;
631 return FALSE;
634 void jabber_keepalive(PurpleConnection *gc)
636 JabberStream *js = purple_connection_get_protocol_data(gc);
638 if (js->keepalive_timeout == 0) {
639 jabber_keepalive_ping(js);
640 js->keepalive_timeout = g_timeout_add_seconds(120,
641 (GSourceFunc)(jabber_keepalive_timeout), gc);
645 static int jabber_get_keepalive_interval(void)
647 return PING_TIMEOUT;
650 static void
651 jabber_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc,
652 PurpleInputCondition cond)
654 PurpleConnection *gc = data;
655 JabberStream *js = purple_connection_get_protocol_data(gc);
656 int len;
657 static char buf[4096];
659 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
661 while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
662 purple_connection_update_last_received(gc);
663 buf[len] = '\0';
664 purple_debug_misc("jabber", "Recv (ssl)(%d): %s", len, buf);
665 jabber_parser_process(js, buf, len);
666 if(js->reinit)
667 jabber_stream_init(js);
670 if(len < 0 && errno == EAGAIN)
671 return;
672 else {
673 gchar *tmp;
674 if (len == 0)
675 tmp = g_strdup(_("Server closed the connection"));
676 else
677 tmp = g_strdup_printf(_("Lost connection with server: %s"),
678 g_strerror(errno));
679 purple_connection_error(js->gc,
680 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
681 g_free(tmp);
685 static void
686 jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition)
688 PurpleConnection *gc = data;
689 JabberStream *js = purple_connection_get_protocol_data(gc);
690 int len;
691 static char buf[4096];
693 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
695 if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
696 purple_connection_update_last_received(gc);
697 #ifdef HAVE_CYRUS_SASL
698 if (js->sasl_maxbuf > 0) {
699 const char *out;
700 unsigned int olen;
701 int rc;
703 rc = sasl_decode(js->sasl, buf, len, &out, &olen);
704 if (rc != SASL_OK) {
705 gchar *error =
706 g_strdup_printf(_("SASL error: %s"),
707 sasl_errdetail(js->sasl));
708 purple_debug_error("jabber",
709 "sasl_decode_error %d: %s\n", rc,
710 sasl_errdetail(js->sasl));
711 purple_connection_error(gc,
712 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
713 error);
714 } else if (olen > 0) {
715 purple_debug_info("jabber", "RecvSASL (%u): %s\n", olen, out);
716 jabber_parser_process(js, out, olen);
717 if (js->reinit)
718 jabber_stream_init(js);
720 return;
722 #endif
723 buf[len] = '\0';
724 purple_debug_misc("jabber", "Recv (%d): %s", len, buf);
725 jabber_parser_process(js, buf, len);
726 if(js->reinit)
727 jabber_stream_init(js);
728 } else if(len < 0 && errno == EAGAIN) {
729 return;
730 } else {
731 gchar *tmp;
732 if (len == 0)
733 tmp = g_strdup(_("Server closed the connection"));
734 else
735 tmp = g_strdup_printf(_("Lost connection with server: %s"),
736 g_strerror(errno));
737 purple_connection_error(js->gc,
738 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
739 g_free(tmp);
743 static void
744 jabber_login_callback_ssl(gpointer data, PurpleSslConnection *gsc,
745 PurpleInputCondition cond)
747 PurpleConnection *gc = data;
748 JabberStream *js;
750 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
752 js = purple_connection_get_protocol_data(gc);
754 if(js->state == JABBER_STREAM_CONNECTING)
755 jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
756 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
757 purple_ssl_input_add(gsc, jabber_recv_cb_ssl, gc);
759 /* Tell the app that we're doing encryption */
760 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
763 static void
764 txt_resolved_cb(GObject *sender, GAsyncResult *result, gpointer data)
766 GError *error = NULL;
767 GList *records = NULL, *l = NULL;
768 JabberStream *js = data;
769 gboolean found = FALSE;
771 records = g_resolver_lookup_records_finish(G_RESOLVER(sender),
772 result, &error);
773 if(error) {
774 purple_debug_warning("jabber", "Unable to find alternative XMPP connection "
775 "methods after failing to connect directly. : %s\n",
776 error->message);
778 purple_connection_error(js->gc,
779 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
780 _("Unable to connect"));
782 g_error_free(error);
784 return;
787 for(l = records; l; l = l->next) {
788 GVariantIter *iter = NULL;
789 gchar *str = NULL;
791 g_variant_get((GVariant *)l->data, "(as)", &iter);
792 while(g_variant_iter_loop(iter, "s", &str)) {
793 gchar **token = g_strsplit(str, "=", 2);
795 if(!g_ascii_strcasecmp(token[0], "_xmpp-client-xbosh")) {
796 purple_debug_info("jabber","Found alternative connection method using %s at %s.\n", token[0], token[1]);
798 js->bosh = jabber_bosh_connection_new(js, token[1]);
800 g_strfreev(token);
802 break;
805 g_strfreev(token);
808 g_variant_iter_free(iter);
811 g_list_free_full(records, (GDestroyNotify)g_variant_unref);
813 if (js->bosh)
814 found = TRUE;
816 if (!found) {
817 purple_debug_warning("jabber", "Unable to find alternative XMPP connection "
818 "methods after failing to connect directly.\n");
819 purple_connection_error(js->gc,
820 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
821 _("Unable to connect"));
822 return;
826 static void
827 jabber_login_callback(gpointer data, gint source, const gchar *error)
829 PurpleConnection *gc = data;
830 JabberStream *js = purple_connection_get_protocol_data(gc);
832 if (source < 0) {
833 GResolver *resolver = g_resolver_get_default();
834 gchar *name = g_strdup_printf("_xmppconnect.%s", js->user->domain);
836 purple_debug_info("jabber", "Couldn't connect directly to %s. Trying to find alternative connection methods, like BOSH.\n", js->user->domain);
838 g_resolver_lookup_records_async(resolver,
839 name,
840 G_RESOLVER_RECORD_TXT,
841 js->cancellable,
842 txt_resolved_cb,
843 js);
844 g_free(name);
845 g_object_unref(resolver);
847 return;
850 js->fd = source;
852 if(js->state == JABBER_STREAM_CONNECTING)
853 jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
855 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
856 js->inpa = purple_input_add(js->fd, PURPLE_INPUT_READ, jabber_recv_cb, gc);
859 static void
860 jabber_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
861 gpointer data)
863 PurpleConnection *gc = data;
864 JabberStream *js;
866 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
868 js = purple_connection_get_protocol_data(gc);
869 js->gsc = NULL;
871 purple_connection_ssl_error (gc, error);
874 static void tls_init(JabberStream *js)
876 purple_input_remove(js->inpa);
877 js->inpa = 0;
878 js->gsc = purple_ssl_connect_with_host_fd(purple_connection_get_account(js->gc), js->fd,
879 jabber_login_callback_ssl, jabber_ssl_connect_failure, js->certificate_CN, js->gc);
880 /* The fd is no longer our concern */
881 js->fd = -1;
884 static gboolean jabber_login_connect(JabberStream *js, const char *domain, const char *host, int port,
885 gboolean fatal_failure)
887 /* host should be used in preference to domain to
888 * allow SASL authentication to work with FQDN of the server,
889 * but we use domain as fallback for when users enter IP address
890 * in connect server */
891 g_free(js->serverFQDN);
892 if (purple_ip_address_is_valid(host))
893 js->serverFQDN = g_strdup(domain);
894 else
895 js->serverFQDN = g_strdup(host);
897 if (purple_proxy_connect(js->gc, purple_connection_get_account(js->gc),
898 host, port, jabber_login_callback, js->gc) == NULL) {
899 if (fatal_failure) {
900 purple_connection_error(js->gc,
901 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
902 _("Unable to connect"));
905 return FALSE;
908 return TRUE;
911 static void
912 srv_resolved_cb(GObject *sender, GAsyncResult *result, gpointer data)
914 GError *error = NULL;
915 GList *targets = NULL, *l = NULL;
916 JabberStream *js = data;
918 targets = g_resolver_lookup_service_finish(G_RESOLVER(sender),
919 result, &error);
920 if(error) {
921 purple_debug_warning("jabber",
922 "SRV lookup failed, proceeding with normal connection : %s",
923 error->message);
925 g_error_free(error);
927 jabber_login_connect(js, js->user->domain, js->user->domain,
928 purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222),
929 TRUE);
931 } else {
932 for(l = targets; l; l = l->next) {
933 GSrvTarget *target = (GSrvTarget *)l->data;
934 const gchar *hostname = g_srv_target_get_hostname(target);
935 guint port = g_srv_target_get_port(target);
937 if(jabber_login_connect(js, hostname, hostname, port, FALSE)) {
938 g_resolver_free_targets(targets);
940 return;
944 g_resolver_free_targets(targets);
946 jabber_login_connect(js, js->user->domain, js->user->domain,
947 purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222),
948 TRUE);
952 static JabberStream *
953 jabber_stream_new(PurpleAccount *account)
955 PurpleConnection *gc = purple_account_get_connection(account);
956 JabberStream *js;
957 PurplePresence *presence;
958 gchar *user;
959 gchar *slash;
961 js = g_new0(JabberStream, 1);
962 purple_connection_set_protocol_data(gc, js);
963 js->gc = gc;
964 js->fd = -1;
965 js->http_conns = purple_http_connection_set_new();
967 /* we might want to expose this at some point */
968 js->cancellable = g_cancellable_new();
970 user = g_strdup(purple_account_get_username(account));
971 /* jabber_id_new doesn't accept "user@domain/" as valid */
972 slash = strchr(user, '/');
973 if (slash && *(slash + 1) == '\0')
974 *slash = '\0';
975 js->user = jabber_id_new(user);
977 if (!js->user) {
978 purple_connection_error(gc,
979 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
980 _("Invalid XMPP ID"));
981 g_free(user);
982 /* Destroying the connection will free the JabberStream */
983 return NULL;
986 if (!js->user->node || *(js->user->node) == '\0') {
987 purple_connection_error(gc,
988 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
989 _("Invalid XMPP ID. Username portion must be set."));
990 g_free(user);
991 /* Destroying the connection will free the JabberStream */
992 return NULL;
995 if (!js->user->domain || *(js->user->domain) == '\0') {
996 purple_connection_error(gc,
997 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
998 _("Invalid XMPP ID. Domain must be set."));
999 g_free(user);
1000 /* Destroying the connection will free the JabberStream */
1001 return NULL;
1004 js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal,
1005 g_free, (GDestroyNotify)jabber_buddy_free);
1007 /* This is overridden during binding, but we need it here
1008 * in case the server only does legacy non-sasl auth!.
1010 purple_connection_set_display_name(gc, user);
1012 js->user_jb = jabber_buddy_find(js, user, TRUE);
1013 g_free(user);
1014 if (!js->user_jb) {
1015 /* This basically *can't* fail, but for good measure... */
1016 purple_connection_error(gc,
1017 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1018 _("Invalid XMPP ID"));
1019 /* Destroying the connection will free the JabberStream */
1020 g_return_val_if_reached(NULL);
1023 js->user_jb->subscription |= JABBER_SUB_BOTH;
1025 js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
1026 g_free, (GDestroyNotify)jabber_iq_callbackdata_free);
1027 js->chats = g_hash_table_new_full(g_str_hash, g_str_equal,
1028 g_free, (GDestroyNotify)jabber_chat_free);
1029 js->next_id = g_random_int();
1030 js->write_buffer = purple_circular_buffer_new(512);
1031 js->old_length = 0;
1032 js->keepalive_timeout = 0;
1033 js->max_inactivity = DEFAULT_INACTIVITY_TIME;
1034 /* Set the default protocol version to 1.0. Overridden in parser.c. */
1035 js->protocol_version.major = 1;
1036 js->protocol_version.minor = 0;
1037 js->sessions = NULL;
1038 js->stun_ip = NULL;
1039 js->stun_port = 0;
1040 js->google_relay_token = NULL;
1041 js->google_relay_host = NULL;
1043 /* if we are idle, set idle-ness on the stream (this could happen if we get
1044 disconnected and the reconnects while being idle. I don't think it makes
1045 sense to do this when registering a new account... */
1046 presence = purple_account_get_presence(account);
1047 if (purple_presence_is_idle(presence))
1048 js->idle = purple_presence_get_idle_time(presence);
1050 return js;
1053 static void
1054 jabber_stream_connect(JabberStream *js)
1056 PurpleConnection *gc = js->gc;
1057 PurpleAccount *account = purple_connection_get_account(gc);
1058 const char *connect_server = purple_account_get_string(account,
1059 "connect_server", "");
1060 const char *bosh_url = purple_account_get_string(account,
1061 "bosh_url", "");
1063 jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
1065 /* If both BOSH and a Connect Server are specified, we prefer BOSH. I'm not
1066 * attached to that choice, though.
1068 if (*bosh_url) {
1069 js->bosh = jabber_bosh_connection_new(js, bosh_url);
1070 if (!js->bosh) {
1071 purple_connection_error(gc,
1072 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1073 _("Malformed BOSH URL"));
1076 return;
1079 js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
1081 /* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
1082 if (purple_strequal("old_ssl", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
1083 js->gsc = purple_ssl_connect(account, js->certificate_CN,
1084 purple_account_get_int(account, "port", 5223),
1085 jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
1086 if (!js->gsc) {
1087 purple_connection_error(gc,
1088 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
1089 _("Unable to establish SSL connection"));
1092 return;
1095 /* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll
1096 * invoke the magic of SRV lookups, to figure out host and port */
1097 if(connect_server[0]) {
1098 jabber_login_connect(js, js->user->domain, connect_server,
1099 purple_account_get_int(account, "port", 5222), TRUE);
1100 } else {
1101 GResolver *resolver = g_resolver_get_default();
1102 g_resolver_lookup_service_async(resolver,
1103 "xmpp-client",
1104 "tcp",
1105 js->user->domain,
1106 js->cancellable,
1107 srv_resolved_cb,
1108 js);
1109 g_object_unref(resolver);
1113 void
1114 jabber_login(PurpleAccount *account)
1116 PurpleConnection *gc = purple_account_get_connection(account);
1117 JabberStream *js;
1118 PurpleImage *image;
1120 purple_connection_set_flags(gc, PURPLE_CONNECTION_FLAG_HTML |
1121 PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY |
1122 PURPLE_CONNECTION_FLAG_NO_IMAGES);
1123 js = jabber_stream_new(account);
1124 if (js == NULL)
1125 return;
1127 /* replace old default proxies with the new default: NULL
1128 * TODO: these can eventually be removed */
1129 if (purple_strequal("proxy.jabber.org", purple_account_get_string(account, "ft_proxies", ""))
1130 || purple_strequal("proxy.eu.jabber.org", purple_account_get_string(account, "ft_proxies", "")))
1131 purple_account_set_string(account, "ft_proxies", NULL);
1134 * Calculate the avatar hash for our current image so we know (when we
1135 * fetch our vCard and PEP avatar) if we should send our avatar to the
1136 * server.
1138 image = purple_buddy_icons_find_account_icon(account);
1139 if (image != NULL) {
1140 js->initial_avatar_hash = g_compute_checksum_for_data(
1141 G_CHECKSUM_SHA1,
1142 purple_image_get_data(image),
1143 purple_image_get_data_size(image)
1145 g_object_unref(image);
1148 jabber_stream_connect(js);
1152 static gboolean
1153 conn_close_cb(gpointer data)
1155 JabberStream *js = data;
1156 PurpleAccount *account = purple_connection_get_account(js->gc);
1158 jabber_parser_free(js);
1160 purple_account_disconnect(account);
1162 js->conn_close_timeout = 0;
1164 return FALSE;
1167 static void
1168 jabber_connection_schedule_close(JabberStream *js)
1170 js->conn_close_timeout = g_timeout_add(0, conn_close_cb, js);
1173 static void
1174 jabber_registration_result_cb(JabberStream *js, const char *from,
1175 JabberIqType type, const char *id,
1176 PurpleXmlNode *packet, gpointer data)
1178 PurpleAccount *account = purple_connection_get_account(js->gc);
1179 char *buf;
1180 char *to = data;
1182 if (type == JABBER_IQ_RESULT) {
1183 if(js->registration) {
1184 buf = g_strdup_printf(_("Registration of %s@%s successful"),
1185 js->user->node, js->user->domain);
1186 purple_account_register_completed(account, TRUE);
1187 } else {
1188 g_return_if_fail(to != NULL);
1189 buf = g_strdup_printf(_("Registration to %s successful"),
1190 to);
1192 purple_notify_info(NULL, _("Registration Successful"),
1193 _("Registration Successful"), buf,
1194 purple_request_cpar_from_connection(js->gc));
1195 g_free(buf);
1196 } else {
1197 char *msg = jabber_parse_error(js, packet, NULL);
1199 if(!msg)
1200 msg = g_strdup(_("Unknown Error"));
1202 purple_notify_error(NULL, _("Registration Failed"),
1203 _("Registration Failed"), msg,
1204 purple_request_cpar_from_connection(js->gc));
1205 g_free(msg);
1206 purple_account_register_completed(account, FALSE);
1208 g_free(to);
1209 if(js->registration)
1210 jabber_connection_schedule_close(js);
1213 static void
1214 jabber_unregistration_result_cb(JabberStream *js, const char *from,
1215 JabberIqType type, const char *id,
1216 PurpleXmlNode *packet, gpointer data)
1218 char *buf;
1219 char *to = data;
1221 /* This function is never called for unregistering our XMPP account from
1222 * the server, so there should always be a 'to' address. */
1223 g_return_if_fail(to != NULL);
1225 if (type == JABBER_IQ_RESULT) {
1226 buf = g_strdup_printf(_("Registration from %s successfully removed"),
1227 to);
1228 purple_notify_info(NULL, _("Unregistration Successful"),
1229 _("Unregistration Successful"), buf,
1230 purple_request_cpar_from_connection(js->gc));
1231 g_free(buf);
1232 } else {
1233 char *msg = jabber_parse_error(js, packet, NULL);
1235 if(!msg)
1236 msg = g_strdup(_("Unknown Error"));
1238 purple_notify_error(NULL, _("Unregistration Failed"),
1239 _("Unregistration Failed"), msg,
1240 purple_request_cpar_from_connection(js->gc));
1241 g_free(msg);
1243 g_free(to);
1246 typedef struct {
1247 JabberStream *js;
1248 char *who;
1249 } JabberRegisterCBData;
1251 static void
1252 jabber_register_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
1254 GList *groups, *flds;
1255 PurpleXmlNode *query, *y;
1256 JabberIq *iq;
1257 char *username;
1259 iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
1260 query = purple_xmlnode_get_child(iq->node, "query");
1261 if (cbdata->who)
1262 purple_xmlnode_set_attrib(iq->node, "to", cbdata->who);
1264 for(groups = purple_request_fields_get_groups(fields); groups;
1265 groups = groups->next) {
1266 for(flds = purple_request_field_group_get_fields(groups->data);
1267 flds; flds = flds->next) {
1268 PurpleRequestField *field = flds->data;
1269 const char *id = purple_request_field_get_id(field);
1270 if(purple_strequal(id,"unregister")) {
1271 gboolean value = purple_request_field_bool_get_value(field);
1272 if(value) {
1273 /* unregister from service. this doesn't include any of the fields, so remove them from the stanza by recreating it
1274 (there's no "remove child" function for PurpleXmlNode) */
1275 jabber_iq_free(iq);
1276 iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
1277 query = purple_xmlnode_get_child(iq->node, "query");
1278 if (cbdata->who)
1279 purple_xmlnode_set_attrib(iq->node,"to",cbdata->who);
1280 purple_xmlnode_new_child(query, "remove");
1282 jabber_iq_set_callback(iq, jabber_unregistration_result_cb, cbdata->who);
1284 jabber_iq_send(iq);
1285 g_free(cbdata);
1286 return;
1288 } else {
1289 const char *ids[] = {"username", "password", "name", "email", "nick", "first",
1290 "last", "address", "city", "state", "zip", "phone", "url", "date",
1291 NULL};
1292 const char *value = purple_request_field_string_get_value(field);
1293 int i;
1294 for (i = 0; ids[i]; i++) {
1295 if (purple_strequal(id, ids[i]))
1296 break;
1299 if (!ids[i])
1300 continue;
1301 y = purple_xmlnode_new_child(query, ids[i]);
1302 purple_xmlnode_insert_data(y, value, -1);
1303 if(cbdata->js->registration && purple_strequal(id, "username")) {
1304 g_free(cbdata->js->user->node);
1305 cbdata->js->user->node = g_strdup(value);
1307 if(cbdata->js->registration && purple_strequal(id, "password"))
1308 purple_account_set_password(purple_connection_get_account(cbdata->js->gc), value, NULL, NULL);
1313 if(cbdata->js->registration) {
1314 username = g_strdup_printf("%s@%s%s%s", cbdata->js->user->node, cbdata->js->user->domain,
1315 cbdata->js->user->resource ? "/" : "",
1316 cbdata->js->user->resource ? cbdata->js->user->resource : "");
1317 purple_account_set_username(purple_connection_get_account(cbdata->js->gc), username);
1318 g_free(username);
1321 jabber_iq_set_callback(iq, jabber_registration_result_cb, cbdata->who);
1323 jabber_iq_send(iq);
1324 g_free(cbdata);
1327 static void
1328 jabber_register_cancel_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
1330 PurpleAccount *account = purple_connection_get_account(cbdata->js->gc);
1331 if(account && cbdata->js->registration) {
1332 purple_account_register_completed(account, FALSE);
1333 jabber_connection_schedule_close(cbdata->js);
1335 g_free(cbdata->who);
1336 g_free(cbdata);
1339 static void jabber_register_x_data_cb(JabberStream *js, PurpleXmlNode *result, gpointer data)
1341 PurpleXmlNode *query;
1342 JabberIq *iq;
1343 char *to = data;
1345 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
1346 query = purple_xmlnode_get_child(iq->node, "query");
1347 if (to)
1348 purple_xmlnode_set_attrib(iq->node,"to",to);
1350 purple_xmlnode_insert_child(query, result);
1352 jabber_iq_set_callback(iq, jabber_registration_result_cb, to);
1353 jabber_iq_send(iq);
1356 static const struct {
1357 const char *name;
1358 const char *label;
1359 } registration_fields[] = {
1360 { "email", N_("Email") },
1361 { "nick", N_("Nickname") },
1362 { "first", N_("First name") },
1363 { "last", N_("Last name") },
1364 { "address", N_("Address") },
1365 { "city", N_("City") },
1366 { "state", N_("State") },
1367 { "zip", N_("Postal code") },
1368 { "phone", N_("Phone") },
1369 { "url", N_("URL") },
1370 { "date", N_("Date") },
1371 { NULL, NULL }
1374 void jabber_register_parse(JabberStream *js, const char *from, JabberIqType type,
1375 const char *id, PurpleXmlNode *query)
1377 PurpleAccount *account = purple_connection_get_account(js->gc);
1378 PurpleRequestFields *fields;
1379 PurpleRequestFieldGroup *group;
1380 PurpleRequestField *field;
1381 PurpleXmlNode *x, *y, *node;
1382 char *instructions;
1383 JabberRegisterCBData *cbdata;
1384 gboolean registered = FALSE;
1385 int i;
1387 if (type != JABBER_IQ_RESULT)
1388 return;
1390 if(js->registration) {
1391 /* get rid of the login thingy */
1392 purple_connection_set_state(js->gc, PURPLE_CONNECTION_CONNECTED);
1395 if(purple_xmlnode_get_child(query, "registered")) {
1396 registered = TRUE;
1398 if(js->registration) {
1399 purple_notify_error(NULL, _("Already Registered"),
1400 _("Already Registered"), NULL,
1401 purple_request_cpar_from_connection(js->gc));
1402 purple_account_register_completed(account, FALSE);
1403 jabber_connection_schedule_close(js);
1404 return;
1408 if((x = purple_xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1409 jabber_x_data_request(js, x, jabber_register_x_data_cb, g_strdup(from));
1410 return;
1412 } else if((x = purple_xmlnode_get_child_with_namespace(query, "x", NS_OOB_X_DATA))) {
1413 PurpleXmlNode *url;
1415 if((url = purple_xmlnode_get_child(x, "url"))) {
1416 char *href;
1417 if((href = purple_xmlnode_get_data(url))) {
1418 purple_notify_uri(NULL, href);
1419 g_free(href);
1421 if(js->registration) {
1422 /* succeeded, but we have no login info */
1423 purple_account_register_completed(account, TRUE);
1424 purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR,
1425 _("Registration completed successfully. Please reconnect to continue."));
1426 jabber_connection_schedule_close(js);
1428 return;
1433 /* as a last resort, use the old jabber:iq:register syntax */
1435 fields = purple_request_fields_new();
1436 group = purple_request_field_group_new(NULL);
1437 purple_request_fields_add_group(fields, group);
1439 if((node = purple_xmlnode_get_child(query, "username"))) {
1440 char *data = purple_xmlnode_get_data(node);
1441 if(js->registration)
1442 field = purple_request_field_string_new("username", _("Username"), data ? data : js->user->node, FALSE);
1443 else
1444 field = purple_request_field_string_new("username", _("Username"), data, FALSE);
1446 purple_request_field_group_add_field(group, field);
1447 g_free(data);
1449 if((node = purple_xmlnode_get_child(query, "password"))) {
1450 if(js->registration)
1451 field = purple_request_field_string_new("password", _("Password"),
1452 purple_connection_get_password(js->gc), FALSE);
1453 else {
1454 char *data = purple_xmlnode_get_data(node);
1455 field = purple_request_field_string_new("password", _("Password"), data, FALSE);
1456 g_free(data);
1459 purple_request_field_string_set_masked(field, TRUE);
1460 purple_request_field_group_add_field(group, field);
1463 if((node = purple_xmlnode_get_child(query, "name"))) {
1464 if(js->registration)
1465 field = purple_request_field_string_new("name", _("Name"),
1466 purple_account_get_private_alias(purple_connection_get_account(js->gc)), FALSE);
1467 else {
1468 char *data = purple_xmlnode_get_data(node);
1469 field = purple_request_field_string_new("name", _("Name"), data, FALSE);
1470 g_free(data);
1472 purple_request_field_group_add_field(group, field);
1475 for (i = 0; registration_fields[i].name != NULL; ++i) {
1476 if ((node = purple_xmlnode_get_child(query, registration_fields[i].name))) {
1477 char *data = purple_xmlnode_get_data(node);
1478 field = purple_request_field_string_new(registration_fields[i].name,
1479 _(registration_fields[i].label),
1480 data, FALSE);
1481 purple_request_field_group_add_field(group, field);
1482 g_free(data);
1486 if(registered) {
1487 field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE);
1488 purple_request_field_group_add_field(group, field);
1491 if((y = purple_xmlnode_get_child(query, "instructions")))
1492 instructions = purple_xmlnode_get_data(y);
1493 else if(registered)
1494 instructions = g_strdup(_("Please fill out the information below "
1495 "to change your account registration."));
1496 else
1497 instructions = g_strdup(_("Please fill out the information below "
1498 "to register your new account."));
1500 cbdata = g_new0(JabberRegisterCBData, 1);
1501 cbdata->js = js;
1502 cbdata->who = g_strdup(from);
1504 if(js->registration)
1505 purple_request_fields(js->gc, _("Register New XMPP Account"),
1506 _("Register New XMPP Account"), instructions, fields,
1507 _("Register"), G_CALLBACK(jabber_register_cb),
1508 _("Cancel"), G_CALLBACK(jabber_register_cancel_cb),
1509 purple_request_cpar_from_connection(js->gc),
1510 cbdata);
1511 else {
1512 char *title;
1513 g_return_if_fail(from != NULL);
1514 title = registered ? g_strdup_printf(_("Change Account Registration at %s"), from)
1515 :g_strdup_printf(_("Register New Account at %s"), from);
1516 purple_request_fields(js->gc, title, title, instructions,
1517 fields, (registered ? _("Change Registration") :
1518 _("Register")), G_CALLBACK(jabber_register_cb),
1519 _("Cancel"), G_CALLBACK(jabber_register_cancel_cb),
1520 purple_request_cpar_from_connection(js->gc), cbdata);
1521 g_free(title);
1524 g_free(instructions);
1527 void jabber_register_start(JabberStream *js)
1529 JabberIq *iq;
1531 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register");
1532 jabber_iq_send(iq);
1535 void jabber_register_gateway(JabberStream *js, const char *gateway) {
1536 JabberIq *iq;
1538 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register");
1539 purple_xmlnode_set_attrib(iq->node, "to", gateway);
1540 jabber_iq_send(iq);
1543 void jabber_register_account(PurpleAccount *account)
1545 JabberStream *js;
1547 js = jabber_stream_new(account);
1548 if (js == NULL)
1549 return;
1551 js->registration = TRUE;
1552 jabber_stream_connect(js);
1555 static void
1556 jabber_unregister_account_iq_cb(JabberStream *js, const char *from,
1557 JabberIqType type, const char *id,
1558 PurpleXmlNode *packet, gpointer data)
1560 PurpleAccount *account = purple_connection_get_account(js->gc);
1562 if (type == JABBER_IQ_ERROR) {
1563 char *msg = jabber_parse_error(js, packet, NULL);
1565 purple_notify_error(js->gc, _("Error unregistering account"),
1566 _("Error unregistering account"), msg,
1567 purple_request_cpar_from_connection(js->gc));
1568 g_free(msg);
1569 if(js->unregistration_cb)
1570 js->unregistration_cb(account, FALSE, js->unregistration_user_data);
1571 } else {
1572 purple_notify_info(js->gc, _("Account successfully "
1573 "unregistered"), _("Account successfully unregistered"),
1574 NULL, purple_request_cpar_from_connection(js->gc));
1575 if(js->unregistration_cb)
1576 js->unregistration_cb(account, TRUE, js->unregistration_user_data);
1580 static void jabber_unregister_account_cb(JabberStream *js) {
1581 JabberIq *iq;
1582 PurpleXmlNode *query;
1584 g_return_if_fail(js->unregistration);
1586 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
1588 query = purple_xmlnode_get_child_with_namespace(iq->node, "query", "jabber:iq:register");
1590 purple_xmlnode_new_child(query, "remove");
1591 purple_xmlnode_set_attrib(iq->node, "to", js->user->domain);
1593 jabber_iq_set_callback(iq, jabber_unregister_account_iq_cb, NULL);
1594 jabber_iq_send(iq);
1597 void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data) {
1598 PurpleConnection *gc = purple_account_get_connection(account);
1599 JabberStream *js;
1601 if (purple_connection_get_state(gc) != PURPLE_CONNECTION_CONNECTED) {
1602 if (purple_connection_get_state(gc) != PURPLE_CONNECTION_CONNECTING)
1603 jabber_login(account);
1604 js = purple_connection_get_protocol_data(gc);
1605 js->unregistration = TRUE;
1606 js->unregistration_cb = cb;
1607 js->unregistration_user_data = user_data;
1608 return;
1611 js = purple_connection_get_protocol_data(gc);
1613 if (js->unregistration) {
1614 purple_debug_error("jabber", "Unregistration in process; ignoring duplicate request.\n");
1615 return;
1618 js->unregistration = TRUE;
1619 js->unregistration_cb = cb;
1620 js->unregistration_user_data = user_data;
1622 jabber_unregister_account_cb(js);
1625 /* TODO: As Will pointed out in IRC, after being notified by the core to
1626 * shutdown, we should async. wait for the server to send us the stream
1627 * termination before destorying everything. That seems like it would require
1628 * changing the semantics of protocol's close(), so it's a good idea for 3.0.0.
1630 void jabber_close(PurpleConnection *gc)
1632 JabberStream *js = purple_connection_get_protocol_data(gc);
1634 /* Close all of the open Jingle sessions on this stream */
1635 jingle_terminate_sessions(js);
1637 if (js->bosh) {
1638 jabber_bosh_connection_destroy(js->bosh);
1639 js->bosh = NULL;
1640 } else if ((js->gsc && js->gsc->fd > 0) || js->fd > 0)
1641 jabber_send_raw(js, "</stream:stream>", -1);
1643 if(js->gsc) {
1644 purple_ssl_close(js->gsc);
1645 } else if (js->fd > 0) {
1646 if(js->inpa) {
1647 purple_input_remove(js->inpa);
1648 js->inpa = 0;
1650 close(js->fd);
1653 jabber_buddy_remove_all_pending_buddy_info_requests(js);
1655 jabber_parser_free(js);
1657 if(js->iq_callbacks)
1658 g_hash_table_destroy(js->iq_callbacks);
1659 if(js->buddies)
1660 g_hash_table_destroy(js->buddies);
1661 if(js->chats)
1662 g_hash_table_destroy(js->chats);
1664 while(js->chat_servers) {
1665 g_free(js->chat_servers->data);
1666 js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers);
1669 while(js->user_directories) {
1670 g_free(js->user_directories->data);
1671 js->user_directories = g_list_delete_link(js->user_directories, js->user_directories);
1674 while(js->bs_proxies) {
1675 JabberBytestreamsStreamhost *sh = js->bs_proxies->data;
1676 g_free(sh->jid);
1677 g_free(sh->host);
1678 g_free(sh->zeroconf);
1679 g_free(sh);
1680 js->bs_proxies = g_list_delete_link(js->bs_proxies, js->bs_proxies);
1683 purple_http_connection_set_destroy(js->http_conns);
1685 g_free(js->stream_id);
1686 if(js->user)
1687 jabber_id_free(js->user);
1688 g_free(js->initial_avatar_hash);
1689 g_free(js->avatar_hash);
1690 g_free(js->caps_hash);
1692 if (js->write_buffer)
1693 g_object_unref(G_OBJECT(js->write_buffer));
1694 if(js->writeh)
1695 purple_input_remove(js->writeh);
1696 if (js->auth_mech && js->auth_mech->dispose)
1697 js->auth_mech->dispose(js);
1698 #ifdef HAVE_CYRUS_SASL
1699 if(js->sasl)
1700 sasl_dispose(&js->sasl);
1701 if(js->sasl_mechs)
1702 g_string_free(js->sasl_mechs, TRUE);
1703 g_free(js->sasl_cb);
1704 /* Note: _not_ g_free. See auth_cyrus.c:jabber_sasl_cb_secret */
1705 free(js->sasl_secret);
1706 g_free(js->sasl_password);
1707 #endif
1708 g_free(js->serverFQDN);
1709 while(js->commands) {
1710 JabberAdHocCommands *cmd = js->commands->data;
1711 g_free(cmd->jid);
1712 g_free(cmd->node);
1713 g_free(cmd->name);
1714 g_free(cmd);
1715 js->commands = g_list_delete_link(js->commands, js->commands);
1717 g_free(js->server_name);
1718 g_free(js->certificate_CN);
1719 g_free(js->gmail_last_time);
1720 g_free(js->gmail_last_tid);
1721 g_free(js->old_msg);
1722 g_free(js->old_avatarhash);
1723 g_free(js->old_artist);
1724 g_free(js->old_title);
1725 g_free(js->old_source);
1726 g_free(js->old_uri);
1727 g_free(js->old_track);
1729 if (js->vcard_timer != 0)
1730 g_source_remove(js->vcard_timer);
1732 if (js->keepalive_timeout != 0)
1733 g_source_remove(js->keepalive_timeout);
1734 if (js->inactivity_timer != 0)
1735 g_source_remove(js->inactivity_timer);
1736 if (js->conn_close_timeout != 0)
1737 g_source_remove(js->conn_close_timeout);
1739 g_cancellable_cancel(js->cancellable);
1740 g_object_unref(G_OBJECT(js->cancellable));
1742 g_free(js->stun_ip);
1744 /* remove Google relay-related stuff */
1745 g_free(js->google_relay_token);
1746 g_free(js->google_relay_host);
1748 g_free(js);
1750 purple_connection_set_protocol_data(gc, NULL);
1753 void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
1755 #define JABBER_CONNECT_STEPS ((js->gsc || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) ? 9 : 5)
1757 js->state = state;
1758 switch(state) {
1759 case JABBER_STREAM_OFFLINE:
1760 break;
1761 case JABBER_STREAM_CONNECTING:
1762 purple_connection_update_progress(js->gc, _("Connecting"), 1,
1763 JABBER_CONNECT_STEPS);
1764 break;
1765 case JABBER_STREAM_INITIALIZING:
1766 purple_connection_update_progress(js->gc, _("Initializing Stream"),
1767 js->gsc ? 5 : 2, JABBER_CONNECT_STEPS);
1768 jabber_stream_init(js);
1769 break;
1770 case JABBER_STREAM_INITIALIZING_ENCRYPTION:
1771 purple_connection_update_progress(js->gc, _("Initializing SSL/TLS"),
1772 6, JABBER_CONNECT_STEPS);
1773 break;
1774 case JABBER_STREAM_AUTHENTICATING:
1775 purple_connection_update_progress(js->gc, _("Authenticating"),
1776 js->gsc ? 7 : 3, JABBER_CONNECT_STEPS);
1777 break;
1778 case JABBER_STREAM_POST_AUTH:
1779 purple_connection_update_progress(js->gc, _("Re-initializing Stream"),
1780 (js->gsc ? 8 : 4), JABBER_CONNECT_STEPS);
1782 break;
1783 case JABBER_STREAM_CONNECTED:
1784 /* Send initial presence */
1785 jabber_presence_send(js, TRUE);
1786 /* Start up the inactivity timer */
1787 jabber_stream_restart_inactivity_timer(js);
1789 purple_connection_set_state(js->gc, PURPLE_CONNECTION_CONNECTED);
1790 break;
1793 #undef JABBER_CONNECT_STEPS
1796 char *jabber_get_next_id(JabberStream *js)
1798 return g_strdup_printf("purple%x", js->next_id++);
1802 void jabber_idle_set(PurpleConnection *gc, int idle)
1804 JabberStream *js = purple_connection_get_protocol_data(gc);
1806 js->idle = idle ? time(NULL) - idle : idle;
1808 /* send out an updated prescence */
1809 purple_debug_info("jabber", "sending updated presence for idle\n");
1810 jabber_presence_send(js, FALSE);
1813 void jabber_blocklist_parse_push(JabberStream *js, const char *from,
1814 JabberIqType type, const char *id,
1815 PurpleXmlNode *child)
1817 JabberIq *result;
1818 PurpleXmlNode *item;
1819 PurpleAccount *account;
1820 gboolean is_block;
1821 GSList *deny;
1823 if (!jabber_is_own_account(js, from)) {
1824 PurpleXmlNode *error, *x;
1825 result = jabber_iq_new(js, JABBER_IQ_ERROR);
1826 purple_xmlnode_set_attrib(result->node, "id", id);
1827 if (from)
1828 purple_xmlnode_set_attrib(result->node, "to", from);
1830 error = purple_xmlnode_new_child(result->node, "error");
1831 purple_xmlnode_set_attrib(error, "type", "cancel");
1832 x = purple_xmlnode_new_child(error, "not-allowed");
1833 purple_xmlnode_set_namespace(x, NS_XMPP_STANZAS);
1835 jabber_iq_send(result);
1836 return;
1839 account = purple_connection_get_account(js->gc);
1840 is_block = purple_strequal(child->name, "block");
1842 item = purple_xmlnode_get_child(child, "item");
1843 if (!is_block && item == NULL) {
1844 /* Unblock everyone */
1845 purple_debug_info("jabber", "Received unblock push. Unblocking everyone.\n");
1847 while ((deny = purple_account_privacy_get_denied(account)) != NULL) {
1848 purple_account_privacy_deny_remove(account, deny->data, TRUE);
1850 } else if (item == NULL) {
1851 /* An empty <block/> is bogus */
1852 PurpleXmlNode *error, *x;
1853 result = jabber_iq_new(js, JABBER_IQ_ERROR);
1854 purple_xmlnode_set_attrib(result->node, "id", id);
1856 error = purple_xmlnode_new_child(result->node, "error");
1857 purple_xmlnode_set_attrib(error, "type", "modify");
1858 x = purple_xmlnode_new_child(error, "bad-request");
1859 purple_xmlnode_set_namespace(x, NS_XMPP_STANZAS);
1861 jabber_iq_send(result);
1862 return;
1863 } else {
1864 for ( ; item; item = purple_xmlnode_get_next_twin(item)) {
1865 const char *jid = purple_xmlnode_get_attrib(item, "jid");
1866 if (jid == NULL || *jid == '\0')
1867 continue;
1869 if (is_block)
1870 purple_account_privacy_deny_add(account, jid, TRUE);
1871 else
1872 purple_account_privacy_deny_remove(account, jid, TRUE);
1876 result = jabber_iq_new(js, JABBER_IQ_RESULT);
1877 purple_xmlnode_set_attrib(result->node, "id", id);
1878 jabber_iq_send(result);
1881 static void jabber_blocklist_parse(JabberStream *js, const char *from,
1882 JabberIqType type, const char *id,
1883 PurpleXmlNode *packet, gpointer data)
1885 PurpleXmlNode *blocklist, *item;
1886 PurpleAccount *account;
1887 GSList *deny;
1889 blocklist = purple_xmlnode_get_child_with_namespace(packet,
1890 "blocklist", NS_SIMPLE_BLOCKING);
1891 account = purple_connection_get_account(js->gc);
1893 if (type == JABBER_IQ_ERROR || blocklist == NULL)
1894 return;
1896 /* This is the only privacy method supported by XEP-0191 */
1897 purple_account_set_privacy_type(account, PURPLE_ACCOUNT_PRIVACY_DENY_USERS);
1900 * TODO: When account->deny is something more than a hash table, this can
1901 * be re-written to find the set intersection and difference.
1903 while ((deny = purple_account_privacy_get_denied(account)))
1904 purple_account_privacy_deny_remove(account, deny->data, TRUE);
1906 item = purple_xmlnode_get_child(blocklist, "item");
1907 while (item != NULL) {
1908 const char *jid = purple_xmlnode_get_attrib(item, "jid");
1909 purple_account_privacy_deny_add(account, jid, TRUE);
1910 item = purple_xmlnode_get_next_twin(item);
1914 void jabber_request_block_list(JabberStream *js)
1916 JabberIq *iq;
1917 PurpleXmlNode *blocklist;
1919 iq = jabber_iq_new(js, JABBER_IQ_GET);
1921 blocklist = purple_xmlnode_new_child(iq->node, "blocklist");
1922 purple_xmlnode_set_namespace(blocklist, NS_SIMPLE_BLOCKING);
1924 jabber_iq_set_callback(iq, jabber_blocklist_parse, NULL);
1926 jabber_iq_send(iq);
1929 void jabber_add_deny(PurpleConnection *gc, const char *who)
1931 JabberStream *js;
1932 JabberIq *iq;
1933 PurpleXmlNode *block, *item;
1935 g_return_if_fail(who != NULL && *who != '\0');
1937 js = purple_connection_get_protocol_data(gc);
1938 if (js == NULL)
1939 return;
1941 if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
1943 jabber_google_roster_add_deny(js, who);
1944 return;
1947 if (!(js->server_caps & JABBER_CAP_BLOCKING))
1949 purple_notify_error(NULL, _("Server doesn't support blocking"),
1950 _("Server doesn't support blocking"), NULL,
1951 purple_request_cpar_from_connection(gc));
1952 return;
1955 iq = jabber_iq_new(js, JABBER_IQ_SET);
1957 block = purple_xmlnode_new_child(iq->node, "block");
1958 purple_xmlnode_set_namespace(block, NS_SIMPLE_BLOCKING);
1960 item = purple_xmlnode_new_child(block, "item");
1961 purple_xmlnode_set_attrib(item, "jid", who);
1963 jabber_iq_send(iq);
1966 void jabber_rem_deny(PurpleConnection *gc, const char *who)
1968 JabberStream *js;
1969 JabberIq *iq;
1970 PurpleXmlNode *unblock, *item;
1972 g_return_if_fail(who != NULL && *who != '\0');
1974 js = purple_connection_get_protocol_data(gc);
1975 if (js == NULL)
1976 return;
1978 if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
1980 jabber_google_roster_rem_deny(js, who);
1981 return;
1984 if (!(js->server_caps & JABBER_CAP_BLOCKING))
1985 return;
1987 iq = jabber_iq_new(js, JABBER_IQ_SET);
1989 unblock = purple_xmlnode_new_child(iq->node, "unblock");
1990 purple_xmlnode_set_namespace(unblock, NS_SIMPLE_BLOCKING);
1992 item = purple_xmlnode_new_child(unblock, "item");
1993 purple_xmlnode_set_attrib(item, "jid", who);
1995 jabber_iq_send(iq);
1998 void jabber_add_feature(const char *namespace, JabberFeatureEnabled cb) {
1999 JabberFeature *feat;
2001 g_return_if_fail(namespace != NULL);
2003 feat = g_new0(JabberFeature,1);
2004 feat->namespace = g_strdup(namespace);
2005 feat->is_enabled = cb;
2007 /* try to remove just in case it already exists in the list */
2008 jabber_remove_feature(namespace);
2010 jabber_features = g_list_append(jabber_features, feat);
2013 void jabber_remove_feature(const char *namespace) {
2014 GList *feature;
2015 for(feature = jabber_features; feature; feature = feature->next) {
2016 JabberFeature *feat = (JabberFeature*)feature->data;
2017 if(purple_strequal(feat->namespace, namespace)) {
2018 g_free(feat->namespace);
2019 g_free(feature->data);
2020 jabber_features = g_list_delete_link(jabber_features, feature);
2021 break;
2026 static void jabber_features_destroy(void)
2028 while (jabber_features) {
2029 JabberFeature *feature = jabber_features->data;
2030 g_free(feature->namespace);
2031 g_free(feature);
2032 jabber_features = g_list_delete_link(jabber_features, jabber_features);
2036 gint
2037 jabber_identity_compare(gconstpointer a, gconstpointer b)
2039 const JabberIdentity *ac;
2040 const JabberIdentity *bc;
2041 gint cat_cmp;
2042 gint typ_cmp;
2044 ac = a;
2045 bc = b;
2047 cat_cmp = g_strcmp0(ac->category, bc->category);
2048 if (cat_cmp != 0) {
2049 return cat_cmp;
2052 typ_cmp = g_strcmp0(ac->type, bc->type);
2053 if (typ_cmp != 0) {
2054 return typ_cmp;
2057 return g_strcmp0(ac->lang, bc->lang);
2060 void jabber_add_identity(const gchar *category, const gchar *type,
2061 const gchar *lang, const gchar *name)
2063 GList *identity;
2064 JabberIdentity *ident;
2066 /* both required according to XEP-0030 */
2067 g_return_if_fail(category != NULL);
2068 g_return_if_fail(type != NULL);
2070 /* Check if this identity is already there... */
2071 for (identity = jabber_identities; identity; identity = identity->next) {
2072 JabberIdentity *id = identity->data;
2073 if (purple_strequal(id->category, category) &&
2074 purple_strequal(id->type, type) &&
2075 purple_strequal(id->lang, lang))
2076 return;
2079 ident = g_new0(JabberIdentity, 1);
2080 ident->category = g_strdup(category);
2081 ident->type = g_strdup(type);
2082 ident->lang = g_strdup(lang);
2083 ident->name = g_strdup(name);
2084 jabber_identities = g_list_insert_sorted(jabber_identities, ident,
2085 jabber_identity_compare);
2088 static void jabber_identities_destroy(void)
2090 while (jabber_identities) {
2091 JabberIdentity *id = jabber_identities->data;
2092 g_free(id->category);
2093 g_free(id->type);
2094 g_free(id->lang);
2095 g_free(id->name);
2096 g_free(id);
2097 jabber_identities = g_list_delete_link(jabber_identities, jabber_identities);
2101 gboolean jabber_stream_is_ssl(JabberStream *js)
2103 return (js->bosh && jabber_bosh_connection_is_ssl(js->bosh)) ||
2104 (!js->bosh && js->gsc);
2107 static gboolean
2108 inactivity_cb(gpointer data)
2110 JabberStream *js = data;
2112 /* We want whatever is sent to set this. It's okay because
2113 * the eventloop unsets it via the return FALSE.
2115 js->inactivity_timer = 0;
2117 if (js->bosh)
2118 jabber_bosh_connection_send_keepalive(js->bosh);
2119 else
2120 jabber_send_raw(js, "\t", 1);
2122 return FALSE;
2125 void jabber_stream_restart_inactivity_timer(JabberStream *js)
2127 if (js->inactivity_timer != 0) {
2128 g_source_remove(js->inactivity_timer);
2129 js->inactivity_timer = 0;
2132 g_return_if_fail(js->max_inactivity > 0);
2134 js->inactivity_timer =
2135 g_timeout_add_seconds(js->max_inactivity,
2136 inactivity_cb, js);
2139 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
2141 return "jabber";
2144 const char* jabber_list_emblem(PurpleBuddy *b)
2146 JabberStream *js;
2147 JabberBuddy *jb = NULL;
2148 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(b));
2150 if(!gc)
2151 return NULL;
2153 js = purple_connection_get_protocol_data(gc);
2154 if(js)
2155 jb = jabber_buddy_find(js, purple_buddy_get_name(b), FALSE);
2157 if(!PURPLE_BUDDY_IS_ONLINE(b)) {
2158 if(jb && (jb->subscription & JABBER_SUB_PENDING ||
2159 !(jb->subscription & JABBER_SUB_TO)))
2160 return "not-authorized";
2163 if (jb) {
2164 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, NULL);
2165 if (jbr) {
2166 const gchar *client_type =
2167 jabber_resource_get_identity_category_type(jbr, "client");
2169 if (client_type) {
2170 if (purple_strequal(client_type, "phone")) {
2171 return "mobile";
2172 } else if (purple_strequal(client_type, "web")) {
2173 return "external";
2174 } else if (purple_strequal(client_type, "handheld")) {
2175 return "hiptop";
2176 } else if (purple_strequal(client_type, "bot")) {
2177 return "bot";
2179 /* the default value "pc" falls through and has no emblem */
2184 return NULL;
2187 char *jabber_status_text(PurpleBuddy *b)
2189 char *ret = NULL;
2190 JabberBuddy *jb = NULL;
2191 PurpleAccount *account = purple_buddy_get_account(b);
2192 PurpleConnection *gc = purple_account_get_connection(account);
2194 if (gc && purple_connection_get_protocol_data(gc))
2195 jb = jabber_buddy_find(purple_connection_get_protocol_data(gc), purple_buddy_get_name(b), FALSE);
2197 if(jb && !PURPLE_BUDDY_IS_ONLINE(b) && (jb->subscription & JABBER_SUB_PENDING || !(jb->subscription & JABBER_SUB_TO))) {
2198 ret = g_strdup(_("Not Authorized"));
2199 } else if(jb && !PURPLE_BUDDY_IS_ONLINE(b) && jb->error_msg) {
2200 ret = g_strdup(jb->error_msg);
2201 } else {
2202 PurplePresence *presence = purple_buddy_get_presence(b);
2203 PurpleStatus *status = purple_presence_get_active_status(presence);
2204 const char *message;
2206 if((message = purple_status_get_attr_string(status, "message"))) {
2207 ret = g_markup_escape_text(message, -1);
2208 } else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
2209 PurpleStatus *status = purple_presence_get_status(presence, "tune");
2210 const char *title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
2211 const char *artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
2212 const char *album = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
2213 ret = purple_util_format_song_info(title, artist, album, NULL);
2217 return ret;
2220 static void
2221 jabber_tooltip_add_resource_text(JabberBuddyResource *jbr,
2222 PurpleNotifyUserInfo *user_info, gboolean multiple_resources)
2224 char *text = NULL;
2225 char *res = NULL;
2226 char *label, *value;
2227 const char *state;
2229 if(jbr->status) {
2230 text = g_markup_escape_text(jbr->status, -1);
2233 if(jbr->name)
2234 res = g_strdup_printf(" (%s)", jbr->name);
2236 state = jabber_buddy_state_get_name(jbr->state);
2237 if (text != NULL && !purple_utf8_strcasecmp(state, text)) {
2238 g_free(text);
2239 text = NULL;
2242 label = g_strdup_printf("%s%s", _("Status"), (res ? res : ""));
2243 value = g_strdup_printf("%s%s%s", state, (text ? ": " : ""), (text ? text : ""));
2245 purple_notify_user_info_add_pair_html(user_info, label, value);
2246 g_free(label);
2247 g_free(value);
2248 g_free(text);
2250 /* if the resource is idle, show that */
2251 /* only show it if there is more than one resource available for
2252 the buddy, since the "general" idleness will be shown anyway,
2253 this way we can see see the idleness of lower-priority resources */
2254 if (jbr->idle && multiple_resources) {
2255 gchar *idle_str =
2256 purple_str_seconds_to_string(time(NULL) - jbr->idle);
2257 label = g_strdup_printf("%s%s", _("Idle"), (res ? res : ""));
2258 purple_notify_user_info_add_pair_plaintext(user_info, label, idle_str);
2259 g_free(idle_str);
2260 g_free(label);
2262 g_free(res);
2265 void jabber_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
2267 JabberBuddy *jb;
2268 PurpleAccount *account;
2269 PurpleConnection *gc;
2270 JabberStream *js;
2272 g_return_if_fail(b != NULL);
2274 account = purple_buddy_get_account(b);
2275 g_return_if_fail(account != NULL);
2277 gc = purple_account_get_connection(account);
2278 g_return_if_fail(gc != NULL);
2280 js = purple_connection_get_protocol_data(gc);
2281 g_return_if_fail(js != NULL);
2283 jb = jabber_buddy_find(js, purple_buddy_get_name(b), FALSE);
2285 if(jb) {
2286 JabberBuddyResource *jbr = NULL;
2287 PurplePresence *presence = purple_buddy_get_presence(b);
2288 const char *sub;
2289 GList *l;
2290 const char *mood;
2291 gboolean multiple_resources =
2292 jb->resources && g_list_next(jb->resources);
2293 JabberBuddyResource *top_jbr = jabber_buddy_find_resource(jb, NULL);
2295 /* resource-specific info for the top resource */
2296 if (top_jbr) {
2297 jabber_tooltip_add_resource_text(top_jbr, user_info,
2298 multiple_resources);
2301 for(l=jb->resources; l; l = l->next) {
2302 jbr = l->data;
2303 /* the remaining resources */
2304 if (jbr != top_jbr) {
2305 jabber_tooltip_add_resource_text(jbr, user_info,
2306 multiple_resources);
2310 if (full) {
2311 PurpleStatus *status;
2313 status = purple_presence_get_status(presence, "mood");
2314 mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
2315 if(mood && *mood) {
2316 const char *moodtext;
2317 /* find the mood */
2318 PurpleMood *moods = jabber_get_moods(account);
2319 const char *description = NULL;
2321 for (; moods->mood ; moods++) {
2322 if (purple_strequal(moods->mood, mood)) {
2323 description = moods->description;
2324 break;
2328 moodtext = purple_status_get_attr_string(status, PURPLE_MOOD_COMMENT);
2329 if(moodtext && *moodtext) {
2330 char *moodplustext =
2331 g_strdup_printf("%s (%s)", description ? _(description) : mood, moodtext);
2333 purple_notify_user_info_add_pair_html(user_info, _("Mood"), moodplustext);
2334 g_free(moodplustext);
2335 } else
2336 purple_notify_user_info_add_pair_html(user_info, _("Mood"),
2337 description ? _(description) : mood);
2339 if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
2340 PurpleStatus *tune = purple_presence_get_status(presence, "tune");
2341 const char *title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
2342 const char *artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST);
2343 const char *album = purple_status_get_attr_string(tune, PURPLE_TUNE_ALBUM);
2344 char *playing = purple_util_format_song_info(title, artist, album, NULL);
2345 if (playing) {
2346 purple_notify_user_info_add_pair_html(user_info, _("Now Listening"), playing);
2347 g_free(playing);
2351 if(jb->subscription & JABBER_SUB_FROM) {
2352 if(jb->subscription & JABBER_SUB_TO)
2353 sub = _("Both");
2354 else if(jb->subscription & JABBER_SUB_PENDING)
2355 sub = _("From (To pending)");
2356 else
2357 sub = _("From");
2358 } else {
2359 if(jb->subscription & JABBER_SUB_TO)
2360 sub = _("To");
2361 else if(jb->subscription & JABBER_SUB_PENDING)
2362 sub = _("None (To pending)");
2363 else
2364 sub = _("None");
2367 purple_notify_user_info_add_pair_html(user_info, _("Subscription"), sub);
2371 if(!PURPLE_BUDDY_IS_ONLINE(b) && jb->error_msg) {
2372 purple_notify_user_info_add_pair_html(user_info, _("Error"), jb->error_msg);
2377 GList *jabber_status_types(PurpleAccount *account)
2379 PurpleStatusType *type;
2380 GList *types = NULL;
2381 GValue *priority_value;
2382 GValue *buzz_enabled;
2384 priority_value = purple_value_new(G_TYPE_INT);
2385 g_value_set_int(priority_value, 1);
2386 buzz_enabled = purple_value_new(G_TYPE_BOOLEAN);
2387 g_value_set_boolean(buzz_enabled, TRUE);
2388 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
2389 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_ONLINE),
2390 NULL, TRUE, TRUE, FALSE,
2391 "priority", _("Priority"), priority_value,
2392 "message", _("Message"), purple_value_new(G_TYPE_STRING),
2393 "mood", _("Mood"), purple_value_new(G_TYPE_STRING),
2394 "moodtext", _("Mood Text"), purple_value_new(G_TYPE_STRING),
2395 "nick", _("Nickname"), purple_value_new(G_TYPE_STRING),
2396 "buzz", _("Allow Buzz"), buzz_enabled,
2397 NULL);
2398 types = g_list_prepend(types, type);
2401 type = purple_status_type_new_with_attrs(PURPLE_STATUS_MOOD,
2402 "mood", NULL, TRUE, TRUE, TRUE,
2403 PURPLE_MOOD_NAME, _("Mood Name"), purple_value_new(G_TYPE_STRING),
2404 PURPLE_MOOD_COMMENT, _("Mood Comment"), purple_value_new(G_TYPE_STRING),
2405 NULL);
2406 types = g_list_prepend(types, type);
2408 priority_value = purple_value_new(G_TYPE_INT);
2409 g_value_set_int(priority_value, 1);
2410 buzz_enabled = purple_value_new(G_TYPE_BOOLEAN);
2411 g_value_set_boolean(buzz_enabled, TRUE);
2412 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
2413 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT),
2414 _("Chatty"), TRUE, TRUE, FALSE,
2415 "priority", _("Priority"), priority_value,
2416 "message", _("Message"), purple_value_new(G_TYPE_STRING),
2417 "mood", _("Mood"), purple_value_new(G_TYPE_STRING),
2418 "moodtext", _("Mood Text"), purple_value_new(G_TYPE_STRING),
2419 "nick", _("Nickname"), purple_value_new(G_TYPE_STRING),
2420 "buzz", _("Allow Buzz"), buzz_enabled,
2421 NULL);
2422 types = g_list_prepend(types, type);
2424 priority_value = purple_value_new(G_TYPE_INT);
2425 g_value_set_int(priority_value, 0);
2426 buzz_enabled = purple_value_new(G_TYPE_BOOLEAN);
2427 g_value_set_boolean(buzz_enabled, TRUE);
2428 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY,
2429 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY),
2430 NULL, TRUE, TRUE, FALSE,
2431 "priority", _("Priority"), priority_value,
2432 "message", _("Message"), purple_value_new(G_TYPE_STRING),
2433 "mood", _("Mood"), purple_value_new(G_TYPE_STRING),
2434 "moodtext", _("Mood Text"), purple_value_new(G_TYPE_STRING),
2435 "nick", _("Nickname"), purple_value_new(G_TYPE_STRING),
2436 "buzz", _("Allow Buzz"), buzz_enabled,
2437 NULL);
2438 types = g_list_prepend(types, type);
2440 priority_value = purple_value_new(G_TYPE_INT);
2441 g_value_set_int(priority_value, 0);
2442 buzz_enabled = purple_value_new(G_TYPE_BOOLEAN);
2443 g_value_set_boolean(buzz_enabled, TRUE);
2444 type = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY,
2445 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA),
2446 NULL, TRUE, TRUE, FALSE,
2447 "priority", _("Priority"), priority_value,
2448 "message", _("Message"), purple_value_new(G_TYPE_STRING),
2449 "mood", _("Mood"), purple_value_new(G_TYPE_STRING),
2450 "moodtext", _("Mood Text"), purple_value_new(G_TYPE_STRING),
2451 "nick", _("Nickname"), purple_value_new(G_TYPE_STRING),
2452 "buzz", _("Allow Buzz"), buzz_enabled,
2453 NULL);
2454 types = g_list_prepend(types, type);
2456 priority_value = purple_value_new(G_TYPE_INT);
2457 g_value_set_int(priority_value, 0);
2458 type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE,
2459 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND),
2460 _("Do Not Disturb"), TRUE, TRUE, FALSE,
2461 "priority", _("Priority"), priority_value,
2462 "message", _("Message"), purple_value_new(G_TYPE_STRING),
2463 "mood", _("Mood"), purple_value_new(G_TYPE_STRING),
2464 "moodtext", _("Mood Text"), purple_value_new(G_TYPE_STRING),
2465 "nick", _("Nickname"), purple_value_new(G_TYPE_STRING),
2466 NULL);
2467 types = g_list_prepend(types, type);
2470 if(js->protocol_version == JABBER_PROTO_0_9)
2471 "Invisible"
2474 type = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE,
2475 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE),
2476 NULL, TRUE, TRUE, FALSE,
2477 "message", _("Message"), purple_value_new(G_TYPE_STRING),
2478 NULL);
2479 types = g_list_prepend(types, type);
2481 type = purple_status_type_new_with_attrs(PURPLE_STATUS_TUNE,
2482 "tune", NULL, FALSE, TRUE, TRUE,
2483 PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(G_TYPE_STRING),
2484 PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(G_TYPE_STRING),
2485 PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(G_TYPE_STRING),
2486 PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(G_TYPE_STRING),
2487 PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(G_TYPE_STRING),
2488 PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(G_TYPE_STRING),
2489 PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(G_TYPE_INT),
2490 PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(G_TYPE_INT),
2491 PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(G_TYPE_STRING),
2492 NULL);
2493 types = g_list_prepend(types, type);
2495 return g_list_reverse(types);
2498 static void
2499 jabber_password_change_result_cb(JabberStream *js, const char *from,
2500 JabberIqType type, const char *id,
2501 PurpleXmlNode *packet, gpointer data)
2503 if (type == JABBER_IQ_RESULT) {
2504 purple_notify_info(js->gc, _("Password Changed"), _("Password "
2505 "Changed"), _("Your password has been changed."),
2506 purple_request_cpar_from_connection(js->gc));
2508 purple_account_set_password(purple_connection_get_account(js->gc), (const char *)data, NULL, NULL);
2509 } else {
2510 char *msg = jabber_parse_error(js, packet, NULL);
2512 purple_notify_error(js->gc, _("Error changing password"),
2513 _("Error changing password"), msg,
2514 purple_request_cpar_from_connection(js->gc));
2515 g_free(msg);
2518 g_free(data);
2521 static void jabber_password_change_cb(JabberStream *js,
2522 PurpleRequestFields *fields)
2524 const char *p1, *p2;
2525 JabberIq *iq;
2526 PurpleXmlNode *query, *y;
2528 p1 = purple_request_fields_get_string(fields, "password1");
2529 p2 = purple_request_fields_get_string(fields, "password2");
2531 if(!purple_strequal(p1, p2)) {
2532 purple_notify_error(js->gc, NULL,
2533 _("New passwords do not match."), NULL,
2534 purple_request_cpar_from_connection(js->gc));
2535 return;
2538 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
2540 purple_xmlnode_set_attrib(iq->node, "to", js->user->domain);
2542 query = purple_xmlnode_get_child(iq->node, "query");
2544 y = purple_xmlnode_new_child(query, "username");
2545 purple_xmlnode_insert_data(y, js->user->node, -1);
2546 y = purple_xmlnode_new_child(query, "password");
2547 purple_xmlnode_insert_data(y, p1, -1);
2549 jabber_iq_set_callback(iq, jabber_password_change_result_cb, g_strdup(p1));
2551 jabber_iq_send(iq);
2554 static void jabber_password_change(PurpleProtocolAction *action)
2557 PurpleConnection *gc = (PurpleConnection *) action->connection;
2558 JabberStream *js = purple_connection_get_protocol_data(gc);
2559 PurpleRequestFields *fields;
2560 PurpleRequestFieldGroup *group;
2561 PurpleRequestField *field;
2563 fields = purple_request_fields_new();
2564 group = purple_request_field_group_new(NULL);
2565 purple_request_fields_add_group(fields, group);
2567 field = purple_request_field_string_new("password1", _("Password"),
2568 "", FALSE);
2569 purple_request_field_string_set_masked(field, TRUE);
2570 purple_request_field_set_required(field, TRUE);
2571 purple_request_field_group_add_field(group, field);
2573 field = purple_request_field_string_new("password2", _("Password (again)"),
2574 "", FALSE);
2575 purple_request_field_string_set_masked(field, TRUE);
2576 purple_request_field_set_required(field, TRUE);
2577 purple_request_field_group_add_field(group, field);
2579 purple_request_fields(js->gc, _("Change XMPP Password"),
2580 _("Change XMPP Password"), _("Please enter your new password"),
2581 fields, _("OK"), G_CALLBACK(jabber_password_change_cb),
2582 _("Cancel"), NULL,
2583 purple_request_cpar_from_connection(gc), js);
2586 GList *jabber_get_actions(PurpleConnection *gc)
2588 JabberStream *js = purple_connection_get_protocol_data(gc);
2589 GList *m = NULL;
2590 PurpleProtocolAction *act;
2592 act = purple_protocol_action_new(_("Set User Info..."),
2593 jabber_setup_set_info);
2594 m = g_list_append(m, act);
2596 /* if (js->account_options & CHANGE_PASSWORD) { */
2597 act = purple_protocol_action_new(_("Change Password..."),
2598 jabber_password_change);
2599 m = g_list_append(m, act);
2600 /* } */
2602 act = purple_protocol_action_new(_("Search for Users..."),
2603 jabber_user_search_begin);
2604 m = g_list_append(m, act);
2606 purple_debug_info("jabber", "jabber_get_actions: have pep: %s\n", js->pep?"YES":"NO");
2608 if(js->pep)
2609 jabber_pep_init_actions(&m);
2611 if(js->commands)
2612 jabber_adhoc_init_server_commands(js, &m);
2614 return m;
2617 PurpleChat *jabber_find_blist_chat(PurpleAccount *account, const char *name)
2619 PurpleBlistNode *gnode, *cnode;
2620 JabberID *jid;
2622 if(!(jid = jabber_id_new(name)))
2623 return NULL;
2625 for (gnode = purple_blist_get_default_root(); gnode;
2626 gnode = purple_blist_node_get_sibling_next(gnode)) {
2627 for(cnode = purple_blist_node_get_first_child(gnode);
2628 cnode;
2629 cnode = purple_blist_node_get_sibling_next(cnode)) {
2630 PurpleChat *chat = (PurpleChat*)cnode;
2631 const char *room, *server;
2632 GHashTable *components;
2633 if(!PURPLE_IS_CHAT(cnode))
2634 continue;
2636 if (purple_chat_get_account(chat) != account)
2637 continue;
2639 components = purple_chat_get_components(chat);
2640 if(!(room = g_hash_table_lookup(components, "room")))
2641 continue;
2642 if(!(server = g_hash_table_lookup(components, "server")))
2643 continue;
2645 /* FIXME: Collate is wrong in a few cases here; this should be prepped */
2646 if(jid->node && jid->domain &&
2647 !g_utf8_collate(room, jid->node) && !g_utf8_collate(server, jid->domain)) {
2648 jabber_id_free(jid);
2649 return chat;
2653 jabber_id_free(jid);
2654 return NULL;
2657 void jabber_convo_closed(PurpleConnection *gc, const char *who)
2659 JabberStream *js = purple_connection_get_protocol_data(gc);
2660 JabberID *jid;
2661 JabberBuddy *jb;
2662 JabberBuddyResource *jbr;
2664 if(!(jid = jabber_id_new(who)))
2665 return;
2667 if((jb = jabber_buddy_find(js, who, TRUE)) &&
2668 (jbr = jabber_buddy_find_resource(jb, jid->resource))) {
2669 g_free(jbr->thread_id);
2670 jbr->thread_id = NULL;
2673 jabber_id_free(jid);
2677 char *jabber_parse_error(JabberStream *js,
2678 PurpleXmlNode *packet,
2679 PurpleConnectionError *reason)
2681 PurpleXmlNode *error;
2682 const char *code = NULL, *text = NULL;
2683 const char *xmlns = purple_xmlnode_get_namespace(packet);
2684 char *cdata = NULL;
2686 #define SET_REASON(x) \
2687 if(reason != NULL) { *reason = x; }
2689 if((error = purple_xmlnode_get_child(packet, "error"))) {
2690 PurpleXmlNode *t = purple_xmlnode_get_child_with_namespace(error, "text", NS_XMPP_STANZAS);
2691 if (t)
2692 cdata = purple_xmlnode_get_data(t);
2694 code = purple_xmlnode_get_attrib(error, "code");
2696 /* Stanza errors */
2697 if(purple_xmlnode_get_child(error, "bad-request")) {
2698 text = _("Bad Request");
2699 } else if(purple_xmlnode_get_child(error, "conflict")) {
2700 SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE);
2701 text = _("Conflict");
2702 } else if(purple_xmlnode_get_child(error, "feature-not-implemented")) {
2703 text = _("Feature Not Implemented");
2704 } else if(purple_xmlnode_get_child(error, "forbidden")) {
2705 text = _("Forbidden");
2706 } else if(purple_xmlnode_get_child(error, "gone")) {
2707 text = _("Gone");
2708 } else if(purple_xmlnode_get_child(error, "internal-server-error")) {
2709 text = _("Internal Server Error");
2710 } else if(purple_xmlnode_get_child(error, "item-not-found")) {
2711 text = _("Item Not Found");
2712 } else if(purple_xmlnode_get_child(error, "jid-malformed")) {
2713 text = _("Malformed XMPP ID");
2714 } else if(purple_xmlnode_get_child(error, "not-acceptable")) {
2715 text = _("Not Acceptable");
2716 } else if(purple_xmlnode_get_child(error, "not-allowed")) {
2717 text = _("Not Allowed");
2718 } else if(purple_xmlnode_get_child(error, "not-authorized")) {
2719 text = _("Not Authorized");
2720 } else if(purple_xmlnode_get_child(error, "payment-required")) {
2721 text = _("Payment Required");
2722 } else if(purple_xmlnode_get_child(error, "recipient-unavailable")) {
2723 text = _("Recipient Unavailable");
2724 } else if(purple_xmlnode_get_child(error, "redirect")) {
2725 /* XXX */
2726 } else if(purple_xmlnode_get_child(error, "registration-required")) {
2727 text = _("Registration Required");
2728 } else if(purple_xmlnode_get_child(error, "remote-server-not-found")) {
2729 text = _("Remote Server Not Found");
2730 } else if(purple_xmlnode_get_child(error, "remote-server-timeout")) {
2731 text = _("Remote Server Timeout");
2732 } else if(purple_xmlnode_get_child(error, "resource-constraint")) {
2733 text = _("Server Overloaded");
2734 } else if(purple_xmlnode_get_child(error, "service-unavailable")) {
2735 text = _("Service Unavailable");
2736 } else if(purple_xmlnode_get_child(error, "subscription-required")) {
2737 text = _("Subscription Required");
2738 } else if(purple_xmlnode_get_child(error, "unexpected-request")) {
2739 text = _("Unexpected Request");
2740 } else if(purple_xmlnode_get_child(error, "undefined-condition")) {
2741 text = _("Unknown Error");
2743 } else if(purple_strequal(xmlns, NS_XMPP_SASL)) {
2744 /* Most common reason can be the default */
2745 SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR);
2746 if(purple_xmlnode_get_child(packet, "aborted")) {
2747 text = _("Authorization Aborted");
2748 } else if(purple_xmlnode_get_child(packet, "incorrect-encoding")) {
2749 text = _("Incorrect encoding in authorization");
2750 } else if(purple_xmlnode_get_child(packet, "invalid-authzid")) {
2751 text = _("Invalid authzid");
2752 } else if(purple_xmlnode_get_child(packet, "invalid-mechanism")) {
2753 text = _("Invalid Authorization Mechanism");
2754 } else if(purple_xmlnode_get_child(packet, "mechanism-too-weak")) {
2755 SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE);
2756 text = _("Authorization mechanism too weak");
2757 } else if(purple_xmlnode_get_child(packet, "not-authorized")) {
2758 SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED);
2759 /* Clear the pasword if it isn't being saved */
2760 if (!purple_account_get_remember_password(purple_connection_get_account(js->gc)))
2761 purple_account_set_password(purple_connection_get_account(js->gc), NULL, NULL, NULL);
2762 text = _("Not Authorized");
2763 } else if(purple_xmlnode_get_child(packet, "temporary-auth-failure")) {
2764 text = _("Temporary Authentication Failure");
2765 } else {
2766 SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED);
2767 text = _("Authentication Failure");
2769 } else if(purple_strequal(packet->name, "stream:error") ||
2770 (purple_strequal(packet->name, "error") &&
2771 purple_strequal(xmlns, NS_XMPP_STREAMS))) {
2772 /* Most common reason as default: */
2773 SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR);
2774 if(purple_xmlnode_get_child(packet, "bad-format")) {
2775 text = _("Bad Format");
2776 } else if(purple_xmlnode_get_child(packet, "bad-namespace-prefix")) {
2777 text = _("Bad Namespace Prefix");
2778 } else if(purple_xmlnode_get_child(packet, "conflict")) {
2779 SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE);
2780 text = _("Resource Conflict");
2781 } else if(purple_xmlnode_get_child(packet, "connection-timeout")) {
2782 text = _("Connection Timeout");
2783 } else if(purple_xmlnode_get_child(packet, "host-gone")) {
2784 text = _("Host Gone");
2785 } else if(purple_xmlnode_get_child(packet, "host-unknown")) {
2786 text = _("Host Unknown");
2787 } else if(purple_xmlnode_get_child(packet, "improper-addressing")) {
2788 text = _("Improper Addressing");
2789 } else if(purple_xmlnode_get_child(packet, "internal-server-error")) {
2790 text = _("Internal Server Error");
2791 } else if(purple_xmlnode_get_child(packet, "invalid-id")) {
2792 text = _("Invalid ID");
2793 } else if(purple_xmlnode_get_child(packet, "invalid-namespace")) {
2794 text = _("Invalid Namespace");
2795 } else if(purple_xmlnode_get_child(packet, "invalid-xml")) {
2796 text = _("Invalid XML");
2797 } else if(purple_xmlnode_get_child(packet, "nonmatching-hosts")) {
2798 text = _("Non-matching Hosts");
2799 } else if(purple_xmlnode_get_child(packet, "not-authorized")) {
2800 text = _("Not Authorized");
2801 } else if(purple_xmlnode_get_child(packet, "policy-violation")) {
2802 text = _("Policy Violation");
2803 } else if(purple_xmlnode_get_child(packet, "remote-connection-failed")) {
2804 text = _("Remote Connection Failed");
2805 } else if(purple_xmlnode_get_child(packet, "resource-constraint")) {
2806 text = _("Resource Constraint");
2807 } else if(purple_xmlnode_get_child(packet, "restricted-xml")) {
2808 text = _("Restricted XML");
2809 } else if(purple_xmlnode_get_child(packet, "see-other-host")) {
2810 text = _("See Other Host");
2811 } else if(purple_xmlnode_get_child(packet, "system-shutdown")) {
2812 text = _("System Shutdown");
2813 } else if(purple_xmlnode_get_child(packet, "undefined-condition")) {
2814 text = _("Undefined Condition");
2815 } else if(purple_xmlnode_get_child(packet, "unsupported-encoding")) {
2816 text = _("Unsupported Encoding");
2817 } else if(purple_xmlnode_get_child(packet, "unsupported-stanza-type")) {
2818 text = _("Unsupported Stanza Type");
2819 } else if(purple_xmlnode_get_child(packet, "unsupported-version")) {
2820 text = _("Unsupported Version");
2821 } else if(purple_xmlnode_get_child(packet, "xml-not-well-formed")) {
2822 text = _("XML Not Well Formed");
2823 } else {
2824 text = _("Stream Error");
2828 #undef SET_REASON
2830 if(text || cdata) {
2831 char *ret = g_strdup_printf("%s%s%s", code ? code : "",
2832 code ? ": " : "", text ? text : cdata);
2833 g_free(cdata);
2834 return ret;
2835 } else {
2836 return NULL;
2840 static PurpleCmdRet jabber_cmd_chat_config(PurpleConversation *conv,
2841 const char *cmd, char **args, char **error, void *data)
2843 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
2845 if (!chat)
2846 return PURPLE_CMD_RET_FAILED;
2848 jabber_chat_request_room_configure(chat);
2849 return PURPLE_CMD_RET_OK;
2852 static PurpleCmdRet jabber_cmd_chat_register(PurpleConversation *conv,
2853 const char *cmd, char **args, char **error, void *data)
2855 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
2857 if (!chat)
2858 return PURPLE_CMD_RET_FAILED;
2860 jabber_chat_register(chat);
2861 return PURPLE_CMD_RET_OK;
2864 static PurpleCmdRet jabber_cmd_chat_topic(PurpleConversation *conv,
2865 const char *cmd, char **args, char **error, void *data)
2867 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
2869 if (!chat)
2870 return PURPLE_CMD_RET_FAILED;
2872 if (args && args[0] && *args[0])
2873 jabber_chat_change_topic(chat, args[0]);
2874 else {
2875 const char *cur = purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv));
2876 char *buf, *tmp, *tmp2;
2878 if (cur) {
2879 tmp = g_markup_escape_text(cur, -1);
2880 tmp2 = purple_markup_linkify(tmp);
2881 buf = g_strdup_printf(_("current topic is: %s"), tmp2);
2882 g_free(tmp);
2883 g_free(tmp2);
2884 } else
2885 buf = g_strdup(_("No topic is set"));
2886 purple_conversation_write_system_message(conv, buf, PURPLE_MESSAGE_NO_LOG);
2887 g_free(buf);
2890 return PURPLE_CMD_RET_OK;
2893 static PurpleCmdRet jabber_cmd_chat_nick(PurpleConversation *conv,
2894 const char *cmd, char **args, char **error, void *data)
2896 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
2898 if(!chat || !args || !args[0])
2899 return PURPLE_CMD_RET_FAILED;
2901 if (!jabber_resourceprep_validate(args[0])) {
2902 *error = g_strdup(_("Invalid nickname"));
2903 return PURPLE_CMD_RET_FAILED;
2906 if (jabber_chat_change_nick(chat, args[0]))
2907 return PURPLE_CMD_RET_OK;
2908 else
2909 return PURPLE_CMD_RET_FAILED;
2912 static PurpleCmdRet jabber_cmd_chat_part(PurpleConversation *conv,
2913 const char *cmd, char **args, char **error, void *data)
2915 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
2917 if (!chat)
2918 return PURPLE_CMD_RET_FAILED;
2920 jabber_chat_part(chat, args ? args[0] : NULL);
2921 return PURPLE_CMD_RET_OK;
2924 static PurpleCmdRet jabber_cmd_chat_ban(PurpleConversation *conv,
2925 const char *cmd, char **args, char **error, void *data)
2927 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
2929 if(!chat || !args || !args[0])
2930 return PURPLE_CMD_RET_FAILED;
2932 if(!jabber_chat_ban_user(chat, args[0], args[1])) {
2933 *error = g_strdup_printf(_("Unable to ban user %s"), args[0]);
2934 return PURPLE_CMD_RET_FAILED;
2937 return PURPLE_CMD_RET_OK;
2940 static PurpleCmdRet jabber_cmd_chat_affiliate(PurpleConversation *conv,
2941 const char *cmd, char **args, char **error, void *data)
2943 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
2945 if (!chat || !args || !args[0])
2946 return PURPLE_CMD_RET_FAILED;
2948 if (!purple_strequal(args[0], "owner") &&
2949 !purple_strequal(args[0], "admin") &&
2950 !purple_strequal(args[0], "member") &&
2951 !purple_strequal(args[0], "outcast") &&
2952 !purple_strequal(args[0], "none")) {
2953 *error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[0]);
2954 return PURPLE_CMD_RET_FAILED;
2957 if (args[1]) {
2958 int i;
2959 char **nicks = g_strsplit(args[1], " ", -1);
2961 for (i = 0; nicks[i]; ++i)
2962 if (!jabber_chat_affiliate_user(chat, nicks[i], args[0])) {
2963 *error = g_strdup_printf(_("Unable to affiliate user %s as \"%s\""), nicks[i], args[0]);
2964 g_strfreev(nicks);
2965 return PURPLE_CMD_RET_FAILED;
2968 g_strfreev(nicks);
2969 } else {
2970 jabber_chat_affiliation_list(chat, args[0]);
2973 return PURPLE_CMD_RET_OK;
2976 static PurpleCmdRet jabber_cmd_chat_role(PurpleConversation *conv,
2977 const char *cmd, char **args, char **error, void *data)
2979 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
2981 if (!chat || !args || !args[0])
2982 return PURPLE_CMD_RET_FAILED;
2984 if (!purple_strequal(args[0], "moderator") &&
2985 !purple_strequal(args[0], "participant") &&
2986 !purple_strequal(args[0], "visitor") &&
2987 !purple_strequal(args[0], "none")) {
2988 *error = g_strdup_printf(_("Unknown role: \"%s\""), args[0]);
2989 return PURPLE_CMD_RET_FAILED;
2992 if (args[1]) {
2993 int i;
2994 char **nicks = g_strsplit(args[1], " ", -1);
2996 for (i = 0; nicks[i]; i++)
2997 if (!jabber_chat_role_user(chat, nicks[i], args[0], NULL)) {
2998 *error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
2999 args[0], nicks[i]);
3000 g_strfreev(nicks);
3001 return PURPLE_CMD_RET_FAILED;
3004 g_strfreev(nicks);
3005 } else {
3006 jabber_chat_role_list(chat, args[0]);
3008 return PURPLE_CMD_RET_OK;
3011 static PurpleCmdRet jabber_cmd_chat_invite(PurpleConversation *conv,
3012 const char *cmd, char **args, char **error, void *data)
3014 if(!args || !args[0])
3015 return PURPLE_CMD_RET_FAILED;
3017 jabber_chat_invite(purple_conversation_get_connection(conv),
3018 purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv)), args[1] ? args[1] : "",
3019 args[0]);
3021 return PURPLE_CMD_RET_OK;
3024 static PurpleCmdRet jabber_cmd_chat_join(PurpleConversation *conv,
3025 const char *cmd, char **args, char **error, void *data)
3027 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
3028 GHashTable *components;
3029 JabberID *jid = NULL;
3030 const char *room = NULL, *server = NULL, *handle = NULL;
3032 if (!chat || !args || !args[0])
3033 return PURPLE_CMD_RET_FAILED;
3035 components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3037 if (strchr(args[0], '@'))
3038 jid = jabber_id_new(args[0]);
3039 if (jid) {
3040 room = jid->node;
3041 server = jid->domain;
3042 handle = jid->resource ? jid->resource : chat->handle;
3043 } else {
3044 /* If jabber_id_new failed, the user may have just passed in
3045 * a room name. For backward compatibility, handle that here.
3047 if (strchr(args[0], '@')) {
3048 *error = g_strdup(_("Invalid XMPP ID"));
3049 return PURPLE_CMD_RET_FAILED;
3052 room = args[0];
3053 server = chat->server;
3054 handle = chat->handle;
3057 g_hash_table_insert(components, "room", (gpointer)room);
3058 g_hash_table_insert(components, "server", (gpointer)server);
3059 g_hash_table_insert(components, "handle", (gpointer)handle);
3061 if (args[1])
3062 g_hash_table_insert(components, "password", args[1]);
3064 jabber_chat_join(purple_conversation_get_connection(conv), components);
3066 g_hash_table_destroy(components);
3067 jabber_id_free(jid);
3068 return PURPLE_CMD_RET_OK;
3071 static PurpleCmdRet jabber_cmd_chat_kick(PurpleConversation *conv,
3072 const char *cmd, char **args, char **error, void *data)
3074 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
3076 if(!chat || !args || !args[0])
3077 return PURPLE_CMD_RET_FAILED;
3079 if(!jabber_chat_role_user(chat, args[0], "none", args[1])) {
3080 *error = g_strdup_printf(_("Unable to kick user %s"), args[0]);
3081 return PURPLE_CMD_RET_FAILED;
3084 return PURPLE_CMD_RET_OK;
3087 static PurpleCmdRet jabber_cmd_chat_msg(PurpleConversation *conv,
3088 const char *cmd, char **args, char **error, void *data)
3090 JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv));
3091 char *who;
3093 if (!chat)
3094 return PURPLE_CMD_RET_FAILED;
3096 who = g_strdup_printf("%s@%s/%s", chat->room, chat->server, args[0]);
3098 jabber_message_send_im(purple_conversation_get_connection(conv),
3099 purple_message_new_outgoing(who, args[1], 0));
3101 g_free(who);
3102 return PURPLE_CMD_RET_OK;
3105 static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv,
3106 const char *cmd, char **args, char **error, void *data)
3108 PurpleAccount *account;
3109 PurpleConnection *pc;
3111 if(!args || !args[0])
3112 return PURPLE_CMD_RET_FAILED;
3114 account = purple_conversation_get_account(conv);
3115 pc = purple_account_get_connection(account);
3117 if(!jabber_ping_jid(purple_connection_get_protocol_data(pc), args[0])) {
3118 *error = g_strdup_printf(_("Unable to ping user %s"), args[0]);
3119 return PURPLE_CMD_RET_FAILED;
3122 return PURPLE_CMD_RET_OK;
3125 static gboolean _jabber_send_buzz(JabberStream *js, const char *username, char **error) {
3127 JabberBuddy *jb;
3128 JabberBuddyResource *jbr;
3129 PurpleConnection *gc = js->gc;
3130 PurpleBuddy *buddy =
3131 purple_blist_find_buddy(purple_connection_get_account(gc), username);
3132 const gchar *alias =
3133 buddy ? purple_buddy_get_contact_alias(buddy) : username;
3135 if(!username)
3136 return FALSE;
3138 jb = jabber_buddy_find(js, username, FALSE);
3139 if(!jb) {
3140 *error = g_strdup_printf(_("Unable to buzz, because there is nothing "
3141 "known about %s."), alias);
3142 return FALSE;
3145 jbr = jabber_buddy_find_resource(jb, NULL);
3146 if (!jbr) {
3147 *error = g_strdup_printf(_("Unable to buzz, because %s might be offline."),
3148 alias);
3149 return FALSE;
3152 if (jabber_resource_has_capability(jbr, NS_ATTENTION)) {
3153 PurpleXmlNode *buzz, *msg = purple_xmlnode_new("message");
3154 gchar *to;
3156 to = g_strdup_printf("%s/%s", username, jbr->name);
3157 purple_xmlnode_set_attrib(msg, "to", to);
3158 g_free(to);
3160 /* avoid offline storage */
3161 purple_xmlnode_set_attrib(msg, "type", "headline");
3163 buzz = purple_xmlnode_new_child(msg, "attention");
3164 purple_xmlnode_set_namespace(buzz, NS_ATTENTION);
3166 jabber_send(js, msg);
3167 purple_xmlnode_free(msg);
3169 return TRUE;
3170 } else {
3171 *error = g_strdup_printf(_("Unable to buzz, because %s does "
3172 "not support it or does not wish to receive buzzes now."), alias);
3173 return FALSE;
3177 static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv,
3178 const char *cmd, char **args, char **error, void *data)
3180 PurpleAccount *account = purple_conversation_get_account(conv);
3181 JabberStream *js = purple_connection_get_protocol_data(purple_account_get_connection(account));
3182 const gchar *who;
3183 gchar *description;
3184 PurpleBuddy *buddy;
3185 const char *alias;
3186 PurpleAttentionType *attn =
3187 purple_get_attention_type_from_code(account, 0);
3189 if (!args || !args[0]) {
3190 /* use the buddy from conversation, if it's a one-to-one conversation */
3191 if (PURPLE_IS_IM_CONVERSATION(conv)) {
3192 who = purple_conversation_get_name(conv);
3193 } else {
3194 return PURPLE_CMD_RET_FAILED;
3196 } else {
3197 who = args[0];
3200 buddy = purple_blist_find_buddy(account, who);
3201 if (buddy != NULL)
3202 alias = purple_buddy_get_contact_alias(buddy);
3203 else
3204 alias = who;
3206 description =
3207 g_strdup_printf(purple_attention_type_get_outgoing_desc(attn), alias);
3208 purple_conversation_write_system_message(conv, description,
3209 PURPLE_MESSAGE_NOTIFY);
3210 g_free(description);
3211 return _jabber_send_buzz(js, who, error) ? PURPLE_CMD_RET_OK : PURPLE_CMD_RET_FAILED;
3214 GList *jabber_attention_types(PurpleProtocolAttention *attn, PurpleAccount *account)
3216 static GList *types = NULL;
3218 if (!types) {
3219 types = g_list_append(types, purple_attention_type_new("Buzz", _("Buzz"),
3220 _("%s has buzzed you!"), _("Buzzing %s...")));
3223 return types;
3226 gboolean jabber_send_attention(PurpleProtocolAttention *attn, PurpleConnection *gc, const char *username, guint code)
3228 JabberStream *js = purple_connection_get_protocol_data(gc);
3229 gchar *error = NULL;
3231 if (!_jabber_send_buzz(js, username, &error)) {
3232 PurpleAccount *account = purple_connection_get_account(gc);
3233 PurpleConversation *conv =
3234 purple_conversations_find_with_account(username, account);
3235 purple_debug_error("jabber", "jabber_send_attention: jabber_cmd_buzz failed with error: %s\n", error ? error : "(NULL)");
3237 if (conv) {
3238 purple_conversation_write_system_message(conv,
3239 error, PURPLE_MESSAGE_ERROR);
3242 g_free(error);
3243 return FALSE;
3246 return TRUE;
3250 gboolean jabber_offline_message(const PurpleBuddy *buddy)
3252 return TRUE;
3255 #ifdef USE_VV
3256 gboolean
3257 jabber_audio_enabled(JabberStream *js, const char *namespace)
3259 PurpleMediaManager *manager = purple_media_manager_get();
3260 PurpleMediaCaps caps = purple_media_manager_get_ui_caps(manager);
3262 return (caps & (PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION));
3265 gboolean
3266 jabber_video_enabled(JabberStream *js, const char *namespace)
3268 PurpleMediaManager *manager = purple_media_manager_get();
3269 PurpleMediaCaps caps = purple_media_manager_get_ui_caps(manager);
3271 return (caps & (PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION));
3274 typedef struct {
3275 PurpleAccount *account;
3276 gchar *who;
3277 PurpleMediaSessionType type;
3279 } JabberMediaRequest;
3281 static void
3282 jabber_media_cancel_cb(JabberMediaRequest *request,
3283 PurpleRequestFields *fields)
3285 g_free(request->who);
3286 g_free(request);
3289 static void
3290 jabber_media_ok_cb(JabberMediaRequest *request, PurpleRequestFields *fields)
3292 PurpleRequestField *field =
3293 purple_request_fields_get_field(fields, "resource");
3294 const gchar *selected = purple_request_field_choice_get_value(field);
3295 gchar *who = g_strdup_printf("%s/%s", request->who, selected);
3296 jabber_initiate_media(request->account, who, request->type);
3298 g_free(who);
3299 g_free(request->who);
3300 g_free(request);
3302 #endif
3304 gboolean
3305 jabber_initiate_media(PurpleAccount *account, const char *who,
3306 PurpleMediaSessionType type)
3308 #ifdef USE_VV
3309 PurpleConnection *gc = purple_account_get_connection(account);
3310 JabberStream *js = purple_connection_get_protocol_data(gc);
3311 JabberBuddy *jb;
3312 JabberBuddyResource *jbr = NULL;
3313 char *resource = NULL;
3315 if (!js) {
3316 purple_debug_error("jabber",
3317 "jabber_initiate_media: NULL stream\n");
3318 return FALSE;
3321 jb = jabber_buddy_find(js, who, FALSE);
3323 if(!jb || !jb->resources ||
3324 (((resource = jabber_get_resource(who)) != NULL)
3325 && (jbr = jabber_buddy_find_resource(jb, resource)) == NULL)) {
3326 /* no resources online, we're trying to initiate with someone
3327 * whose presence we're not subscribed to, or
3328 * someone who is offline. Let's inform the user */
3329 char *msg;
3331 if(!jb) {
3332 msg = g_strdup_printf(_("Unable to initiate media with %s: invalid JID"), who);
3333 } else if(jb->subscription & JABBER_SUB_TO && !jb->resources) {
3334 msg = g_strdup_printf(_("Unable to initiate media with %s: user is not online"), who);
3335 } else if(resource) {
3336 msg = g_strdup_printf(_("Unable to initiate media with %s: resource is not online"), who);
3337 } else {
3338 msg = g_strdup_printf(_("Unable to initiate media with %s: not subscribed to user presence"), who);
3341 purple_notify_error(account, _("Media Initiation Failed"),
3342 _("Media Initiation Failed"), msg,
3343 purple_request_cpar_from_connection(gc));
3344 g_free(msg);
3345 g_free(resource);
3346 return FALSE;
3347 } else if(jbr != NULL) {
3348 /* they've specified a resource, no need to ask or
3349 * default or anything, just do it */
3351 g_free(resource);
3353 if (type & PURPLE_MEDIA_AUDIO &&
3354 !jabber_resource_has_capability(jbr,
3355 JINGLE_APP_RTP_SUPPORT_AUDIO) &&
3356 jabber_resource_has_capability(jbr, NS_GOOGLE_VOICE))
3357 return jabber_google_session_initiate(js, who, type);
3358 else
3359 return jingle_rtp_initiate_media(js, who, type);
3360 } else if(!jb->resources->next) {
3361 /* only 1 resource online (probably our most common case)
3362 * so no need to ask who to initiate with */
3363 gchar *name;
3364 gboolean result;
3365 jbr = jb->resources->data;
3366 name = g_strdup_printf("%s/%s", who, jbr->name);
3367 result = jabber_initiate_media(account, name, type);
3368 g_free(name);
3369 return result;
3370 } else {
3371 /* we've got multiple resources,
3372 * we need to pick one to initiate with */
3373 GList *l;
3374 char *msg;
3375 PurpleRequestFields *fields;
3376 PurpleRequestField *field = purple_request_field_choice_new(
3377 "resource", _("Resource"), 0);
3378 PurpleRequestFieldGroup *group;
3379 JabberMediaRequest *request;
3381 purple_request_field_choice_set_data_destructor(field, g_free);
3383 for(l = jb->resources; l; l = l->next)
3385 JabberBuddyResource *ljbr = l->data;
3386 PurpleMediaCaps caps;
3387 gchar *name;
3388 name = g_strdup_printf("%s/%s", who, ljbr->name);
3389 caps = jabber_get_media_caps(account, name);
3390 g_free(name);
3392 if ((type & PURPLE_MEDIA_AUDIO) &&
3393 (type & PURPLE_MEDIA_VIDEO)) {
3394 if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
3395 jbr = ljbr;
3396 purple_request_field_choice_add(field,
3397 jbr->name, g_strdup(jbr->name));
3399 } else if (type & (PURPLE_MEDIA_AUDIO) &&
3400 (caps & PURPLE_MEDIA_CAPS_AUDIO)) {
3401 jbr = ljbr;
3402 purple_request_field_choice_add(field,
3403 jbr->name, g_strdup(jbr->name));
3404 }else if (type & (PURPLE_MEDIA_VIDEO) &&
3405 (caps & PURPLE_MEDIA_CAPS_VIDEO)) {
3406 jbr = ljbr;
3407 purple_request_field_choice_add(field,
3408 jbr->name, g_strdup(jbr->name));
3412 if (jbr == NULL) {
3413 purple_debug_error("jabber",
3414 "No resources available\n");
3415 return FALSE;
3418 if (g_list_length(purple_request_field_choice_get_elements(
3419 field)) <= 2) {
3420 gchar *name;
3421 gboolean result;
3422 purple_request_field_destroy(field);
3423 name = g_strdup_printf("%s/%s", who, jbr->name);
3424 result = jabber_initiate_media(account, name, type);
3425 g_free(name);
3426 return result;
3429 msg = g_strdup_printf(_("Please select the resource of %s with which you would like to start a media session."), who);
3430 fields = purple_request_fields_new();
3431 group = purple_request_field_group_new(NULL);
3432 request = g_new0(JabberMediaRequest, 1);
3433 request->account = account;
3434 request->who = g_strdup(who);
3435 request->type = type;
3437 purple_request_field_group_add_field(group, field);
3438 purple_request_fields_add_group(fields, group);
3439 purple_request_fields(account, _("Select a Resource"), msg,
3440 NULL, fields, _("Initiate Media"),
3441 G_CALLBACK(jabber_media_ok_cb), _("Cancel"),
3442 G_CALLBACK(jabber_media_cancel_cb),
3443 purple_request_cpar_from_account(account),
3444 request);
3446 g_free(msg);
3447 return TRUE;
3449 #endif
3450 return FALSE;
3453 PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who)
3455 #ifdef USE_VV
3456 PurpleConnection *gc = purple_account_get_connection(account);
3457 JabberStream *js = purple_connection_get_protocol_data(gc);
3458 JabberBuddy *jb;
3459 JabberBuddyResource *jbr;
3460 PurpleMediaCaps total = PURPLE_MEDIA_CAPS_NONE;
3461 gchar *resource;
3462 GList *specific = NULL, *l;
3464 if (!js) {
3465 purple_debug_info("jabber",
3466 "jabber_can_do_media: NULL stream\n");
3467 return FALSE;
3470 jb = jabber_buddy_find(js, who, FALSE);
3472 if (!jb || !jb->resources) {
3473 /* no resources online, we're trying to get caps for someone
3474 * whose presence we're not subscribed to, or
3475 * someone who is offline. */
3476 return total;
3478 } else if ((resource = jabber_get_resource(who)) != NULL) {
3479 /* they've specified a resource, no need to ask or
3480 * default or anything, just do it */
3481 jbr = jabber_buddy_find_resource(jb, resource);
3482 g_free(resource);
3484 if (!jbr) {
3485 purple_debug_error("jabber", "jabber_get_media_caps:"
3486 " Can't find resource %s\n", who);
3487 return total;
3490 l = specific = g_list_prepend(specific, jbr);
3492 } else {
3493 /* we've got multiple resources, combine their caps */
3494 l = jb->resources;
3497 for (; l; l = l->next) {
3498 PurpleMediaCaps caps = PURPLE_MEDIA_CAPS_NONE;
3499 jbr = l->data;
3501 if (jabber_resource_has_capability(jbr,
3502 JINGLE_APP_RTP_SUPPORT_AUDIO))
3503 caps |= PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION |
3504 PURPLE_MEDIA_CAPS_AUDIO;
3505 if (jabber_resource_has_capability(jbr,
3506 JINGLE_APP_RTP_SUPPORT_VIDEO))
3507 caps |= PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION |
3508 PURPLE_MEDIA_CAPS_VIDEO;
3509 if (caps & PURPLE_MEDIA_CAPS_AUDIO && caps &
3510 PURPLE_MEDIA_CAPS_VIDEO)
3511 caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO;
3512 if (caps != PURPLE_MEDIA_CAPS_NONE) {
3513 if (!jabber_resource_has_capability(jbr,
3514 JINGLE_TRANSPORT_ICEUDP) &&
3515 !jabber_resource_has_capability(jbr,
3516 JINGLE_TRANSPORT_RAWUDP)) {
3517 purple_debug_info("jingle-rtp", "Buddy doesn't "
3518 "support the same transport types\n");
3519 caps = PURPLE_MEDIA_CAPS_NONE;
3520 } else
3521 caps |= PURPLE_MEDIA_CAPS_MODIFY_SESSION |
3522 PURPLE_MEDIA_CAPS_CHANGE_DIRECTION;
3524 if (jabber_resource_has_capability(jbr, NS_GOOGLE_VOICE)) {
3525 caps |= PURPLE_MEDIA_CAPS_AUDIO;
3526 if (jabber_resource_has_capability(jbr, NS_GOOGLE_VIDEO))
3527 caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO;
3530 total |= caps;
3533 if (specific) {
3534 g_list_free(specific);
3537 return total;
3538 #else
3539 return PURPLE_MEDIA_CAPS_NONE;
3540 #endif
3543 gboolean jabber_can_receive_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who)
3545 JabberStream *js = purple_connection_get_protocol_data(gc);
3547 if (js) {
3548 JabberBuddy *jb = jabber_buddy_find(js, who, FALSE);
3549 GList *iter;
3550 gboolean has_resources_without_caps = FALSE;
3552 /* if we didn't find a JabberBuddy, we don't have presence for this
3553 buddy, let's assume they can receive files, disco should tell us
3554 when actually trying */
3555 if (jb == NULL)
3556 return TRUE;
3558 /* find out if there is any resources without caps */
3559 for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
3560 JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
3562 if (!jabber_resource_know_capabilities(jbr)) {
3563 has_resources_without_caps = TRUE;
3567 if (has_resources_without_caps) {
3568 /* there is at least one resource which we don't have caps for,
3569 let's assume they can receive files... */
3570 return TRUE;
3571 } else {
3572 /* we have caps for all the resources, see if at least one has
3573 right caps */
3574 for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
3575 JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
3577 if (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER)
3578 && (jabber_resource_has_capability(jbr,
3579 NS_BYTESTREAMS)
3580 || jabber_resource_has_capability(jbr, NS_IBB))) {
3581 return TRUE;
3584 return FALSE;
3586 } else {
3587 return TRUE;
3591 static PurpleCmdRet
3592 jabber_cmd_mood(PurpleConversation *conv,
3593 const char *cmd, char **args, char **error, void *data)
3595 PurpleAccount *account = purple_conversation_get_account(conv);
3596 JabberStream *js = purple_connection_get_protocol_data(purple_account_get_connection(account));
3598 if (js->pep) {
3599 gboolean ret;
3601 if (!args || !args[0]) {
3602 /* No arguments; unset mood */
3603 ret = jabber_mood_set(js, NULL, NULL);
3604 } else {
3605 /* At least one argument. Relying on the list of arguments
3606 * being NULL-terminated.
3608 ret = jabber_mood_set(js, args[0], args[1]);
3609 if (!ret) {
3610 /* Let's try again */
3611 char *tmp = g_strjoin(" ", args[0], args[1], NULL);
3612 ret = jabber_mood_set(js, "undefined", tmp);
3613 g_free(tmp);
3617 if (ret) {
3618 return PURPLE_CMD_RET_OK;
3619 } else {
3620 purple_conversation_write_system_message(conv,
3621 _("Failed to specify mood"),
3622 PURPLE_MESSAGE_ERROR);
3623 return PURPLE_CMD_RET_FAILED;
3625 } else {
3626 /* account does not support PEP, can't set a mood */
3627 purple_conversation_write_system_message(conv,
3628 _("Account does not support PEP, can't set mood"),
3629 PURPLE_MESSAGE_ERROR);
3630 return PURPLE_CMD_RET_FAILED;
3634 static void
3635 jabber_register_commands(PurpleProtocol *protocol)
3637 GSList *commands = NULL;
3638 PurpleCmdId id;
3639 const gchar *proto_id = purple_protocol_get_id(protocol);
3641 id = purple_cmd_register("config", "", PURPLE_CMD_P_PROTOCOL,
3642 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id,
3643 jabber_cmd_chat_config, _("config: Configure a chat room."),
3644 NULL);
3645 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3647 id = purple_cmd_register("configure", "", PURPLE_CMD_P_PROTOCOL,
3648 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id,
3649 jabber_cmd_chat_config, _("configure: Configure a chat room."),
3650 NULL);
3651 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3653 id = purple_cmd_register("nick", "s", PURPLE_CMD_P_PROTOCOL,
3654 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id,
3655 jabber_cmd_chat_nick, _("nick &lt;new nickname&gt;: "
3656 "Change your nickname."), NULL);
3657 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3659 id = purple_cmd_register("part", "s", PURPLE_CMD_P_PROTOCOL,
3660 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3661 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_part,
3662 _("part [message]: Leave the room."), NULL);
3663 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3665 id = purple_cmd_register("register", "", PURPLE_CMD_P_PROTOCOL,
3666 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id,
3667 jabber_cmd_chat_register,
3668 _("register: Register with a chat room."), NULL);
3669 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3671 /* XXX: there needs to be a core /topic cmd, methinks */
3672 id = purple_cmd_register("topic", "s", PURPLE_CMD_P_PROTOCOL,
3673 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3674 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_topic,
3675 _("topic [new topic]: View or change the topic."), NULL);
3676 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3678 id = purple_cmd_register("ban", "ws", PURPLE_CMD_P_PROTOCOL,
3679 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3680 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_ban,
3681 _("ban &lt;user&gt; [reason]: Ban a user from the room."),
3682 NULL);
3683 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3685 id = purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PROTOCOL,
3686 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3687 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id,
3688 jabber_cmd_chat_affiliate, _("affiliate "
3689 "&lt;owner|admin|member|outcast|none&gt; [nick1] [nick2] ...: "
3690 "Get the users with an affiliation or set users' affiliation "
3691 "with the room."), NULL);
3692 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3694 id = purple_cmd_register("role", "ws", PURPLE_CMD_P_PROTOCOL,
3695 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3696 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_role,
3697 _("role &lt;moderator|participant|visitor|none&gt; [nick1] "
3698 "[nick2] ...: Get the users with a role or set users' role "
3699 "with the room."), NULL);
3700 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3702 id = purple_cmd_register("invite", "ws", PURPLE_CMD_P_PROTOCOL,
3703 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3704 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_invite,
3705 _("invite &lt;user&gt; [message]: Invite a user to the room."),
3706 NULL);
3707 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3709 id = purple_cmd_register("join", "ws", PURPLE_CMD_P_PROTOCOL,
3710 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3711 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_join,
3712 _("join: &lt;room[@server]&gt; [password]: Join a chat."),
3713 NULL);
3714 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3716 id = purple_cmd_register("kick", "ws", PURPLE_CMD_P_PROTOCOL,
3717 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3718 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_kick,
3719 _("kick &lt;user&gt; [reason]: Kick a user from the room."),
3720 NULL);
3721 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3723 id = purple_cmd_register("msg", "ws", PURPLE_CMD_P_PROTOCOL,
3724 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id,
3725 jabber_cmd_chat_msg, _("msg &lt;user&gt; &lt;message&gt;: "
3726 "Send a private message to another user."), NULL);
3727 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3729 id = purple_cmd_register("ping", "w", PURPLE_CMD_P_PROTOCOL,
3730 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
3731 PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id, jabber_cmd_ping,
3732 _("ping &lt;jid&gt;: Ping a user/component/server."), NULL);
3733 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3735 id = purple_cmd_register("buzz", "w", PURPLE_CMD_P_PROTOCOL,
3736 PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PROTOCOL_ONLY |
3737 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_buzz,
3738 _("buzz: Buzz a user to get their attention"), NULL);
3739 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3741 id = purple_cmd_register("mood", "ws", PURPLE_CMD_P_PROTOCOL,
3742 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
3743 PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
3744 proto_id, jabber_cmd_mood,
3745 _("mood &lt;mood&gt; [text]: Set current user mood"), NULL);
3746 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3748 g_hash_table_insert(jabber_cmds, protocol, commands);
3751 static void cmds_free_func(gpointer value)
3753 GSList *commands = value;
3754 while (commands) {
3755 purple_cmd_unregister(GPOINTER_TO_UINT(commands->data));
3756 commands = g_slist_delete_link(commands, commands);
3760 static void jabber_unregister_commands(PurpleProtocol *protocol)
3762 g_hash_table_remove(jabber_cmds, protocol);
3765 static PurpleAccount *find_acct(const char *protocol, const char *acct_id)
3767 PurpleAccount *acct = NULL;
3769 /* If we have a specific acct, use it */
3770 if (acct_id) {
3771 acct = purple_accounts_find(acct_id, protocol);
3772 if (acct && !purple_account_is_connected(acct))
3773 acct = NULL;
3774 } else { /* Otherwise find an active account for the protocol */
3775 GList *l = purple_accounts_get_all();
3776 while (l) {
3777 if (purple_strequal(protocol, purple_account_get_protocol_id(l->data))
3778 && purple_account_is_connected(l->data)) {
3779 acct = l->data;
3780 break;
3782 l = l->next;
3786 return acct;
3789 static gboolean
3790 xmpp_uri_handler(const char *proto, const char *user, GHashTable *params,
3791 gpointer user_data)
3793 PurpleProtocol *protocol = (PurpleProtocol *)user_data;
3794 const gchar *acct_id = NULL;
3795 PurpleAccount *acct;
3797 g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE);
3799 if (g_ascii_strcasecmp(proto, "xmpp"))
3800 return FALSE;
3802 if (params != NULL) {
3803 acct_id = g_hash_table_lookup(params, "account");
3806 acct = find_acct(protocol->id, acct_id);
3808 if (!acct)
3809 return FALSE;
3811 /* xmpp:romeo@montague.net?message;subject=Test%20Message;body=Here%27s%20a%20test%20message */
3812 /* params is NULL if the URI has no '?' (or anything after it) */
3813 if (!params || g_hash_table_lookup_extended(params, "message", NULL, NULL)) {
3814 if (user && *user) {
3815 PurpleIMConversation *im =
3816 purple_im_conversation_new(acct, user);
3817 const gchar *body = NULL;
3819 purple_conversation_present(PURPLE_CONVERSATION(im));
3821 if (params != NULL) {
3822 body = g_hash_table_lookup(params, "body");
3825 if (body && *body)
3826 purple_conversation_send_confirm(PURPLE_CONVERSATION(im), body);
3827 return TRUE;
3829 } else if (g_hash_table_lookup_extended(params, "roster", NULL, NULL)) {
3830 char *name = g_hash_table_lookup(params, "name");
3831 if (user && *user) {
3832 purple_blist_request_add_buddy(acct, user, NULL, name);
3833 return TRUE;
3835 } else if (g_hash_table_lookup_extended(params, "join", NULL, NULL)) {
3836 PurpleConnection *gc = purple_account_get_connection(acct);
3837 if (user && *user) {
3838 GHashTable *params = jabber_chat_info_defaults(gc, user);
3839 jabber_chat_join(gc, params);
3841 return TRUE;
3844 return FALSE;
3847 static void
3848 jabber_do_init(void)
3850 GHashTable *ui_info = purple_core_get_ui_info();
3851 const gchar *ui_type;
3852 const gchar *type = "pc"; /* default client type, if unknown or
3853 unspecified */
3854 const gchar *ui_name = NULL;
3855 #ifdef HAVE_CYRUS_SASL
3856 /* We really really only want to do this once per process */
3857 static gboolean sasl_initialized = FALSE;
3858 #ifdef _WIN32
3859 UINT old_error_mode;
3860 gchar *sasldir;
3861 #endif
3862 int ret;
3863 #endif
3865 /* XXX - If any other plugin wants SASL this won't be good ... */
3866 #ifdef HAVE_CYRUS_SASL
3867 if (!sasl_initialized) {
3868 sasl_initialized = TRUE;
3869 #ifdef _WIN32
3870 sasldir = g_strdup(wpurple_lib_dir("sasl2"));
3871 sasl_set_path(SASL_PATH_TYPE_PLUGIN, sasldir);
3872 g_free(sasldir);
3873 /* Suppress error popups for failing to load sasl plugins */
3874 old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
3875 #endif
3876 if ((ret = sasl_client_init(NULL)) != SASL_OK) {
3877 purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret);
3879 #ifdef _WIN32
3880 /* Restore the original error mode */
3881 SetErrorMode(old_error_mode);
3882 #endif
3884 #endif
3886 jabber_cmds = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, cmds_free_func);
3888 ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL;
3889 if (ui_type) {
3890 if (purple_strequal(ui_type, "pc") ||
3891 purple_strequal(ui_type, "console") ||
3892 purple_strequal(ui_type, "phone") ||
3893 purple_strequal(ui_type, "handheld") ||
3894 purple_strequal(ui_type, "web") ||
3895 purple_strequal(ui_type, "bot")) {
3896 type = ui_type;
3900 if (ui_info)
3901 ui_name = g_hash_table_lookup(ui_info, "name");
3902 if (ui_name == NULL)
3903 ui_name = PACKAGE;
3905 jabber_add_identity("client", type, NULL, ui_name);
3907 /* initialize jabber_features list */
3908 jabber_add_feature(NS_LAST_ACTIVITY, NULL);
3909 jabber_add_feature(NS_OOB_IQ_DATA, NULL);
3910 jabber_add_feature(NS_ENTITY_TIME, NULL);
3911 jabber_add_feature("jabber:iq:version", NULL);
3912 jabber_add_feature("jabber:x:conference", NULL);
3913 jabber_add_feature(NS_BYTESTREAMS, NULL);
3914 jabber_add_feature("http://jabber.org/protocol/caps", NULL);
3915 jabber_add_feature("http://jabber.org/protocol/chatstates", NULL);
3916 jabber_add_feature(NS_DISCO_INFO, NULL);
3917 jabber_add_feature(NS_DISCO_ITEMS, NULL);
3918 jabber_add_feature(NS_IBB, NULL);
3919 jabber_add_feature("http://jabber.org/protocol/muc", NULL);
3920 jabber_add_feature("http://jabber.org/protocol/muc#user", NULL);
3921 jabber_add_feature("http://jabber.org/protocol/si", NULL);
3922 jabber_add_feature(NS_SI_FILE_TRANSFER, NULL);
3923 jabber_add_feature(NS_XHTML_IM, NULL);
3924 jabber_add_feature(NS_PING, NULL);
3926 /* Buzz/Attention */
3927 jabber_add_feature(NS_ATTENTION, jabber_buzz_isenabled);
3929 /* Bits Of Binary */
3930 jabber_add_feature(NS_BOB, NULL);
3932 /* Jingle features! */
3933 jabber_add_feature(JINGLE, NULL);
3935 #ifdef USE_VV
3936 jabber_add_feature(NS_GOOGLE_PROTOCOL_SESSION, jabber_audio_enabled);
3937 jabber_add_feature(NS_GOOGLE_VOICE, jabber_audio_enabled);
3938 jabber_add_feature(NS_GOOGLE_VIDEO, jabber_video_enabled);
3939 jabber_add_feature(NS_GOOGLE_CAMERA, jabber_video_enabled);
3940 jabber_add_feature(JINGLE_APP_RTP, NULL);
3941 jabber_add_feature(JINGLE_APP_RTP_SUPPORT_AUDIO, jabber_audio_enabled);
3942 jabber_add_feature(JINGLE_APP_RTP_SUPPORT_VIDEO, jabber_video_enabled);
3943 jabber_add_feature(JINGLE_TRANSPORT_RAWUDP, NULL);
3944 jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, NULL);
3946 g_signal_connect(G_OBJECT(purple_media_manager_get()), "ui-caps-changed",
3947 G_CALLBACK(jabber_caps_broadcast_change), NULL);
3948 #endif
3950 /* reverse order of unload_plugin */
3951 jabber_iq_init();
3952 jabber_presence_init();
3953 jabber_caps_init();
3954 /* PEP things should be init via jabber_pep_init, not here */
3955 jabber_pep_init();
3956 jabber_data_init();
3957 jabber_bosh_init();
3959 /* TODO: Implement adding and retrieving own features via IPC API */
3961 jabber_ibb_init();
3962 jabber_si_init();
3964 jabber_auth_init();
3967 static void
3968 jabber_do_uninit(void)
3970 /* reverse order of jabber_do_init */
3971 jabber_bosh_uninit();
3972 jabber_data_uninit();
3973 jabber_si_uninit();
3974 jabber_ibb_uninit();
3975 /* PEP things should be uninit via jabber_pep_uninit, not here */
3976 jabber_pep_uninit();
3977 jabber_caps_uninit();
3978 jabber_presence_uninit();
3979 jabber_iq_uninit();
3981 #ifdef USE_VV
3982 g_signal_handlers_disconnect_by_func(G_OBJECT(purple_media_manager_get()),
3983 G_CALLBACK(jabber_caps_broadcast_change), NULL);
3984 #endif
3986 jabber_auth_uninit();
3987 jabber_features_destroy();
3988 jabber_identities_destroy();
3990 g_hash_table_destroy(jabber_cmds);
3991 jabber_cmds = NULL;
3994 static void jabber_init_protocol(PurpleProtocol *protocol)
3996 ++plugin_ref;
3998 if (plugin_ref == 1)
3999 jabber_do_init();
4001 jabber_register_commands(protocol);
4003 purple_signal_register(protocol, "jabber-register-namespace-watcher",
4004 purple_marshal_VOID__POINTER_POINTER,
4005 G_TYPE_NONE, 2,
4006 G_TYPE_STRING, /* node */
4007 G_TYPE_STRING); /* namespace */
4009 purple_signal_register(protocol, "jabber-unregister-namespace-watcher",
4010 purple_marshal_VOID__POINTER_POINTER,
4011 G_TYPE_NONE, 2,
4012 G_TYPE_STRING, /* node */
4013 G_TYPE_STRING); /* namespace */
4015 purple_signal_connect(protocol, "jabber-register-namespace-watcher",
4016 protocol, PURPLE_CALLBACK(jabber_iq_signal_register), NULL);
4017 purple_signal_connect(protocol, "jabber-unregister-namespace-watcher",
4018 protocol, PURPLE_CALLBACK(jabber_iq_signal_unregister), NULL);
4021 purple_signal_register(protocol, "jabber-receiving-xmlnode",
4022 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
4023 PURPLE_TYPE_CONNECTION,
4024 G_TYPE_POINTER); /* pointer to a PurpleXmlNode* */
4026 purple_signal_register(protocol, "jabber-sending-xmlnode",
4027 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
4028 PURPLE_TYPE_CONNECTION,
4029 G_TYPE_POINTER); /* pointer to a PurpleXmlNode* */
4032 * Do not remove this or the plugin will fail. Completely. You have been
4033 * warned!
4035 purple_signal_connect_priority(protocol, "jabber-sending-xmlnode",
4036 protocol, PURPLE_CALLBACK(jabber_send_signal_cb),
4037 NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
4039 purple_signal_register(protocol, "jabber-sending-text",
4040 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
4041 PURPLE_TYPE_CONNECTION,
4042 G_TYPE_POINTER); /* pointer to a string */
4044 purple_signal_register(protocol, "jabber-receiving-message",
4045 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER_POINTER,
4046 G_TYPE_BOOLEAN, 6,
4047 PURPLE_TYPE_CONNECTION,
4048 G_TYPE_STRING, /* type */
4049 G_TYPE_STRING, /* id */
4050 G_TYPE_STRING, /* from */
4051 G_TYPE_STRING, /* to */
4052 PURPLE_TYPE_XMLNODE);
4054 purple_signal_register(protocol, "jabber-receiving-iq",
4055 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
4056 G_TYPE_BOOLEAN, 5,
4057 PURPLE_TYPE_CONNECTION,
4058 G_TYPE_STRING, /* type */
4059 G_TYPE_STRING, /* id */
4060 G_TYPE_STRING, /* from */
4061 PURPLE_TYPE_XMLNODE);
4063 purple_signal_register(protocol, "jabber-watched-iq",
4064 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
4065 G_TYPE_BOOLEAN, 5,
4066 PURPLE_TYPE_CONNECTION,
4067 G_TYPE_STRING, /* type */
4068 G_TYPE_STRING, /* id */
4069 G_TYPE_STRING, /* from */
4070 PURPLE_TYPE_XMLNODE); /* child */
4072 purple_signal_register(protocol, "jabber-receiving-presence",
4073 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER,
4074 G_TYPE_BOOLEAN, 4,
4075 PURPLE_TYPE_CONNECTION,
4076 G_TYPE_STRING, /* type */
4077 G_TYPE_STRING, /* from */
4078 PURPLE_TYPE_XMLNODE);
4081 static void jabber_uninit_protocol(PurpleProtocol *protocol)
4083 g_return_if_fail(plugin_ref > 0);
4085 purple_signals_unregister_by_instance(protocol);
4086 jabber_unregister_commands(protocol);
4088 --plugin_ref;
4089 if (plugin_ref == 0)
4090 jabber_do_uninit();
4093 static void
4094 jabber_protocol_init(JabberProtocol *self)
4096 PurpleProtocol *protocol = PURPLE_PROTOCOL(self);
4098 protocol->id = "prpl-jabber";
4099 protocol->name = "XMPP";
4100 protocol->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
4101 OPT_PROTO_MAIL_CHECK |
4102 #ifdef HAVE_CYRUS_SASL
4103 OPT_PROTO_PASSWORD_OPTIONAL |
4104 #endif
4105 OPT_PROTO_SLASH_COMMANDS_NATIVE;
4107 protocol->icon_spec = purple_buddy_icon_spec_new("png",
4108 32, 32, 96, 96, 0,
4109 PURPLE_ICON_SCALE_SEND |
4110 PURPLE_ICON_SCALE_DISPLAY);
4113 static void
4114 jabber_protocol_class_init(JabberProtocolClass *klass)
4116 PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass);
4118 protocol_class->login = jabber_login;
4119 protocol_class->close = jabber_close;
4120 protocol_class->status_types = jabber_status_types;
4121 protocol_class->list_icon = jabber_list_icon;
4124 static void
4125 jabber_protocol_class_finalize(G_GNUC_UNUSED JabberProtocolClass *klass)
4129 static void
4130 jabber_protocol_client_iface_init(PurpleProtocolClientInterface *client_iface)
4132 client_iface->get_actions = jabber_get_actions;
4133 client_iface->list_emblem = jabber_list_emblem;
4134 client_iface->status_text = jabber_status_text;
4135 client_iface->tooltip_text = jabber_tooltip_text;
4136 client_iface->blist_node_menu = jabber_blist_node_menu;
4137 client_iface->convo_closed = jabber_convo_closed;
4138 client_iface->normalize = jabber_normalize;
4139 client_iface->find_blist_chat = jabber_find_blist_chat;
4140 client_iface->offline_message = jabber_offline_message;
4141 client_iface->get_moods = jabber_get_moods;
4144 static void
4145 jabber_protocol_server_iface_init(PurpleProtocolServerInterface *server_iface)
4147 server_iface->register_user = jabber_register_account;
4148 server_iface->unregister_user = jabber_unregister_account;
4149 server_iface->set_info = jabber_set_info;
4150 server_iface->get_info = jabber_buddy_get_info;
4151 server_iface->set_status = jabber_set_status;
4152 server_iface->set_idle = jabber_idle_set;
4153 server_iface->add_buddy = jabber_roster_add_buddy;
4154 server_iface->remove_buddy = jabber_roster_remove_buddy;
4155 server_iface->keepalive = jabber_keepalive;
4156 server_iface->get_keepalive_interval = jabber_get_keepalive_interval;
4157 server_iface->alias_buddy = jabber_roster_alias_change;
4158 server_iface->group_buddy = jabber_roster_group_change;
4159 server_iface->rename_group = jabber_roster_group_rename;
4160 server_iface->set_buddy_icon = jabber_set_buddy_icon;
4161 server_iface->send_raw = jabber_protocol_send_raw;
4164 static void
4165 jabber_protocol_im_iface_init(PurpleProtocolIMInterface *im_iface)
4167 im_iface->send = jabber_message_send_im;
4168 im_iface->send_typing = jabber_send_typing;
4171 static void
4172 jabber_protocol_chat_iface_init(PurpleProtocolChatInterface *chat_iface)
4174 chat_iface->info = jabber_chat_info;
4175 chat_iface->info_defaults = jabber_chat_info_defaults;
4176 chat_iface->join = jabber_chat_join;
4177 chat_iface->get_name = jabber_get_chat_name;
4178 chat_iface->invite = jabber_chat_invite;
4179 chat_iface->leave = jabber_chat_leave;
4180 chat_iface->send = jabber_message_send_chat;
4181 chat_iface->get_user_real_name = jabber_chat_user_real_name;
4182 chat_iface->set_topic = jabber_chat_set_topic;
4185 static void
4186 jabber_protocol_privacy_iface_init(PurpleProtocolPrivacyInterface *privacy_iface)
4188 privacy_iface->add_deny = jabber_add_deny;
4189 privacy_iface->rem_deny = jabber_rem_deny;
4192 static void
4193 jabber_protocol_roomlist_iface_init(PurpleProtocolRoomlistInterface *roomlist_iface)
4195 roomlist_iface->get_list = jabber_roomlist_get_list;
4196 roomlist_iface->cancel = jabber_roomlist_cancel;
4197 roomlist_iface->room_serialize = jabber_roomlist_room_serialize;
4200 static void
4201 jabber_protocol_attention_iface_init(PurpleProtocolAttentionInterface *iface)
4203 iface->send = jabber_send_attention;
4204 iface->get_types = jabber_attention_types;
4207 static void
4208 jabber_protocol_media_iface_init(PurpleProtocolMediaInterface *media_iface)
4210 media_iface->initiate_session = jabber_initiate_media;
4211 media_iface->get_caps = jabber_get_media_caps;
4214 static void
4215 jabber_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
4217 xfer_iface->can_receive = jabber_can_receive_file;
4218 xfer_iface->send_file = jabber_si_xfer_send;
4219 xfer_iface->new_xfer = jabber_si_new_xfer;
4222 G_DEFINE_DYNAMIC_TYPE_EXTENDED(
4223 JabberProtocol, jabber_protocol, PURPLE_TYPE_PROTOCOL,
4224 G_TYPE_FLAG_ABSTRACT,
4226 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CLIENT,
4227 jabber_protocol_client_iface_init)
4229 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_SERVER,
4230 jabber_protocol_server_iface_init)
4232 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_IM,
4233 jabber_protocol_im_iface_init)
4235 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CHAT,
4236 jabber_protocol_chat_iface_init)
4238 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_PRIVACY,
4239 jabber_protocol_privacy_iface_init)
4241 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ROOMLIST,
4242 jabber_protocol_roomlist_iface_init)
4244 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ATTENTION,
4245 jabber_protocol_attention_iface_init)
4247 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_MEDIA,
4248 jabber_protocol_media_iface_init)
4250 G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_XFER,
4251 jabber_protocol_xfer_iface_init));
4253 static PurplePluginInfo *
4254 plugin_query(GError **error)
4256 return purple_plugin_info_new(
4257 "id", "prpl-xmpp",
4258 "name", "XMPP Protocols",
4259 "version", DISPLAY_VERSION,
4260 "category", N_("Protocol"),
4261 "summary", N_("XMPP and GTalk Protocols Plugin"),
4262 "description", N_("XMPP and GTalk Protocols Plugin"),
4263 "website", PURPLE_WEBSITE,
4264 "abi-version", PURPLE_ABI_VERSION,
4265 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
4266 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
4267 NULL
4271 static gboolean
4272 plugin_load(PurplePlugin *plugin, GError **error)
4274 jingle_session_register(plugin);
4276 jingle_transport_register(plugin);
4277 jingle_iceudp_register(plugin);
4278 jingle_rawudp_register(plugin);
4279 jingle_google_p2p_register(plugin);
4281 jingle_content_register(plugin);
4282 #ifdef USE_VV
4283 jingle_rtp_register(plugin);
4284 #endif
4286 jabber_protocol_register_type(G_TYPE_MODULE(plugin));
4288 gtalk_protocol_register(plugin);
4289 xmpp_protocol_register(plugin);
4291 jabber_si_xfer_register(G_TYPE_MODULE(plugin));
4293 xmpp_protocol = purple_protocols_add(XMPP_TYPE_PROTOCOL, error);
4294 if (!xmpp_protocol)
4295 return FALSE;
4297 gtalk_protocol = purple_protocols_add(GTALK_TYPE_PROTOCOL, error);
4298 if (!gtalk_protocol)
4299 return FALSE;
4301 purple_signal_connect(purple_get_core(), "uri-handler", xmpp_protocol,
4302 PURPLE_CALLBACK(xmpp_uri_handler), xmpp_protocol);
4303 purple_signal_connect(purple_get_core(), "uri-handler", gtalk_protocol,
4304 PURPLE_CALLBACK(xmpp_uri_handler), gtalk_protocol);
4306 jabber_init_protocol(xmpp_protocol);
4307 jabber_init_protocol(gtalk_protocol);
4309 return TRUE;
4312 static gboolean
4313 plugin_unload(PurplePlugin *plugin, GError **error)
4315 purple_signal_disconnect(purple_get_core(), "uri-handler",
4316 xmpp_protocol, PURPLE_CALLBACK(xmpp_uri_handler));
4317 purple_signal_disconnect(purple_get_core(), "uri-handler",
4318 gtalk_protocol, PURPLE_CALLBACK(xmpp_uri_handler));
4320 jabber_uninit_protocol(gtalk_protocol);
4321 jabber_uninit_protocol(xmpp_protocol);
4323 if (!purple_protocols_remove(gtalk_protocol, error))
4324 return FALSE;
4326 if (!purple_protocols_remove(xmpp_protocol, error))
4327 return FALSE;
4329 return TRUE;
4332 PURPLE_PLUGIN_INIT(jabber, plugin_query, plugin_load, plugin_unload);