merge of '6159afdf4553bcc227296f683b2a61ad41eadabb'
[pidgin-git.git] / libpurple / protocols / jabber / jabber.c
blob618e4629c19ae6d6e4be62c2be785f36420a7e2e
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 "accountopt.h"
27 #include "blist.h"
28 #include "core.h"
29 #include "cmds.h"
30 #include "connection.h"
31 #include "conversation.h"
32 #include "debug.h"
33 #include "dnssrv.h"
34 #include "imgstore.h"
35 #include "message.h"
36 #include "notify.h"
37 #include "pluginpref.h"
38 #include "privacy.h"
39 #include "proxy.h"
40 #include "prpl.h"
41 #include "request.h"
42 #include "server.h"
43 #include "status.h"
44 #include "util.h"
45 #include "version.h"
46 #include "xmlnode.h"
48 #include "auth.h"
49 #include "buddy.h"
50 #include "caps.h"
51 #include "chat.h"
52 #include "data.h"
53 #include "disco.h"
54 #include "google/google.h"
55 #include "google/google_roster.h"
56 #include "google/google_session.h"
57 #include "ibb.h"
58 #include "iq.h"
59 #include "jutil.h"
60 #include "message.h"
61 #include "parser.h"
62 #include "presence.h"
63 #include "jabber.h"
64 #include "roster.h"
65 #include "ping.h"
66 #include "si.h"
67 #include "usermood.h"
68 #include "xdata.h"
69 #include "pep.h"
70 #include "adhoccommands.h"
72 #include "jingle/jingle.h"
73 #include "jingle/rtp.h"
75 #define PING_TIMEOUT 60
76 /* Send a whitespace keepalive to the server if we haven't sent
77 * anything in the last 120 seconds
79 #define DEFAULT_INACTIVITY_TIME 120
81 GList *jabber_features = NULL;
82 GList *jabber_identities = NULL;
84 static GHashTable *jabber_cmds = NULL; /* PurplePlugin * => GSList of ids */
86 static gint plugin_ref = 0;
88 static void jabber_unregister_account_cb(JabberStream *js);
89 static void try_srv_connect(JabberStream *js);
91 static void jabber_stream_init(JabberStream *js)
93 char *open_stream;
95 if (js->stream_id) {
96 g_free(js->stream_id);
97 js->stream_id = NULL;
100 open_stream = g_strdup_printf("<stream:stream to='%s' "
101 "xmlns='" NS_XMPP_CLIENT "' "
102 "xmlns:stream='" NS_XMPP_STREAMS "' "
103 "version='1.0'>",
104 js->user->domain);
105 /* setup the parser fresh for each stream */
106 jabber_parser_setup(js);
107 jabber_send_raw(js, open_stream, -1);
108 js->reinit = FALSE;
109 g_free(open_stream);
112 static void
113 jabber_session_initialized_cb(JabberStream *js, const char *from,
114 JabberIqType type, const char *id,
115 xmlnode *packet, gpointer data)
117 if (type == JABBER_IQ_RESULT) {
118 jabber_disco_items_server(js);
119 if(js->unregistration)
120 jabber_unregister_account_cb(js);
121 } else {
122 purple_connection_error_reason(js->gc,
123 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
124 ("Error initializing session"));
128 static void jabber_session_init(JabberStream *js)
130 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
131 xmlnode *session;
133 jabber_iq_set_callback(iq, jabber_session_initialized_cb, NULL);
135 session = xmlnode_new_child(iq->node, "session");
136 xmlnode_set_namespace(session, NS_XMPP_SESSION);
138 jabber_iq_send(iq);
141 static void jabber_bind_result_cb(JabberStream *js, const char *from,
142 JabberIqType type, const char *id,
143 xmlnode *packet, gpointer data)
145 xmlnode *bind;
147 if (type == JABBER_IQ_RESULT &&
148 (bind = xmlnode_get_child_with_namespace(packet, "bind", NS_XMPP_BIND))) {
149 xmlnode *jid;
150 char *full_jid;
151 if((jid = xmlnode_get_child(bind, "jid")) && (full_jid = xmlnode_get_data(jid))) {
152 jabber_id_free(js->user);
154 js->user = jabber_id_new(full_jid);
155 if (js->user == NULL) {
156 purple_connection_error_reason(js->gc,
157 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
158 _("Invalid response from server"));
159 g_free(full_jid);
160 return;
163 js->user_jb = jabber_buddy_find(js, full_jid, TRUE);
164 js->user_jb->subscription |= JABBER_SUB_BOTH;
166 purple_connection_set_display_name(js->gc, full_jid);
168 g_free(full_jid);
170 } else {
171 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
172 char *msg = jabber_parse_error(js, packet, &reason);
173 purple_connection_error_reason(js->gc, reason, msg);
174 g_free(msg);
176 return;
179 jabber_session_init(js);
182 static char *jabber_prep_resource(char *input) {
183 char hostname[256], /* current hostname */
184 *dot = NULL;
186 /* Empty resource == don't send any */
187 if (input == NULL || *input == '\0')
188 return NULL;
190 if (strstr(input, "__HOSTNAME__") == NULL)
191 return g_strdup(input);
193 /* Replace __HOSTNAME__ with hostname */
194 if (gethostname(hostname, sizeof(hostname) - 1)) {
195 purple_debug_warning("jabber", "gethostname: %s\n", g_strerror(errno));
196 /* according to glibc doc, the only time an error is returned
197 is if the hostname is longer than the buffer, in which case
198 glibc 2.2+ would still fill the buffer with partial
199 hostname, so maybe we want to detect that and use it
200 instead
202 strcpy(hostname, "localhost");
204 hostname[sizeof(hostname) - 1] = '\0';
206 /* We want only the short hostname, not the FQDN - this will prevent the
207 * resource string from being unreasonably long on systems which stuff the
208 * whole FQDN in the hostname */
209 if((dot = strchr(hostname, '.')))
210 *dot = '\0';
212 return purple_strreplace(input, "__HOSTNAME__", hostname);
215 static gboolean
216 jabber_process_starttls(JabberStream *js, xmlnode *packet)
218 PurpleAccount *account;
219 xmlnode *starttls;
221 account = purple_connection_get_account(js->gc);
223 #if 0
225 * This code DOES NOT EXIST, will never be enabled by default, and
226 * will never ever be supported (by me).
227 * It's literally *only* for developer testing.
230 const gchar *connection_security = purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS);
231 if (!g_str_equal(connection_security, "none") &&
232 purple_ssl_is_supported()) {
233 jabber_send_raw(js,
234 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1);
235 return TRUE;
238 #else
239 if(purple_ssl_is_supported()) {
240 jabber_send_raw(js,
241 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1);
242 return TRUE;
243 } else {
244 purple_debug_warning("jabber", "No libpurple TLS/SSL support found.");
246 #endif
248 starttls = xmlnode_get_child(packet, "starttls");
249 if(xmlnode_get_child(starttls, "required")) {
250 purple_connection_error_reason(js->gc,
251 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
252 _("Server requires TLS/SSL, but no TLS/SSL support was found."));
253 return TRUE;
256 if (g_str_equal("require_tls", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
257 purple_connection_error_reason(js->gc,
258 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
259 _("You require encryption, but no TLS/SSL support was found."));
260 return TRUE;
263 return FALSE;
266 void jabber_stream_features_parse(JabberStream *js, xmlnode *packet)
268 PurpleAccount *account = purple_connection_get_account(js->gc);
269 const char *connection_security =
270 purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS);
272 if (xmlnode_get_child(packet, "starttls")) {
273 if (jabber_process_starttls(js, packet)) {
274 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
275 return;
277 } else if (g_str_equal(connection_security, "require_tls") && !jabber_stream_is_ssl(js)) {
278 purple_connection_error_reason(js->gc,
279 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
280 _("You require encryption, but it is not available on this server."));
281 return;
284 if(js->registration) {
285 jabber_register_start(js);
286 } else if(xmlnode_get_child(packet, "mechanisms")) {
287 jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
288 jabber_auth_start(js, packet);
289 } else if(xmlnode_get_child(packet, "bind")) {
290 xmlnode *bind, *resource;
291 char *requested_resource;
292 JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
293 bind = xmlnode_new_child(iq->node, "bind");
294 xmlnode_set_namespace(bind, NS_XMPP_BIND);
295 requested_resource = jabber_prep_resource(js->user->resource);
297 if (requested_resource != NULL) {
298 resource = xmlnode_new_child(bind, "resource");
299 xmlnode_insert_data(resource, requested_resource, -1);
300 g_free(requested_resource);
303 jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL);
305 jabber_iq_send(iq);
306 } else if (xmlnode_get_child_with_namespace(packet, "ver", NS_ROSTER_VERSIONING)) {
307 js->server_caps |= JABBER_CAP_ROSTER_VERSIONING;
308 } else /* if(xmlnode_get_child_with_namespace(packet, "auth")) */ {
309 /* If we get an empty stream:features packet, or we explicitly get
310 * an auth feature with namespace http://jabber.org/features/iq-auth
311 * we should revert back to iq:auth authentication, even though we're
312 * connecting to an XMPP server. */
313 jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
314 jabber_auth_start_old(js);
318 static void jabber_stream_handle_error(JabberStream *js, xmlnode *packet)
320 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
321 char *msg = jabber_parse_error(js, packet, &reason);
323 purple_connection_error_reason(js->gc, reason, msg);
325 g_free(msg);
328 static void tls_init(JabberStream *js);
330 void jabber_process_packet(JabberStream *js, xmlnode **packet)
332 const char *name;
333 const char *xmlns;
335 purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-receiving-xmlnode", js->gc, packet);
337 /* if the signal leaves us with a null packet, we're done */
338 if(NULL == *packet)
339 return;
341 name = (*packet)->name;
342 xmlns = xmlnode_get_namespace(*packet);
344 if(!strcmp((*packet)->name, "iq")) {
345 jabber_iq_parse(js, *packet);
346 } else if(!strcmp((*packet)->name, "presence")) {
347 jabber_presence_parse(js, *packet);
348 } else if(!strcmp((*packet)->name, "message")) {
349 jabber_message_parse(js, *packet);
350 } else if (purple_strequal(xmlns, NS_XMPP_STREAMS)) {
351 if (g_str_equal(name, "features"))
352 jabber_stream_features_parse(js, *packet);
353 else if (g_str_equal(name, "error"))
354 jabber_stream_handle_error(js, *packet);
355 } else if (purple_strequal(xmlns, NS_XMPP_SASL)) {
356 if (js->state != JABBER_STREAM_AUTHENTICATING)
357 purple_debug_warning("jabber", "Ignoring spurious SASL stanza %s\n", name);
358 else {
359 if (g_str_equal(name, "challenge"))
360 jabber_auth_handle_challenge(js, *packet);
361 else if (g_str_equal(name, "success"))
362 jabber_auth_handle_success(js, *packet);
363 else if (g_str_equal(name, "failure"))
364 jabber_auth_handle_failure(js, *packet);
366 } else if (purple_strequal(xmlns, NS_XMPP_TLS)) {
367 if (js->state != JABBER_STREAM_INITIALIZING_ENCRYPTION || js->gsc)
368 purple_debug_warning("jabber", "Ignoring spurious %s\n", name);
369 else {
370 if (g_str_equal(name, "proceed"))
371 tls_init(js);
372 /* TODO: Handle <failure/>, I guess? */
374 } else {
375 purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet)->name);
379 static int jabber_do_send(JabberStream *js, const char *data, int len)
381 int ret;
383 if (js->gsc)
384 ret = purple_ssl_write(js->gsc, data, len);
385 else
386 ret = write(js->fd, data, len);
388 return ret;
391 static void jabber_send_cb(gpointer data, gint source, PurpleInputCondition cond)
393 JabberStream *js = data;
394 int ret, writelen;
395 writelen = purple_circ_buffer_get_max_read(js->write_buffer);
397 if (writelen == 0) {
398 purple_input_remove(js->writeh);
399 js->writeh = 0;
400 return;
403 ret = jabber_do_send(js, js->write_buffer->outptr, writelen);
405 if (ret < 0 && errno == EAGAIN)
406 return;
407 else if (ret <= 0) {
408 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
409 g_strerror(errno));
410 purple_connection_error_reason(js->gc,
411 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
412 g_free(tmp);
413 return;
416 purple_circ_buffer_mark_read(js->write_buffer, ret);
419 static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len)
421 int ret;
422 gboolean success = TRUE;
424 g_return_val_if_fail(len > 0, FALSE);
426 if (js->state == JABBER_STREAM_CONNECTED)
427 jabber_stream_restart_inactivity_timer(js);
429 if (js->writeh == 0)
430 ret = jabber_do_send(js, data, len);
431 else {
432 ret = -1;
433 errno = EAGAIN;
436 if (ret < 0 && errno != EAGAIN) {
437 PurpleAccount *account = purple_connection_get_account(js->gc);
439 * The server may have closed the socket (on a stream error), so if
440 * we're disconnecting, don't generate (possibly another) error that
441 * (for some UIs) would mask the first.
443 if (!account->disconnecting) {
444 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
445 g_strerror(errno));
446 purple_connection_error_reason(js->gc,
447 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
448 g_free(tmp);
451 success = FALSE;
452 } else if (ret < len) {
453 if (ret < 0)
454 ret = 0;
455 if (js->writeh == 0)
456 js->writeh = purple_input_add(
457 js->gsc ? js->gsc->fd : js->fd,
458 PURPLE_INPUT_WRITE, jabber_send_cb, js);
459 purple_circ_buffer_append(js->write_buffer,
460 data + ret, len - ret);
463 return success;
466 void jabber_send_raw(JabberStream *js, const char *data, int len)
468 PurpleConnection *gc;
469 PurpleAccount *account;
471 gc = js->gc;
472 account = purple_connection_get_account(gc);
474 /* because printing a tab to debug every minute gets old */
475 if(strcmp(data, "\t")) {
476 const char *username;
477 char *text = NULL, *last_part = NULL, *tag_start = NULL;
479 /* Because debug logs with plaintext passwords make me sad */
480 if (!purple_debug_is_unsafe() && js->state != JABBER_STREAM_CONNECTED &&
481 /* Either <auth> or <query><password>... */
482 (((tag_start = strstr(data, "<auth ")) &&
483 strstr(data, "xmlns='" NS_XMPP_SASL "'")) ||
484 ((tag_start = strstr(data, "<query ")) &&
485 strstr(data, "xmlns='jabber:iq:auth'>") &&
486 (tag_start = strstr(tag_start, "<password>"))))) {
487 char *data_start, *tag_end = strchr(tag_start, '>');
488 text = g_strdup(data);
490 /* Better to print out some wacky debugging than crash
491 * due to a plugin sending bad xml */
492 if (tag_end == NULL)
493 tag_end = tag_start;
495 data_start = text + (tag_end - data) + 1;
497 last_part = strchr(data_start, '<');
498 *data_start = '\0';
501 username = purple_connection_get_display_name(gc);
502 if (!username)
503 username = purple_account_get_username(account);
505 purple_debug_misc("jabber", "Sending%s (%s): %s%s%s\n",
506 jabber_stream_is_ssl(js) ? " (ssl)" : "", username,
507 text ? text : data,
508 last_part ? "password removed" : "",
509 last_part ? last_part : "");
511 g_free(text);
514 purple_signal_emit(purple_connection_get_prpl(gc), "jabber-sending-text", gc, &data);
515 if (data == NULL)
516 return;
518 if (len == -1)
519 len = strlen(data);
521 /* If we've got a security layer, we need to encode the data,
522 * splitting it on the maximum buffer length negotiated */
523 #ifdef HAVE_CYRUS_SASL
524 if (js->sasl_maxbuf>0) {
525 int pos = 0;
527 if (!js->gsc && js->fd<0)
528 g_return_if_reached();
530 while (pos < len) {
531 int towrite;
532 const char *out;
533 unsigned olen;
534 int rc;
536 towrite = MIN((len - pos), js->sasl_maxbuf);
538 rc = sasl_encode(js->sasl, &data[pos], towrite,
539 &out, &olen);
540 if (rc != SASL_OK) {
541 gchar *error =
542 g_strdup_printf(_("SASL error: %s"),
543 sasl_errdetail(js->sasl));
544 purple_debug_error("jabber",
545 "sasl_encode error %d: %s\n", rc,
546 sasl_errdetail(js->sasl));
547 purple_connection_error_reason(gc,
548 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
549 error);
550 g_free(error);
551 return;
553 pos += towrite;
555 /* do_jabber_send_raw returns FALSE when it throws a
556 * connection error.
558 if (!do_jabber_send_raw(js, out, olen))
559 break;
561 return;
563 #endif
565 if (js->bosh)
566 jabber_bosh_connection_send_raw(js->bosh, data);
567 else
568 do_jabber_send_raw(js, data, len);
571 int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len)
573 JabberStream *js = purple_connection_get_protocol_data(gc);
575 g_return_val_if_fail(js != NULL, -1);
576 /* TODO: It's probably worthwhile to restrict this to when the account
577 * state is CONNECTED, but I can /almost/ envision reasons for wanting
578 * to do things during the connection process.
581 jabber_send_raw(js, buf, len);
582 return len;
585 void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet,
586 gpointer unused)
588 JabberStream *js;
589 char *txt;
590 int len;
592 if (NULL == packet)
593 return;
595 g_return_if_fail(PURPLE_CONNECTION_IS_VALID(pc));
597 js = purple_connection_get_protocol_data(pc);
599 if (NULL == js)
600 return;
602 if (js->bosh)
603 if (g_str_equal((*packet)->name, "message") ||
604 g_str_equal((*packet)->name, "iq") ||
605 g_str_equal((*packet)->name, "presence"))
606 xmlnode_set_namespace(*packet, NS_XMPP_CLIENT);
607 txt = xmlnode_to_str(*packet, &len);
608 jabber_send_raw(js, txt, len);
609 g_free(txt);
612 void jabber_send(JabberStream *js, xmlnode *packet)
614 purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-xmlnode", js->gc, &packet);
617 static gboolean jabber_keepalive_timeout(PurpleConnection *gc)
619 JabberStream *js = gc->proto_data;
620 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
621 _("Ping timed out"));
622 js->keepalive_timeout = 0;
623 return FALSE;
626 void jabber_keepalive(PurpleConnection *gc)
628 JabberStream *js = purple_connection_get_protocol_data(gc);
629 time_t now = time(NULL);
631 if (js->keepalive_timeout == 0 && (now - js->last_ping) >= PING_TIMEOUT) {
632 js->last_ping = now;
634 jabber_keepalive_ping(js);
635 js->keepalive_timeout = purple_timeout_add_seconds(120,
636 (GSourceFunc)(jabber_keepalive_timeout), gc);
640 static void
641 jabber_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc,
642 PurpleInputCondition cond)
644 PurpleConnection *gc = data;
645 JabberStream *js = gc->proto_data;
646 int len;
647 static char buf[4096];
649 /* TODO: It should be possible to make this check unnecessary */
650 if(!PURPLE_CONNECTION_IS_VALID(gc)) {
651 purple_ssl_close(gsc);
652 g_return_if_reached();
655 while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
656 gc->last_received = time(NULL);
657 buf[len] = '\0';
658 purple_debug_info("jabber", "Recv (ssl)(%d): %s\n", len, buf);
659 jabber_parser_process(js, buf, len);
660 if(js->reinit)
661 jabber_stream_init(js);
664 if(len < 0 && errno == EAGAIN)
665 return;
666 else {
667 gchar *tmp;
668 if (len == 0)
669 tmp = g_strdup_printf(_("Server closed the connection"));
670 else
671 tmp = g_strdup_printf(_("Lost connection with server: %s"),
672 g_strerror(errno));
673 purple_connection_error_reason(js->gc,
674 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
675 g_free(tmp);
679 static void
680 jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition)
682 PurpleConnection *gc = data;
683 JabberStream *js = purple_connection_get_protocol_data(gc);
684 int len;
685 static char buf[4096];
687 g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
689 if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
690 gc->last_received = time(NULL);
691 #ifdef HAVE_CYRUS_SASL
692 if (js->sasl_maxbuf > 0) {
693 const char *out;
694 unsigned int olen;
695 int rc;
697 rc = sasl_decode(js->sasl, buf, len, &out, &olen);
698 if (rc != SASL_OK) {
699 gchar *error =
700 g_strdup_printf(_("SASL error: %s"),
701 sasl_errdetail(js->sasl));
702 purple_debug_error("jabber",
703 "sasl_decode_error %d: %s\n", rc,
704 sasl_errdetail(js->sasl));
705 purple_connection_error_reason(gc,
706 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
707 error);
708 } else if (olen > 0) {
709 purple_debug_info("jabber", "RecvSASL (%u): %s\n", olen, out);
710 jabber_parser_process(js, out, olen);
711 if (js->reinit)
712 jabber_stream_init(js);
714 return;
716 #endif
717 buf[len] = '\0';
718 purple_debug_info("jabber", "Recv (%d): %s\n", len, buf);
719 jabber_parser_process(js, buf, len);
720 if(js->reinit)
721 jabber_stream_init(js);
722 } else if(len < 0 && errno == EAGAIN) {
723 return;
724 } else {
725 gchar *tmp;
726 if (len == 0)
727 tmp = g_strdup_printf(_("Server closed the connection"));
728 else
729 tmp = g_strdup_printf(_("Lost connection with server: %s"),
730 g_strerror(errno));
731 purple_connection_error_reason(js->gc,
732 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
733 g_free(tmp);
737 static void
738 jabber_login_callback_ssl(gpointer data, PurpleSslConnection *gsc,
739 PurpleInputCondition cond)
741 PurpleConnection *gc = data;
742 JabberStream *js;
744 /* TODO: It should be possible to make this check unnecessary */
745 if(!PURPLE_CONNECTION_IS_VALID(gc)) {
746 purple_ssl_close(gsc);
747 g_return_if_reached();
750 js = gc->proto_data;
752 if(js->state == JABBER_STREAM_CONNECTING)
753 jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
754 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
755 purple_ssl_input_add(gsc, jabber_recv_cb_ssl, gc);
757 /* Tell the app that we're doing encryption */
758 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
761 static void
762 txt_resolved_cb(GList *responses, gpointer data)
764 JabberStream *js = data;
765 gboolean found = FALSE;
767 js->srv_query_data = NULL;
769 while (responses) {
770 PurpleTxtResponse *resp = responses->data;
771 gchar **token;
772 token = g_strsplit(purple_txt_response_get_content(resp), "=", 2);
773 if (!strcmp(token[0], "_xmpp-client-xbosh")) {
774 purple_debug_info("jabber","Found alternative connection method using %s at %s.\n", token[0], token[1]);
775 js->bosh = jabber_bosh_connection_init(js, token[1]);
776 g_strfreev(token);
777 break;
779 g_strfreev(token);
780 purple_txt_response_destroy(resp);
781 responses = g_list_delete_link(responses, responses);
784 if (js->bosh) {
785 found = TRUE;
786 jabber_bosh_connection_connect(js->bosh);
789 if (!found) {
790 purple_debug_warning("jabber", "Unable to find alternative XMPP connection "
791 "methods after failing to connect directly.\n");
792 purple_connection_error_reason(js->gc,
793 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
794 _("Unable to connect"));
795 return;
798 if (responses) {
799 g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL);
800 g_list_free(responses);
804 static void
805 jabber_login_callback(gpointer data, gint source, const gchar *error)
807 PurpleConnection *gc = data;
808 JabberStream *js = purple_connection_get_protocol_data(gc);
810 if (source < 0) {
811 if (js->srv_rec != NULL) {
812 purple_debug_error("jabber", "Unable to connect to server: %s. Trying next SRV record or connecting directly.\n", error);
813 try_srv_connect(js);
814 } else {
815 purple_debug_info("jabber","Couldn't connect directly to %s. Trying to find alternative connection methods, like BOSH.\n", js->user->domain);
816 js->srv_query_data = purple_txt_resolve("_xmppconnect",
817 js->user->domain, txt_resolved_cb, js);
819 return;
822 g_free(js->srv_rec);
823 js->srv_rec = NULL;
825 js->fd = source;
827 if(js->state == JABBER_STREAM_CONNECTING)
828 jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
830 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
831 gc->inpa = purple_input_add(js->fd, PURPLE_INPUT_READ, jabber_recv_cb, gc);
834 static void
835 jabber_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
836 gpointer data)
838 PurpleConnection *gc = data;
839 JabberStream *js;
841 /* If the connection is already disconnected, we don't need to do anything else */
842 g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
844 js = gc->proto_data;
845 js->gsc = NULL;
847 purple_connection_ssl_error (gc, error);
850 static void tls_init(JabberStream *js)
852 purple_input_remove(js->gc->inpa);
853 js->gc->inpa = 0;
854 js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
855 jabber_login_callback_ssl, jabber_ssl_connect_failure, js->certificate_CN, js->gc);
856 /* The fd is no longer our concern */
857 js->fd = -1;
860 static gboolean jabber_login_connect(JabberStream *js, const char *domain, const char *host, int port,
861 gboolean fatal_failure)
863 /* host should be used in preference to domain to
864 * allow SASL authentication to work with FQDN of the server,
865 * but we use domain as fallback for when users enter IP address
866 * in connect server */
867 g_free(js->serverFQDN);
868 if (purple_ip_address_is_valid(host))
869 js->serverFQDN = g_strdup(domain);
870 else
871 js->serverFQDN = g_strdup(host);
873 if (purple_proxy_connect(js->gc, purple_connection_get_account(js->gc),
874 host, port, jabber_login_callback, js->gc) == NULL) {
875 if (fatal_failure) {
876 purple_connection_error_reason(js->gc,
877 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
878 _("Unable to connect"));
881 return FALSE;
884 return TRUE;
887 static void try_srv_connect(JabberStream *js)
889 while (js->srv_rec != NULL && js->srv_rec_idx < js->max_srv_rec_idx) {
890 PurpleSrvResponse *tmp_resp = js->srv_rec + (js->srv_rec_idx++);
891 if (jabber_login_connect(js, tmp_resp->hostname, tmp_resp->hostname, tmp_resp->port, FALSE))
892 return;
895 g_free(js->srv_rec);
896 js->srv_rec = NULL;
898 /* Fall back to the defaults (I'm not sure if we should actually do this) */
899 jabber_login_connect(js, js->user->domain, js->user->domain,
900 purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222),
901 TRUE);
904 static void srv_resolved_cb(PurpleSrvResponse *resp, int results, gpointer data)
906 JabberStream *js = data;
907 js->srv_query_data = NULL;
909 if(results) {
910 js->srv_rec = resp;
911 js->srv_rec_idx = 0;
912 js->max_srv_rec_idx = results;
913 try_srv_connect(js);
914 } else {
915 jabber_login_connect(js, js->user->domain, js->user->domain,
916 purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222),
917 TRUE);
921 static JabberStream *
922 jabber_stream_new(PurpleAccount *account)
924 PurpleConnection *gc = purple_account_get_connection(account);
925 JabberStream *js;
926 PurplePresence *presence;
927 gchar *user;
928 gchar *slash;
930 js = gc->proto_data = g_new0(JabberStream, 1);
931 js->gc = gc;
932 js->fd = -1;
934 user = g_strdup(purple_account_get_username(account));
935 /* jabber_id_new doesn't accept "user@domain/" as valid */
936 slash = strchr(user, '/');
937 if (slash && *(slash + 1) == '\0')
938 *slash = '\0';
939 js->user = jabber_id_new(user);
941 if (!js->user) {
942 purple_connection_error_reason(gc,
943 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
944 _("Invalid XMPP ID"));
945 g_free(user);
946 /* Destroying the connection will free the JabberStream */
947 return NULL;
950 if (!js->user->node || *(js->user->node) == '\0') {
951 purple_connection_error_reason(gc,
952 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
953 _("Invalid XMPP ID. Username portion must be set."));
954 g_free(user);
955 /* Destroying the connection will free the JabberStream */
956 return NULL;
959 if (!js->user->domain || *(js->user->domain) == '\0') {
960 purple_connection_error_reason(gc,
961 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
962 _("Invalid XMPP ID. Domain must be set."));
963 g_free(user);
964 /* Destroying the connection will free the JabberStream */
965 return NULL;
968 js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal,
969 g_free, (GDestroyNotify)jabber_buddy_free);
971 /* This is overridden during binding, but we need it here
972 * in case the server only does legacy non-sasl auth!.
974 purple_connection_set_display_name(gc, user);
976 js->user_jb = jabber_buddy_find(js, user, TRUE);
977 g_free(user);
978 if (!js->user_jb) {
979 /* This basically *can't* fail, but for good measure... */
980 purple_connection_error_reason(gc,
981 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
982 _("Invalid XMPP ID"));
983 /* Destroying the connection will free the JabberStream */
984 g_return_val_if_reached(NULL);
987 js->user_jb->subscription |= JABBER_SUB_BOTH;
989 js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
990 g_free, g_free);
991 js->chats = g_hash_table_new_full(g_str_hash, g_str_equal,
992 g_free, (GDestroyNotify)jabber_chat_free);
993 js->next_id = g_random_int();
994 js->write_buffer = purple_circ_buffer_new(512);
995 js->old_length = 0;
996 js->keepalive_timeout = 0;
997 js->max_inactivity = DEFAULT_INACTIVITY_TIME;
998 /* Set the default protocol version to 1.0. Overridden in parser.c. */
999 js->protocol_version.major = 1;
1000 js->protocol_version.minor = 0;
1001 js->sessions = NULL;
1002 js->stun_ip = NULL;
1003 js->stun_port = 0;
1004 js->stun_query = NULL;
1005 js->google_relay_token = NULL;
1006 js->google_relay_host = NULL;
1007 js->google_relay_requests = NULL;
1009 /* if we are idle, set idle-ness on the stream (this could happen if we get
1010 disconnected and the reconnects while being idle. I don't think it makes
1011 sense to do this when registering a new account... */
1012 presence = purple_account_get_presence(account);
1013 if (purple_presence_is_idle(presence))
1014 js->idle = purple_presence_get_idle_time(presence);
1016 return js;
1019 static void
1020 jabber_stream_connect(JabberStream *js)
1022 PurpleConnection *gc = js->gc;
1023 PurpleAccount *account = purple_connection_get_account(gc);
1024 const char *connect_server = purple_account_get_string(account,
1025 "connect_server", "");
1026 const char *bosh_url = purple_account_get_string(account,
1027 "bosh_url", "");
1029 jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
1031 /* If both BOSH and a Connect Server are specified, we prefer BOSH. I'm not
1032 * attached to that choice, though.
1034 if (*bosh_url) {
1035 js->bosh = jabber_bosh_connection_init(js, bosh_url);
1036 if (js->bosh)
1037 jabber_bosh_connection_connect(js->bosh);
1038 else {
1039 purple_connection_error_reason(gc,
1040 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1041 _("Malformed BOSH URL"));
1044 return;
1047 js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
1049 /* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
1050 if (g_str_equal("old_ssl", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
1051 if(purple_ssl_is_supported()) {
1052 js->gsc = purple_ssl_connect(account, js->certificate_CN,
1053 purple_account_get_int(account, "port", 5223),
1054 jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
1055 if (!js->gsc) {
1056 purple_connection_error_reason(gc,
1057 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
1058 _("Unable to establish SSL connection"));
1060 } else {
1061 purple_connection_error_reason(gc,
1062 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
1063 _("SSL support unavailable"));
1066 return;
1069 /* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll
1070 * invoke the magic of SRV lookups, to figure out host and port */
1071 if(connect_server[0]) {
1072 jabber_login_connect(js, js->user->domain, connect_server,
1073 purple_account_get_int(account, "port", 5222), TRUE);
1074 } else {
1075 js->srv_query_data = purple_srv_resolve("xmpp-client",
1076 "tcp", js->user->domain, srv_resolved_cb, js);
1080 void
1081 jabber_login(PurpleAccount *account)
1083 PurpleConnection *gc = purple_account_get_connection(account);
1084 JabberStream *js;
1085 PurpleStoredImage *image;
1087 gc->flags |= PURPLE_CONNECTION_HTML |
1088 PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
1089 js = jabber_stream_new(account);
1090 if (js == NULL)
1091 return;
1093 /* TODO: Remove this at some point. Added 2010-02-14 (v2.6.6) */
1094 if (g_str_equal("proxy.jabber.org", purple_account_get_string(account, "ft_proxies", "")))
1095 purple_account_set_string(account, "ft_proxies", JABBER_DEFAULT_FT_PROXIES);
1098 * Calculate the avatar hash for our current image so we know (when we
1099 * fetch our vCard and PEP avatar) if we should send our avatar to the
1100 * server.
1102 image = purple_buddy_icons_find_account_icon(account);
1103 if (image != NULL) {
1104 js->initial_avatar_hash =
1105 jabber_calculate_data_hash(purple_imgstore_get_data(image),
1106 purple_imgstore_get_size(image), "sha1");
1107 purple_imgstore_unref(image);
1110 jabber_stream_connect(js);
1114 static gboolean
1115 conn_close_cb(gpointer data)
1117 JabberStream *js = data;
1118 PurpleAccount *account = purple_connection_get_account(js->gc);
1120 jabber_parser_free(js);
1122 purple_account_disconnect(account);
1124 return FALSE;
1127 static void
1128 jabber_connection_schedule_close(JabberStream *js)
1130 purple_timeout_add(0, conn_close_cb, js);
1133 static void
1134 jabber_registration_result_cb(JabberStream *js, const char *from,
1135 JabberIqType type, const char *id,
1136 xmlnode *packet, gpointer data)
1138 PurpleAccount *account = purple_connection_get_account(js->gc);
1139 char *buf;
1140 char *to = data;
1142 if (type == JABBER_IQ_RESULT) {
1143 if(js->registration) {
1144 buf = g_strdup_printf(_("Registration of %s@%s successful"),
1145 js->user->node, js->user->domain);
1146 if(account->registration_cb)
1147 (account->registration_cb)(account, TRUE, account->registration_cb_user_data);
1148 } else {
1149 g_return_if_fail(to != NULL);
1150 buf = g_strdup_printf(_("Registration to %s successful"),
1151 to);
1153 purple_notify_info(NULL, _("Registration Successful"),
1154 _("Registration Successful"), buf);
1155 g_free(buf);
1156 } else {
1157 char *msg = jabber_parse_error(js, packet, NULL);
1159 if(!msg)
1160 msg = g_strdup(_("Unknown Error"));
1162 purple_notify_error(NULL, _("Registration Failed"),
1163 _("Registration Failed"), msg);
1164 g_free(msg);
1165 if(account->registration_cb)
1166 (account->registration_cb)(account, FALSE, account->registration_cb_user_data);
1168 g_free(to);
1169 if(js->registration)
1170 jabber_connection_schedule_close(js);
1173 static void
1174 jabber_unregistration_result_cb(JabberStream *js, const char *from,
1175 JabberIqType type, const char *id,
1176 xmlnode *packet, gpointer data)
1178 char *buf;
1179 char *to = data;
1181 /* This function is never called for unregistering our XMPP account from
1182 * the server, so there should always be a 'to' address. */
1183 g_return_if_fail(to != NULL);
1185 if (type == JABBER_IQ_RESULT) {
1186 buf = g_strdup_printf(_("Registration from %s successfully removed"),
1187 to);
1188 purple_notify_info(NULL, _("Unregistration Successful"),
1189 _("Unregistration Successful"), buf);
1190 g_free(buf);
1191 } else {
1192 char *msg = jabber_parse_error(js, packet, NULL);
1194 if(!msg)
1195 msg = g_strdup(_("Unknown Error"));
1197 purple_notify_error(NULL, _("Unregistration Failed"),
1198 _("Unregistration Failed"), msg);
1199 g_free(msg);
1201 g_free(to);
1204 typedef struct _JabberRegisterCBData {
1205 JabberStream *js;
1206 char *who;
1207 } JabberRegisterCBData;
1209 static void
1210 jabber_register_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
1212 GList *groups, *flds;
1213 xmlnode *query, *y;
1214 JabberIq *iq;
1215 char *username;
1217 iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
1218 query = xmlnode_get_child(iq->node, "query");
1219 if (cbdata->who)
1220 xmlnode_set_attrib(iq->node, "to", cbdata->who);
1222 for(groups = purple_request_fields_get_groups(fields); groups;
1223 groups = groups->next) {
1224 for(flds = purple_request_field_group_get_fields(groups->data);
1225 flds; flds = flds->next) {
1226 PurpleRequestField *field = flds->data;
1227 const char *id = purple_request_field_get_id(field);
1228 if(!strcmp(id,"unregister")) {
1229 gboolean value = purple_request_field_bool_get_value(field);
1230 if(value) {
1231 /* unregister from service. this doesn't include any of the fields, so remove them from the stanza by recreating it
1232 (there's no "remove child" function for xmlnode) */
1233 jabber_iq_free(iq);
1234 iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
1235 query = xmlnode_get_child(iq->node, "query");
1236 if (cbdata->who)
1237 xmlnode_set_attrib(iq->node,"to",cbdata->who);
1238 xmlnode_new_child(query, "remove");
1240 jabber_iq_set_callback(iq, jabber_unregistration_result_cb, cbdata->who);
1242 jabber_iq_send(iq);
1243 g_free(cbdata);
1244 return;
1246 } else {
1247 const char *ids[] = {"username", "password", "name", "email", "nick", "first",
1248 "last", "address", "city", "state", "zip", "phone", "url", "date",
1249 NULL};
1250 const char *value = purple_request_field_string_get_value(field);
1251 int i;
1252 for (i = 0; ids[i]; i++) {
1253 if (!strcmp(id, ids[i]))
1254 break;
1257 if (!ids[i])
1258 continue;
1259 y = xmlnode_new_child(query, ids[i]);
1260 xmlnode_insert_data(y, value, -1);
1261 if(cbdata->js->registration && !strcmp(id, "username")) {
1262 g_free(cbdata->js->user->node);
1263 cbdata->js->user->node = g_strdup(value);
1265 if(cbdata->js->registration && !strcmp(id, "password"))
1266 purple_account_set_password(cbdata->js->gc->account, value);
1271 if(cbdata->js->registration) {
1272 username = g_strdup_printf("%s@%s%s%s", cbdata->js->user->node, cbdata->js->user->domain,
1273 cbdata->js->user->resource ? "/" : "",
1274 cbdata->js->user->resource ? cbdata->js->user->resource : "");
1275 purple_account_set_username(cbdata->js->gc->account, username);
1276 g_free(username);
1279 jabber_iq_set_callback(iq, jabber_registration_result_cb, cbdata->who);
1281 jabber_iq_send(iq);
1282 g_free(cbdata);
1285 static void
1286 jabber_register_cancel_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
1288 PurpleAccount *account = purple_connection_get_account(cbdata->js->gc);
1289 if(account && cbdata->js->registration) {
1290 if(account->registration_cb)
1291 (account->registration_cb)(account, FALSE, account->registration_cb_user_data);
1292 jabber_connection_schedule_close(cbdata->js);
1294 g_free(cbdata->who);
1295 g_free(cbdata);
1298 static void jabber_register_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
1300 xmlnode *query;
1301 JabberIq *iq;
1302 char *to = data;
1304 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
1305 query = xmlnode_get_child(iq->node, "query");
1306 if (to)
1307 xmlnode_set_attrib(iq->node,"to",to);
1309 xmlnode_insert_child(query, result);
1311 jabber_iq_set_callback(iq, jabber_registration_result_cb, to);
1312 jabber_iq_send(iq);
1315 static const struct {
1316 const char *name;
1317 const char *label;
1318 } registration_fields[] = {
1319 { "email", N_("Email") },
1320 { "nick", N_("Nickname") },
1321 { "first", N_("First name") },
1322 { "last", N_("Last name") },
1323 { "address", N_("Address") },
1324 { "city", N_("City") },
1325 { "state", N_("State") },
1326 { "zip", N_("Postal code") },
1327 { "phone", N_("Phone") },
1328 { "url", N_("URL") },
1329 { "date", N_("Date") },
1330 { NULL, NULL }
1333 void jabber_register_parse(JabberStream *js, const char *from, JabberIqType type,
1334 const char *id, xmlnode *query)
1336 PurpleAccount *account = purple_connection_get_account(js->gc);
1337 PurpleRequestFields *fields;
1338 PurpleRequestFieldGroup *group;
1339 PurpleRequestField *field;
1340 xmlnode *x, *y, *node;
1341 char *instructions;
1342 JabberRegisterCBData *cbdata;
1343 gboolean registered = FALSE;
1344 int i;
1346 if (type != JABBER_IQ_RESULT)
1347 return;
1349 if(js->registration) {
1350 /* get rid of the login thingy */
1351 purple_connection_set_state(js->gc, PURPLE_CONNECTED);
1354 if(xmlnode_get_child(query, "registered")) {
1355 registered = TRUE;
1357 if(js->registration) {
1358 purple_notify_error(NULL, _("Already Registered"),
1359 _("Already Registered"), NULL);
1360 if(account->registration_cb)
1361 (account->registration_cb)(account, FALSE, account->registration_cb_user_data);
1362 jabber_connection_schedule_close(js);
1363 return;
1367 if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1368 jabber_x_data_request(js, x, jabber_register_x_data_cb, g_strdup(from));
1369 return;
1371 } else if((x = xmlnode_get_child_with_namespace(query, "x", NS_OOB_X_DATA))) {
1372 xmlnode *url;
1374 if((url = xmlnode_get_child(x, "url"))) {
1375 char *href;
1376 if((href = xmlnode_get_data(url))) {
1377 purple_notify_uri(NULL, href);
1378 g_free(href);
1380 if(js->registration) {
1381 js->gc->wants_to_die = TRUE;
1382 if(account->registration_cb) /* succeeded, but we have no login info */
1383 (account->registration_cb)(account, TRUE, account->registration_cb_user_data);
1384 jabber_connection_schedule_close(js);
1386 return;
1391 /* as a last resort, use the old jabber:iq:register syntax */
1393 fields = purple_request_fields_new();
1394 group = purple_request_field_group_new(NULL);
1395 purple_request_fields_add_group(fields, group);
1397 if((node = xmlnode_get_child(query, "username"))) {
1398 char *data = xmlnode_get_data(node);
1399 if(js->registration)
1400 field = purple_request_field_string_new("username", _("Username"), data ? data : js->user->node, FALSE);
1401 else
1402 field = purple_request_field_string_new("username", _("Username"), data, FALSE);
1404 purple_request_field_group_add_field(group, field);
1405 g_free(data);
1407 if((node = xmlnode_get_child(query, "password"))) {
1408 if(js->registration)
1409 field = purple_request_field_string_new("password", _("Password"),
1410 purple_connection_get_password(js->gc), FALSE);
1411 else {
1412 char *data = xmlnode_get_data(node);
1413 field = purple_request_field_string_new("password", _("Password"), data, FALSE);
1414 g_free(data);
1417 purple_request_field_string_set_masked(field, TRUE);
1418 purple_request_field_group_add_field(group, field);
1421 if((node = xmlnode_get_child(query, "name"))) {
1422 if(js->registration)
1423 field = purple_request_field_string_new("name", _("Name"),
1424 purple_account_get_alias(js->gc->account), FALSE);
1425 else {
1426 char *data = xmlnode_get_data(node);
1427 field = purple_request_field_string_new("name", _("Name"), data, FALSE);
1428 g_free(data);
1430 purple_request_field_group_add_field(group, field);
1433 for (i = 0; registration_fields[i].name != NULL; ++i) {
1434 if ((node = xmlnode_get_child(query, registration_fields[i].name))) {
1435 char *data = xmlnode_get_data(node);
1436 field = purple_request_field_string_new(registration_fields[i].name,
1437 _(registration_fields[i].label),
1438 data, FALSE);
1439 purple_request_field_group_add_field(group, field);
1440 g_free(data);
1444 if(registered) {
1445 field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE);
1446 purple_request_field_group_add_field(group, field);
1449 if((y = xmlnode_get_child(query, "instructions")))
1450 instructions = xmlnode_get_data(y);
1451 else if(registered)
1452 instructions = g_strdup(_("Please fill out the information below "
1453 "to change your account registration."));
1454 else
1455 instructions = g_strdup(_("Please fill out the information below "
1456 "to register your new account."));
1458 cbdata = g_new0(JabberRegisterCBData, 1);
1459 cbdata->js = js;
1460 cbdata->who = g_strdup(from);
1462 if(js->registration)
1463 purple_request_fields(js->gc, _("Register New XMPP Account"),
1464 _("Register New XMPP Account"), instructions, fields,
1465 _("Register"), G_CALLBACK(jabber_register_cb),
1466 _("Cancel"), G_CALLBACK(jabber_register_cancel_cb),
1467 purple_connection_get_account(js->gc), NULL, NULL,
1468 cbdata);
1469 else {
1470 char *title;
1471 g_return_if_fail(from != NULL);
1472 title = registered ? g_strdup_printf(_("Change Account Registration at %s"), from)
1473 :g_strdup_printf(_("Register New Account at %s"), from);
1474 purple_request_fields(js->gc, title,
1475 title, instructions, fields,
1476 (registered ? _("Change Registration") : _("Register")), G_CALLBACK(jabber_register_cb),
1477 _("Cancel"), G_CALLBACK(jabber_register_cancel_cb),
1478 purple_connection_get_account(js->gc), NULL, NULL,
1479 cbdata);
1480 g_free(title);
1483 g_free(instructions);
1486 void jabber_register_start(JabberStream *js)
1488 JabberIq *iq;
1490 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register");
1491 jabber_iq_send(iq);
1494 void jabber_register_gateway(JabberStream *js, const char *gateway) {
1495 JabberIq *iq;
1497 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register");
1498 xmlnode_set_attrib(iq->node, "to", gateway);
1499 jabber_iq_send(iq);
1502 void jabber_register_account(PurpleAccount *account)
1504 JabberStream *js;
1506 js = jabber_stream_new(account);
1507 if (js == NULL)
1508 return;
1510 js->registration = TRUE;
1511 jabber_stream_connect(js);
1514 static void
1515 jabber_unregister_account_iq_cb(JabberStream *js, const char *from,
1516 JabberIqType type, const char *id,
1517 xmlnode *packet, gpointer data)
1519 PurpleAccount *account = purple_connection_get_account(js->gc);
1521 if (type == JABBER_IQ_ERROR) {
1522 char *msg = jabber_parse_error(js, packet, NULL);
1524 purple_notify_error(js->gc, _("Error unregistering account"),
1525 _("Error unregistering account"), msg);
1526 g_free(msg);
1527 if(js->unregistration_cb)
1528 js->unregistration_cb(account, FALSE, js->unregistration_user_data);
1529 } else {
1530 purple_notify_info(js->gc, _("Account successfully unregistered"),
1531 _("Account successfully unregistered"), NULL);
1532 if(js->unregistration_cb)
1533 js->unregistration_cb(account, TRUE, js->unregistration_user_data);
1537 static void jabber_unregister_account_cb(JabberStream *js) {
1538 JabberIq *iq;
1539 xmlnode *query;
1541 g_return_if_fail(js->unregistration);
1543 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
1545 query = xmlnode_get_child_with_namespace(iq->node, "query", "jabber:iq:register");
1547 xmlnode_new_child(query, "remove");
1548 xmlnode_set_attrib(iq->node, "to", js->user->domain);
1550 jabber_iq_set_callback(iq, jabber_unregister_account_iq_cb, NULL);
1551 jabber_iq_send(iq);
1554 void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data) {
1555 PurpleConnection *gc = purple_account_get_connection(account);
1556 JabberStream *js;
1558 if(gc->state != PURPLE_CONNECTED) {
1559 if(gc->state != PURPLE_CONNECTING)
1560 jabber_login(account);
1561 js = gc->proto_data;
1562 js->unregistration = TRUE;
1563 js->unregistration_cb = cb;
1564 js->unregistration_user_data = user_data;
1565 return;
1568 js = gc->proto_data;
1570 if (js->unregistration) {
1571 purple_debug_error("jabber", "Unregistration in process; ignoring duplicate request.\n");
1572 return;
1575 js->unregistration = TRUE;
1576 js->unregistration_cb = cb;
1577 js->unregistration_user_data = user_data;
1579 jabber_unregister_account_cb(js);
1582 /* TODO: As Will pointed out in IRC, after being notified by the core to
1583 * shutdown, we should async. wait for the server to send us the stream
1584 * termination before destorying everything. That seems like it would require
1585 * changing the semantics of prpl->close(), so it's a good idea for 3.0.0.
1587 void jabber_close(PurpleConnection *gc)
1589 JabberStream *js = purple_connection_get_protocol_data(gc);
1591 /* Close all of the open Jingle sessions on this stream */
1592 jingle_terminate_sessions(js);
1594 if (js->bosh)
1595 jabber_bosh_connection_close(js->bosh);
1596 else if ((js->gsc && js->gsc->fd > 0) || js->fd > 0)
1597 jabber_send_raw(js, "</stream:stream>", -1);
1599 if (js->srv_query_data)
1600 purple_srv_cancel(js->srv_query_data);
1602 if(js->gsc) {
1603 purple_ssl_close(js->gsc);
1604 } else if (js->fd > 0) {
1605 if(js->gc->inpa)
1606 purple_input_remove(js->gc->inpa);
1607 close(js->fd);
1610 if (js->bosh)
1611 jabber_bosh_connection_destroy(js->bosh);
1613 jabber_buddy_remove_all_pending_buddy_info_requests(js);
1615 jabber_parser_free(js);
1617 if(js->iq_callbacks)
1618 g_hash_table_destroy(js->iq_callbacks);
1619 if(js->buddies)
1620 g_hash_table_destroy(js->buddies);
1621 if(js->chats)
1622 g_hash_table_destroy(js->chats);
1624 while(js->chat_servers) {
1625 g_free(js->chat_servers->data);
1626 js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers);
1629 while(js->user_directories) {
1630 g_free(js->user_directories->data);
1631 js->user_directories = g_list_delete_link(js->user_directories, js->user_directories);
1634 while(js->bs_proxies) {
1635 JabberBytestreamsStreamhost *sh = js->bs_proxies->data;
1636 g_free(sh->jid);
1637 g_free(sh->host);
1638 g_free(sh->zeroconf);
1639 g_free(sh);
1640 js->bs_proxies = g_list_delete_link(js->bs_proxies, js->bs_proxies);
1643 while(js->url_datas) {
1644 purple_util_fetch_url_cancel(js->url_datas->data);
1645 js->url_datas = g_slist_delete_link(js->url_datas, js->url_datas);
1648 g_free(js->stream_id);
1649 if(js->user)
1650 jabber_id_free(js->user);
1651 g_free(js->initial_avatar_hash);
1652 g_free(js->avatar_hash);
1653 g_free(js->caps_hash);
1655 if (js->write_buffer)
1656 purple_circ_buffer_destroy(js->write_buffer);
1657 if(js->writeh)
1658 purple_input_remove(js->writeh);
1659 if (js->auth_mech && js->auth_mech->dispose)
1660 js->auth_mech->dispose(js);
1661 #ifdef HAVE_CYRUS_SASL
1662 if(js->sasl)
1663 sasl_dispose(&js->sasl);
1664 if(js->sasl_mechs)
1665 g_string_free(js->sasl_mechs, TRUE);
1666 g_free(js->sasl_cb);
1667 /* Note: _not_ g_free. See auth_cyrus.c:jabber_sasl_cb_secret */
1668 free(js->sasl_secret);
1669 #endif
1670 g_free(js->serverFQDN);
1671 while(js->commands) {
1672 JabberAdHocCommands *cmd = js->commands->data;
1673 g_free(cmd->jid);
1674 g_free(cmd->node);
1675 g_free(cmd->name);
1676 g_free(cmd);
1677 js->commands = g_list_delete_link(js->commands, js->commands);
1679 g_free(js->server_name);
1680 g_free(js->certificate_CN);
1681 g_free(js->gmail_last_time);
1682 g_free(js->gmail_last_tid);
1683 g_free(js->old_msg);
1684 g_free(js->old_avatarhash);
1685 g_free(js->old_artist);
1686 g_free(js->old_title);
1687 g_free(js->old_source);
1688 g_free(js->old_uri);
1689 g_free(js->old_track);
1691 if (js->vcard_timer != 0)
1692 purple_timeout_remove(js->vcard_timer);
1694 if (js->keepalive_timeout != 0)
1695 purple_timeout_remove(js->keepalive_timeout);
1696 if (js->inactivity_timer != 0)
1697 purple_timeout_remove(js->inactivity_timer);
1699 g_free(js->srv_rec);
1700 js->srv_rec = NULL;
1702 g_free(js->stun_ip);
1703 js->stun_ip = NULL;
1705 /* cancel DNS query for STUN, if one is ongoing */
1706 if (js->stun_query) {
1707 purple_dnsquery_destroy(js->stun_query);
1708 js->stun_query = NULL;
1711 /* remove Google relay-related stuff */
1712 g_free(js->google_relay_token);
1713 g_free(js->google_relay_host);
1714 if (js->google_relay_requests) {
1715 while (js->google_relay_requests) {
1716 PurpleUtilFetchUrlData *url_data =
1717 (PurpleUtilFetchUrlData *) js->google_relay_requests->data;
1718 purple_util_fetch_url_cancel(url_data);
1719 g_free(url_data);
1720 js->google_relay_requests =
1721 g_list_delete_link(js->google_relay_requests,
1722 js->google_relay_requests);
1726 g_free(js);
1728 gc->proto_data = NULL;
1731 void jabber_stream_set_state(JabberStream *js, JabberStreamState state)
1733 #define JABBER_CONNECT_STEPS ((js->gsc || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) ? 9 : 5)
1735 js->state = state;
1736 switch(state) {
1737 case JABBER_STREAM_OFFLINE:
1738 break;
1739 case JABBER_STREAM_CONNECTING:
1740 purple_connection_update_progress(js->gc, _("Connecting"), 1,
1741 JABBER_CONNECT_STEPS);
1742 break;
1743 case JABBER_STREAM_INITIALIZING:
1744 purple_connection_update_progress(js->gc, _("Initializing Stream"),
1745 js->gsc ? 5 : 2, JABBER_CONNECT_STEPS);
1746 jabber_stream_init(js);
1747 break;
1748 case JABBER_STREAM_INITIALIZING_ENCRYPTION:
1749 purple_connection_update_progress(js->gc, _("Initializing SSL/TLS"),
1750 6, JABBER_CONNECT_STEPS);
1751 break;
1752 case JABBER_STREAM_AUTHENTICATING:
1753 purple_connection_update_progress(js->gc, _("Authenticating"),
1754 js->gsc ? 7 : 3, JABBER_CONNECT_STEPS);
1755 break;
1756 case JABBER_STREAM_POST_AUTH:
1757 purple_connection_update_progress(js->gc, _("Re-initializing Stream"),
1758 (js->gsc ? 8 : 4), JABBER_CONNECT_STEPS);
1760 break;
1761 case JABBER_STREAM_CONNECTED:
1762 /* Send initial presence */
1763 jabber_presence_send(js, TRUE);
1764 /* Start up the inactivity timer */
1765 jabber_stream_restart_inactivity_timer(js);
1767 purple_connection_set_state(js->gc, PURPLE_CONNECTED);
1768 break;
1771 #undef JABBER_CONNECT_STEPS
1774 char *jabber_get_next_id(JabberStream *js)
1776 return g_strdup_printf("purple%x", js->next_id++);
1780 void jabber_idle_set(PurpleConnection *gc, int idle)
1782 JabberStream *js = gc->proto_data;
1784 js->idle = idle ? time(NULL) - idle : idle;
1786 /* send out an updated prescence */
1787 purple_debug_info("jabber", "sending updated presence for idle\n");
1788 jabber_presence_send(js, FALSE);
1791 void jabber_blocklist_parse_push(JabberStream *js, const char *from,
1792 JabberIqType type, const char *id,
1793 xmlnode *child)
1795 JabberIq *result;
1796 xmlnode *item;
1797 PurpleAccount *account;
1798 gboolean is_block;
1800 if (!jabber_is_own_account(js, from)) {
1801 xmlnode *error, *x;
1802 result = jabber_iq_new(js, JABBER_IQ_ERROR);
1803 xmlnode_set_attrib(result->node, "id", id);
1804 if (from)
1805 xmlnode_set_attrib(result->node, "to", from);
1807 error = xmlnode_new_child(result->node, "error");
1808 xmlnode_set_attrib(error, "type", "cancel");
1809 x = xmlnode_new_child(error, "not-allowed");
1810 xmlnode_set_namespace(x, NS_XMPP_STANZAS);
1812 jabber_iq_send(result);
1813 return;
1816 account = purple_connection_get_account(js->gc);
1817 is_block = g_str_equal(child->name, "block");
1819 item = xmlnode_get_child(child, "item");
1820 if (!is_block && item == NULL) {
1821 /* Unblock everyone */
1822 purple_debug_info("jabber", "Received unblock push. Unblocking everyone.\n");
1824 while (account->deny != NULL) {
1825 purple_privacy_deny_remove(account, account->deny->data, TRUE);
1827 } else if (item == NULL) {
1828 /* An empty <block/> is bogus */
1829 xmlnode *error, *x;
1830 result = jabber_iq_new(js, JABBER_IQ_ERROR);
1831 xmlnode_set_attrib(result->node, "id", id);
1833 error = xmlnode_new_child(result->node, "error");
1834 xmlnode_set_attrib(error, "type", "modify");
1835 x = xmlnode_new_child(error, "bad-request");
1836 xmlnode_set_namespace(x, NS_XMPP_STANZAS);
1838 jabber_iq_send(result);
1839 return;
1840 } else {
1841 for ( ; item; item = xmlnode_get_next_twin(item)) {
1842 const char *jid = xmlnode_get_attrib(item, "jid");
1843 if (jid == NULL || *jid == '\0')
1844 continue;
1846 if (is_block)
1847 purple_privacy_deny_add(account, jid, TRUE);
1848 else
1849 purple_privacy_deny_remove(account, jid, TRUE);
1853 result = jabber_iq_new(js, JABBER_IQ_RESULT);
1854 xmlnode_set_attrib(result->node, "id", id);
1855 jabber_iq_send(result);
1858 static void jabber_blocklist_parse(JabberStream *js, const char *from,
1859 JabberIqType type, const char *id,
1860 xmlnode *packet, gpointer data)
1862 xmlnode *blocklist, *item;
1863 PurpleAccount *account;
1865 blocklist = xmlnode_get_child_with_namespace(packet,
1866 "blocklist", NS_SIMPLE_BLOCKING);
1867 account = purple_connection_get_account(js->gc);
1869 if (type == JABBER_IQ_ERROR || blocklist == NULL)
1870 return;
1872 /* This is the only privacy method supported by XEP-0191 */
1873 if (account->perm_deny != PURPLE_PRIVACY_DENY_USERS)
1874 account->perm_deny = PURPLE_PRIVACY_DENY_USERS;
1877 * TODO: When account->deny is something more than a hash table, this can
1878 * be re-written to find the set intersection and difference.
1880 while (account->deny)
1881 purple_privacy_deny_remove(account, account->deny->data, TRUE);
1883 item = xmlnode_get_child(blocklist, "item");
1884 while (item != NULL) {
1885 const char *jid = xmlnode_get_attrib(item, "jid");
1886 purple_privacy_deny_add(account, jid, TRUE);
1887 item = xmlnode_get_next_twin(item);
1891 void jabber_request_block_list(JabberStream *js)
1893 JabberIq *iq;
1894 xmlnode *blocklist;
1896 iq = jabber_iq_new(js, JABBER_IQ_GET);
1898 blocklist = xmlnode_new_child(iq->node, "blocklist");
1899 xmlnode_set_namespace(blocklist, NS_SIMPLE_BLOCKING);
1901 jabber_iq_set_callback(iq, jabber_blocklist_parse, NULL);
1903 jabber_iq_send(iq);
1906 void jabber_add_deny(PurpleConnection *gc, const char *who)
1908 JabberStream *js;
1909 JabberIq *iq;
1910 xmlnode *block, *item;
1912 g_return_if_fail(who != NULL && *who != '\0');
1914 js = purple_connection_get_protocol_data(gc);
1915 if (js == NULL)
1916 return;
1918 if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
1920 jabber_google_roster_add_deny(js, who);
1921 return;
1924 if (!(js->server_caps & JABBER_CAP_BLOCKING))
1926 purple_notify_error(NULL, _("Server doesn't support blocking"),
1927 _("Server doesn't support blocking"), NULL);
1928 return;
1931 iq = jabber_iq_new(js, JABBER_IQ_SET);
1933 block = xmlnode_new_child(iq->node, "block");
1934 xmlnode_set_namespace(block, NS_SIMPLE_BLOCKING);
1936 item = xmlnode_new_child(block, "item");
1937 xmlnode_set_attrib(item, "jid", who);
1939 jabber_iq_send(iq);
1942 void jabber_rem_deny(PurpleConnection *gc, const char *who)
1944 JabberStream *js;
1945 JabberIq *iq;
1946 xmlnode *unblock, *item;
1948 g_return_if_fail(who != NULL && *who != '\0');
1950 js = purple_connection_get_protocol_data(gc);
1951 if (js == NULL)
1952 return;
1954 if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
1956 jabber_google_roster_rem_deny(js, who);
1957 return;
1960 if (!(js->server_caps & JABBER_CAP_BLOCKING))
1961 return;
1963 iq = jabber_iq_new(js, JABBER_IQ_SET);
1965 unblock = xmlnode_new_child(iq->node, "unblock");
1966 xmlnode_set_namespace(unblock, NS_SIMPLE_BLOCKING);
1968 item = xmlnode_new_child(unblock, "item");
1969 xmlnode_set_attrib(item, "jid", who);
1971 jabber_iq_send(iq);
1974 void jabber_add_feature(const char *namespace, JabberFeatureEnabled cb) {
1975 JabberFeature *feat;
1977 g_return_if_fail(namespace != NULL);
1979 feat = g_new0(JabberFeature,1);
1980 feat->namespace = g_strdup(namespace);
1981 feat->is_enabled = cb;
1983 /* try to remove just in case it already exists in the list */
1984 jabber_remove_feature(namespace);
1986 jabber_features = g_list_append(jabber_features, feat);
1989 void jabber_remove_feature(const char *namespace) {
1990 GList *feature;
1991 for(feature = jabber_features; feature; feature = feature->next) {
1992 JabberFeature *feat = (JabberFeature*)feature->data;
1993 if(!strcmp(feat->namespace, namespace)) {
1994 g_free(feat->namespace);
1995 g_free(feature->data);
1996 jabber_features = g_list_delete_link(jabber_features, feature);
1997 break;
2002 static void jabber_features_destroy(void)
2004 while (jabber_features) {
2005 JabberFeature *feature = jabber_features->data;
2006 g_free(feature->namespace);
2007 g_free(feature);
2008 jabber_features = g_list_delete_link(jabber_features, jabber_features);
2012 gint
2013 jabber_identity_compare(gconstpointer a, gconstpointer b)
2015 const JabberIdentity *ac;
2016 const JabberIdentity *bc;
2017 gint cat_cmp;
2018 gint typ_cmp;
2020 ac = a;
2021 bc = b;
2023 if ((cat_cmp = strcmp(ac->category, bc->category)) == 0) {
2024 if ((typ_cmp = strcmp(ac->type, bc->type)) == 0) {
2025 if (!ac->lang && !bc->lang) {
2026 return 0;
2027 } else if (ac->lang && !bc->lang) {
2028 return 1;
2029 } else if (!ac->lang && bc->lang) {
2030 return -1;
2031 } else {
2032 return strcmp(ac->lang, bc->lang);
2034 } else {
2035 return typ_cmp;
2037 } else {
2038 return cat_cmp;
2042 void jabber_add_identity(const gchar *category, const gchar *type,
2043 const gchar *lang, const gchar *name)
2045 GList *identity;
2046 JabberIdentity *ident;
2048 /* both required according to XEP-0030 */
2049 g_return_if_fail(category != NULL);
2050 g_return_if_fail(type != NULL);
2052 /* Check if this identity is already there... */
2053 for (identity = jabber_identities; identity; identity = identity->next) {
2054 JabberIdentity *id = identity->data;
2055 if (g_str_equal(id->category, category) &&
2056 g_str_equal(id->type, type) &&
2057 purple_strequal(id->lang, lang))
2058 return;
2061 ident = g_new0(JabberIdentity, 1);
2062 ident->category = g_strdup(category);
2063 ident->type = g_strdup(type);
2064 ident->lang = g_strdup(lang);
2065 ident->name = g_strdup(name);
2066 jabber_identities = g_list_insert_sorted(jabber_identities, ident,
2067 jabber_identity_compare);
2070 static void jabber_identities_destroy(void)
2072 while (jabber_identities) {
2073 JabberIdentity *id = jabber_identities->data;
2074 g_free(id->category);
2075 g_free(id->type);
2076 g_free(id->lang);
2077 g_free(id->name);
2078 g_free(id);
2079 jabber_identities = g_list_delete_link(jabber_identities, jabber_identities);
2083 gboolean jabber_stream_is_ssl(JabberStream *js)
2085 return (js->bosh && jabber_bosh_connection_is_ssl(js->bosh)) ||
2086 (!js->bosh && js->gsc);
2089 static gboolean
2090 inactivity_cb(gpointer data)
2092 JabberStream *js = data;
2094 /* We want whatever is sent to set this. It's okay because
2095 * the eventloop unsets it via the return FALSE.
2097 js->inactivity_timer = 0;
2099 if (js->bosh)
2100 jabber_bosh_connection_send_keepalive(js->bosh);
2101 else
2102 jabber_send_raw(js, "\t", 1);
2104 return FALSE;
2107 void jabber_stream_restart_inactivity_timer(JabberStream *js)
2109 if (js->inactivity_timer != 0) {
2110 purple_timeout_remove(js->inactivity_timer);
2111 js->inactivity_timer = 0;
2114 g_return_if_fail(js->max_inactivity > 0);
2116 js->inactivity_timer =
2117 purple_timeout_add_seconds(js->max_inactivity,
2118 inactivity_cb, js);
2121 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
2123 return "jabber";
2126 const char* jabber_list_emblem(PurpleBuddy *b)
2128 JabberStream *js;
2129 JabberBuddy *jb = NULL;
2130 PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(b));
2132 if(!gc)
2133 return NULL;
2135 js = gc->proto_data;
2136 if(js)
2137 jb = jabber_buddy_find(js, purple_buddy_get_name(b), FALSE);
2139 if(!PURPLE_BUDDY_IS_ONLINE(b)) {
2140 if(jb && (jb->subscription & JABBER_SUB_PENDING ||
2141 !(jb->subscription & JABBER_SUB_TO)))
2142 return "not-authorized";
2145 if (jb) {
2146 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, NULL);
2147 if (jbr) {
2148 const gchar *client_type =
2149 jabber_resource_get_identity_category_type(jbr, "client");
2151 if (client_type) {
2152 if (strcmp(client_type, "phone") == 0) {
2153 return "mobile";
2154 } else if (strcmp(client_type, "web") == 0) {
2155 return "external";
2156 } else if (strcmp(client_type, "handheld") == 0) {
2157 return "hiptop";
2158 } else if (strcmp(client_type, "bot") == 0) {
2159 return "bot";
2161 /* the default value "pc" falls through and has no emblem */
2166 return NULL;
2169 char *jabber_status_text(PurpleBuddy *b)
2171 char *ret = NULL;
2172 JabberBuddy *jb = NULL;
2173 PurpleAccount *account = purple_buddy_get_account(b);
2174 PurpleConnection *gc = purple_account_get_connection(account);
2176 if (gc && gc->proto_data)
2177 jb = jabber_buddy_find(gc->proto_data, purple_buddy_get_name(b), FALSE);
2179 if(jb && !PURPLE_BUDDY_IS_ONLINE(b) && (jb->subscription & JABBER_SUB_PENDING || !(jb->subscription & JABBER_SUB_TO))) {
2180 ret = g_strdup(_("Not Authorized"));
2181 } else if(jb && !PURPLE_BUDDY_IS_ONLINE(b) && jb->error_msg) {
2182 ret = g_strdup(jb->error_msg);
2183 } else {
2184 PurplePresence *presence = purple_buddy_get_presence(b);
2185 PurpleStatus *status = purple_presence_get_active_status(presence);
2186 const char *message;
2188 if((message = purple_status_get_attr_string(status, "message"))) {
2189 ret = g_markup_escape_text(message, -1);
2190 } else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
2191 PurpleStatus *status = purple_presence_get_status(presence, "tune");
2192 const char *title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
2193 const char *artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
2194 const char *album = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
2195 ret = purple_util_format_song_info(title, artist, album, NULL);
2199 return ret;
2202 static void
2203 jabber_tooltip_add_resource_text(JabberBuddyResource *jbr,
2204 PurpleNotifyUserInfo *user_info, gboolean multiple_resources)
2206 char *text = NULL;
2207 char *res = NULL;
2208 char *label, *value;
2209 const char *state;
2211 if(jbr->status) {
2212 text = g_markup_escape_text(jbr->status, -1);
2215 if(jbr->name)
2216 res = g_strdup_printf(" (%s)", jbr->name);
2218 state = jabber_buddy_state_get_name(jbr->state);
2219 if (text != NULL && !purple_utf8_strcasecmp(state, text)) {
2220 g_free(text);
2221 text = NULL;
2224 label = g_strdup_printf("%s%s", _("Status"), (res ? res : ""));
2225 value = g_strdup_printf("%s%s%s", state, (text ? ": " : ""), (text ? text : ""));
2227 purple_notify_user_info_add_pair(user_info, label, value);
2228 g_free(label);
2229 g_free(value);
2230 g_free(text);
2232 /* if the resource is idle, show that */
2233 /* only show it if there is more than one resource available for
2234 the buddy, since the "general" idleness will be shown anyway,
2235 this way we can see see the idleness of lower-priority resources */
2236 if (jbr->idle && multiple_resources) {
2237 gchar *idle_str =
2238 purple_str_seconds_to_string(time(NULL) - jbr->idle);
2239 label = g_strdup_printf("%s%s", _("Idle"), (res ? res : ""));
2240 purple_notify_user_info_add_pair(user_info, label, idle_str);
2241 g_free(idle_str);
2242 g_free(label);
2244 g_free(res);
2247 void jabber_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
2249 JabberBuddy *jb;
2250 PurpleAccount *account;
2251 PurpleConnection *gc;
2253 g_return_if_fail(b != NULL);
2255 account = purple_buddy_get_account(b);
2256 g_return_if_fail(account != NULL);
2258 gc = purple_account_get_connection(account);
2259 g_return_if_fail(gc != NULL);
2260 g_return_if_fail(gc->proto_data != NULL);
2262 jb = jabber_buddy_find(gc->proto_data, purple_buddy_get_name(b), FALSE);
2264 if(jb) {
2265 JabberBuddyResource *jbr = NULL;
2266 PurplePresence *presence = purple_buddy_get_presence(b);
2267 const char *sub;
2268 GList *l;
2269 const char *mood;
2270 gboolean multiple_resources =
2271 jb->resources && g_list_next(jb->resources);
2272 JabberBuddyResource *top_jbr = jabber_buddy_find_resource(jb, NULL);
2274 /* resource-specific info for the top resource */
2275 if (top_jbr) {
2276 jabber_tooltip_add_resource_text(top_jbr, user_info,
2277 multiple_resources);
2280 for(l=jb->resources; l; l = l->next) {
2281 jbr = l->data;
2282 /* the remaining resources */
2283 if (jbr != top_jbr) {
2284 jabber_tooltip_add_resource_text(jbr, user_info,
2285 multiple_resources);
2289 if (full) {
2290 PurpleStatus *status;
2292 status = purple_presence_get_status(presence, "mood");
2293 mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME);
2294 if(mood && *mood) {
2295 const char *moodtext;
2296 /* find the mood */
2297 PurpleMood *moods = jabber_get_moods(account);
2298 const char *description = NULL;
2300 for (; moods->mood ; moods++) {
2301 if (purple_strequal(moods->mood, mood)) {
2302 description = moods->description;
2303 break;
2307 moodtext = purple_status_get_attr_string(status, PURPLE_MOOD_COMMENT);
2308 if(moodtext && *moodtext) {
2309 char *moodplustext =
2310 g_strdup_printf("%s (%s)", description ? _(description) : mood, moodtext);
2312 purple_notify_user_info_add_pair(user_info, _("Mood"), moodplustext);
2313 g_free(moodplustext);
2314 } else
2315 purple_notify_user_info_add_pair(user_info, _("Mood"),
2316 description ? _(description) : mood);
2318 if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_TUNE)) {
2319 PurpleStatus *tune = purple_presence_get_status(presence, "tune");
2320 const char *title = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
2321 const char *artist = purple_status_get_attr_string(tune, PURPLE_TUNE_ARTIST);
2322 const char *album = purple_status_get_attr_string(tune, PURPLE_TUNE_ALBUM);
2323 char *playing = purple_util_format_song_info(title, artist, album, NULL);
2324 if (playing) {
2325 purple_notify_user_info_add_pair(user_info, _("Now Listening"), playing);
2326 g_free(playing);
2330 if(jb->subscription & JABBER_SUB_FROM) {
2331 if(jb->subscription & JABBER_SUB_TO)
2332 sub = _("Both");
2333 else if(jb->subscription & JABBER_SUB_PENDING)
2334 sub = _("From (To pending)");
2335 else
2336 sub = _("From");
2337 } else {
2338 if(jb->subscription & JABBER_SUB_TO)
2339 sub = _("To");
2340 else if(jb->subscription & JABBER_SUB_PENDING)
2341 sub = _("None (To pending)");
2342 else
2343 sub = _("None");
2346 purple_notify_user_info_add_pair(user_info, _("Subscription"), sub);
2350 if(!PURPLE_BUDDY_IS_ONLINE(b) && jb->error_msg) {
2351 purple_notify_user_info_add_pair(user_info, _("Error"), jb->error_msg);
2356 GList *jabber_status_types(PurpleAccount *account)
2358 PurpleStatusType *type;
2359 GList *types = NULL;
2360 PurpleValue *priority_value;
2361 PurpleValue *buzz_enabled;
2363 priority_value = purple_value_new(PURPLE_TYPE_INT);
2364 purple_value_set_int(priority_value, 1);
2365 buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
2366 purple_value_set_boolean(buzz_enabled, TRUE);
2367 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
2368 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_ONLINE),
2369 NULL, TRUE, TRUE, FALSE,
2370 "priority", _("Priority"), priority_value,
2371 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
2372 "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
2373 "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
2374 "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
2375 "buzz", _("Allow Buzz"), buzz_enabled,
2376 NULL);
2377 types = g_list_prepend(types, type);
2380 type = purple_status_type_new_with_attrs(PURPLE_STATUS_MOOD,
2381 "mood", NULL, TRUE, TRUE, TRUE,
2382 PURPLE_MOOD_NAME, _("Mood Name"), purple_value_new(PURPLE_TYPE_STRING),
2383 PURPLE_MOOD_COMMENT, _("Mood Comment"), purple_value_new(PURPLE_TYPE_STRING),
2384 NULL);
2385 types = g_list_prepend(types, type);
2387 priority_value = purple_value_new(PURPLE_TYPE_INT);
2388 purple_value_set_int(priority_value, 1);
2389 buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
2390 purple_value_set_boolean(buzz_enabled, TRUE);
2391 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE,
2392 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT),
2393 _("Chatty"), TRUE, TRUE, FALSE,
2394 "priority", _("Priority"), priority_value,
2395 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
2396 "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
2397 "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
2398 "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
2399 "buzz", _("Allow Buzz"), buzz_enabled,
2400 NULL);
2401 types = g_list_prepend(types, type);
2403 priority_value = purple_value_new(PURPLE_TYPE_INT);
2404 purple_value_set_int(priority_value, 0);
2405 buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
2406 purple_value_set_boolean(buzz_enabled, TRUE);
2407 type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY,
2408 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY),
2409 NULL, TRUE, TRUE, FALSE,
2410 "priority", _("Priority"), priority_value,
2411 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
2412 "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
2413 "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
2414 "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
2415 "buzz", _("Allow Buzz"), buzz_enabled,
2416 NULL);
2417 types = g_list_prepend(types, type);
2419 priority_value = purple_value_new(PURPLE_TYPE_INT);
2420 purple_value_set_int(priority_value, 0);
2421 buzz_enabled = purple_value_new(PURPLE_TYPE_BOOLEAN);
2422 purple_value_set_boolean(buzz_enabled, TRUE);
2423 type = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY,
2424 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA),
2425 NULL, TRUE, TRUE, FALSE,
2426 "priority", _("Priority"), priority_value,
2427 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
2428 "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
2429 "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
2430 "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
2431 "buzz", _("Allow Buzz"), buzz_enabled,
2432 NULL);
2433 types = g_list_prepend(types, type);
2435 priority_value = purple_value_new(PURPLE_TYPE_INT);
2436 purple_value_set_int(priority_value, 0);
2437 type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE,
2438 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND),
2439 _("Do Not Disturb"), TRUE, TRUE, FALSE,
2440 "priority", _("Priority"), priority_value,
2441 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
2442 "mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
2443 "moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
2444 "nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
2445 NULL);
2446 types = g_list_prepend(types, type);
2449 if(js->protocol_version == JABBER_PROTO_0_9)
2450 "Invisible"
2453 type = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE,
2454 jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE),
2455 NULL, TRUE, TRUE, FALSE,
2456 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
2457 NULL);
2458 types = g_list_prepend(types, type);
2460 type = purple_status_type_new_with_attrs(PURPLE_STATUS_TUNE,
2461 "tune", NULL, FALSE, TRUE, TRUE,
2462 PURPLE_TUNE_ARTIST, _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
2463 PURPLE_TUNE_TITLE, _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
2464 PURPLE_TUNE_ALBUM, _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
2465 PURPLE_TUNE_GENRE, _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
2466 PURPLE_TUNE_COMMENT, _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
2467 PURPLE_TUNE_TRACK, _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
2468 PURPLE_TUNE_TIME, _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
2469 PURPLE_TUNE_YEAR, _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
2470 PURPLE_TUNE_URL, _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
2471 NULL);
2472 types = g_list_prepend(types, type);
2474 return g_list_reverse(types);
2477 static void
2478 jabber_password_change_result_cb(JabberStream *js, const char *from,
2479 JabberIqType type, const char *id,
2480 xmlnode *packet, gpointer data)
2482 if (type == JABBER_IQ_RESULT) {
2483 purple_notify_info(js->gc, _("Password Changed"), _("Password Changed"),
2484 _("Your password has been changed."));
2486 purple_account_set_password(js->gc->account, (char *)data);
2487 } else {
2488 char *msg = jabber_parse_error(js, packet, NULL);
2490 purple_notify_error(js->gc, _("Error changing password"),
2491 _("Error changing password"), msg);
2492 g_free(msg);
2495 g_free(data);
2498 static void jabber_password_change_cb(JabberStream *js,
2499 PurpleRequestFields *fields)
2501 const char *p1, *p2;
2502 JabberIq *iq;
2503 xmlnode *query, *y;
2505 p1 = purple_request_fields_get_string(fields, "password1");
2506 p2 = purple_request_fields_get_string(fields, "password2");
2508 if(strcmp(p1, p2)) {
2509 purple_notify_error(js->gc, NULL, _("New passwords do not match."), NULL);
2510 return;
2513 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
2515 xmlnode_set_attrib(iq->node, "to", js->user->domain);
2517 query = xmlnode_get_child(iq->node, "query");
2519 y = xmlnode_new_child(query, "username");
2520 xmlnode_insert_data(y, js->user->node, -1);
2521 y = xmlnode_new_child(query, "password");
2522 xmlnode_insert_data(y, p1, -1);
2524 jabber_iq_set_callback(iq, jabber_password_change_result_cb, g_strdup(p1));
2526 jabber_iq_send(iq);
2529 static void jabber_password_change(PurplePluginAction *action)
2532 PurpleConnection *gc = (PurpleConnection *) action->context;
2533 JabberStream *js = gc->proto_data;
2534 PurpleRequestFields *fields;
2535 PurpleRequestFieldGroup *group;
2536 PurpleRequestField *field;
2538 fields = purple_request_fields_new();
2539 group = purple_request_field_group_new(NULL);
2540 purple_request_fields_add_group(fields, group);
2542 field = purple_request_field_string_new("password1", _("Password"),
2543 "", FALSE);
2544 purple_request_field_string_set_masked(field, TRUE);
2545 purple_request_field_set_required(field, TRUE);
2546 purple_request_field_group_add_field(group, field);
2548 field = purple_request_field_string_new("password2", _("Password (again)"),
2549 "", FALSE);
2550 purple_request_field_string_set_masked(field, TRUE);
2551 purple_request_field_set_required(field, TRUE);
2552 purple_request_field_group_add_field(group, field);
2554 purple_request_fields(js->gc, _("Change XMPP Password"),
2555 _("Change XMPP Password"), _("Please enter your new password"),
2556 fields, _("OK"), G_CALLBACK(jabber_password_change_cb),
2557 _("Cancel"), NULL,
2558 purple_connection_get_account(gc), NULL, NULL,
2559 js);
2562 GList *jabber_actions(PurplePlugin *plugin, gpointer context)
2564 PurpleConnection *gc = (PurpleConnection *) context;
2565 JabberStream *js = gc->proto_data;
2566 GList *m = NULL;
2567 PurplePluginAction *act;
2569 act = purple_plugin_action_new(_("Set User Info..."),
2570 jabber_setup_set_info);
2571 m = g_list_append(m, act);
2573 /* if (js->protocol_options & CHANGE_PASSWORD) { */
2574 act = purple_plugin_action_new(_("Change Password..."),
2575 jabber_password_change);
2576 m = g_list_append(m, act);
2577 /* } */
2579 act = purple_plugin_action_new(_("Search for Users..."),
2580 jabber_user_search_begin);
2581 m = g_list_append(m, act);
2583 purple_debug_info("jabber", "jabber_actions: have pep: %s\n", js->pep?"YES":"NO");
2585 if(js->pep)
2586 jabber_pep_init_actions(&m);
2588 if(js->commands)
2589 jabber_adhoc_init_server_commands(js, &m);
2591 return m;
2594 PurpleChat *jabber_find_blist_chat(PurpleAccount *account, const char *name)
2596 PurpleBlistNode *gnode, *cnode;
2597 JabberID *jid;
2599 if(!(jid = jabber_id_new(name)))
2600 return NULL;
2602 for(gnode = purple_blist_get_root(); gnode;
2603 gnode = purple_blist_node_get_sibling_next(gnode)) {
2604 for(cnode = purple_blist_node_get_first_child(gnode);
2605 cnode;
2606 cnode = purple_blist_node_get_sibling_next(cnode)) {
2607 PurpleChat *chat = (PurpleChat*)cnode;
2608 const char *room, *server;
2609 GHashTable *components;
2610 if(!PURPLE_BLIST_NODE_IS_CHAT(cnode))
2611 continue;
2613 if (purple_chat_get_account(chat) != account)
2614 continue;
2616 components = purple_chat_get_components(chat);
2617 if(!(room = g_hash_table_lookup(components, "room")))
2618 continue;
2619 if(!(server = g_hash_table_lookup(components, "server")))
2620 continue;
2622 /* FIXME: Collate is wrong in a few cases here; this should be prepped */
2623 if(jid->node && jid->domain &&
2624 !g_utf8_collate(room, jid->node) && !g_utf8_collate(server, jid->domain)) {
2625 jabber_id_free(jid);
2626 return chat;
2630 jabber_id_free(jid);
2631 return NULL;
2634 void jabber_convo_closed(PurpleConnection *gc, const char *who)
2636 JabberStream *js = gc->proto_data;
2637 JabberID *jid;
2638 JabberBuddy *jb;
2639 JabberBuddyResource *jbr;
2641 if(!(jid = jabber_id_new(who)))
2642 return;
2644 if((jb = jabber_buddy_find(js, who, TRUE)) &&
2645 (jbr = jabber_buddy_find_resource(jb, jid->resource))) {
2646 if(jbr->thread_id) {
2647 g_free(jbr->thread_id);
2648 jbr->thread_id = NULL;
2652 jabber_id_free(jid);
2656 char *jabber_parse_error(JabberStream *js,
2657 xmlnode *packet,
2658 PurpleConnectionError *reason)
2660 xmlnode *error;
2661 const char *code = NULL, *text = NULL;
2662 const char *xmlns = xmlnode_get_namespace(packet);
2663 char *cdata = NULL;
2665 #define SET_REASON(x) \
2666 if(reason != NULL) { *reason = x; }
2668 if((error = xmlnode_get_child(packet, "error"))) {
2669 xmlnode *t = xmlnode_get_child_with_namespace(error, "text", NS_XMPP_STANZAS);
2670 if (t)
2671 cdata = xmlnode_get_data(t);
2672 #if 0
2673 cdata = xmlnode_get_data(error);
2674 #endif
2675 code = xmlnode_get_attrib(error, "code");
2677 /* Stanza errors */
2678 if(xmlnode_get_child(error, "bad-request")) {
2679 text = _("Bad Request");
2680 } else if(xmlnode_get_child(error, "conflict")) {
2681 SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE);
2682 text = _("Conflict");
2683 } else if(xmlnode_get_child(error, "feature-not-implemented")) {
2684 text = _("Feature Not Implemented");
2685 } else if(xmlnode_get_child(error, "forbidden")) {
2686 text = _("Forbidden");
2687 } else if(xmlnode_get_child(error, "gone")) {
2688 text = _("Gone");
2689 } else if(xmlnode_get_child(error, "internal-server-error")) {
2690 text = _("Internal Server Error");
2691 } else if(xmlnode_get_child(error, "item-not-found")) {
2692 text = _("Item Not Found");
2693 } else if(xmlnode_get_child(error, "jid-malformed")) {
2694 text = _("Malformed XMPP ID");
2695 } else if(xmlnode_get_child(error, "not-acceptable")) {
2696 text = _("Not Acceptable");
2697 } else if(xmlnode_get_child(error, "not-allowed")) {
2698 text = _("Not Allowed");
2699 } else if(xmlnode_get_child(error, "not-authorized")) {
2700 text = _("Not Authorized");
2701 } else if(xmlnode_get_child(error, "payment-required")) {
2702 text = _("Payment Required");
2703 } else if(xmlnode_get_child(error, "recipient-unavailable")) {
2704 text = _("Recipient Unavailable");
2705 } else if(xmlnode_get_child(error, "redirect")) {
2706 /* XXX */
2707 } else if(xmlnode_get_child(error, "registration-required")) {
2708 text = _("Registration Required");
2709 } else if(xmlnode_get_child(error, "remote-server-not-found")) {
2710 text = _("Remote Server Not Found");
2711 } else if(xmlnode_get_child(error, "remote-server-timeout")) {
2712 text = _("Remote Server Timeout");
2713 } else if(xmlnode_get_child(error, "resource-constraint")) {
2714 text = _("Server Overloaded");
2715 } else if(xmlnode_get_child(error, "service-unavailable")) {
2716 text = _("Service Unavailable");
2717 } else if(xmlnode_get_child(error, "subscription-required")) {
2718 text = _("Subscription Required");
2719 } else if(xmlnode_get_child(error, "unexpected-request")) {
2720 text = _("Unexpected Request");
2721 } else if(xmlnode_get_child(error, "undefined-condition")) {
2722 text = _("Unknown Error");
2724 } else if(xmlns && !strcmp(xmlns, NS_XMPP_SASL)) {
2725 /* Most common reason can be the default */
2726 SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR);
2727 if(xmlnode_get_child(packet, "aborted")) {
2728 text = _("Authorization Aborted");
2729 } else if(xmlnode_get_child(packet, "incorrect-encoding")) {
2730 text = _("Incorrect encoding in authorization");
2731 } else if(xmlnode_get_child(packet, "invalid-authzid")) {
2732 text = _("Invalid authzid");
2733 } else if(xmlnode_get_child(packet, "invalid-mechanism")) {
2734 text = _("Invalid Authorization Mechanism");
2735 } else if(xmlnode_get_child(packet, "mechanism-too-weak")) {
2736 SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE);
2737 text = _("Authorization mechanism too weak");
2738 } else if(xmlnode_get_child(packet, "not-authorized")) {
2739 SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED);
2740 /* Clear the pasword if it isn't being saved */
2741 if (!purple_account_get_remember_password(js->gc->account))
2742 purple_account_set_password(js->gc->account, NULL);
2743 text = _("Not Authorized");
2744 } else if(xmlnode_get_child(packet, "temporary-auth-failure")) {
2745 text = _("Temporary Authentication Failure");
2746 } else {
2747 SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED);
2748 text = _("Authentication Failure");
2750 } else if(!strcmp(packet->name, "stream:error") ||
2751 (!strcmp(packet->name, "error") && xmlns &&
2752 !strcmp(xmlns, NS_XMPP_STREAMS))) {
2753 /* Most common reason as default: */
2754 SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR);
2755 if(xmlnode_get_child(packet, "bad-format")) {
2756 text = _("Bad Format");
2757 } else if(xmlnode_get_child(packet, "bad-namespace-prefix")) {
2758 text = _("Bad Namespace Prefix");
2759 } else if(xmlnode_get_child(packet, "conflict")) {
2760 SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE);
2761 text = _("Resource Conflict");
2762 } else if(xmlnode_get_child(packet, "connection-timeout")) {
2763 text = _("Connection Timeout");
2764 } else if(xmlnode_get_child(packet, "host-gone")) {
2765 text = _("Host Gone");
2766 } else if(xmlnode_get_child(packet, "host-unknown")) {
2767 text = _("Host Unknown");
2768 } else if(xmlnode_get_child(packet, "improper-addressing")) {
2769 text = _("Improper Addressing");
2770 } else if(xmlnode_get_child(packet, "internal-server-error")) {
2771 text = _("Internal Server Error");
2772 } else if(xmlnode_get_child(packet, "invalid-id")) {
2773 text = _("Invalid ID");
2774 } else if(xmlnode_get_child(packet, "invalid-namespace")) {
2775 text = _("Invalid Namespace");
2776 } else if(xmlnode_get_child(packet, "invalid-xml")) {
2777 text = _("Invalid XML");
2778 } else if(xmlnode_get_child(packet, "nonmatching-hosts")) {
2779 text = _("Non-matching Hosts");
2780 } else if(xmlnode_get_child(packet, "not-authorized")) {
2781 text = _("Not Authorized");
2782 } else if(xmlnode_get_child(packet, "policy-violation")) {
2783 text = _("Policy Violation");
2784 } else if(xmlnode_get_child(packet, "remote-connection-failed")) {
2785 text = _("Remote Connection Failed");
2786 } else if(xmlnode_get_child(packet, "resource-constraint")) {
2787 text = _("Resource Constraint");
2788 } else if(xmlnode_get_child(packet, "restricted-xml")) {
2789 text = _("Restricted XML");
2790 } else if(xmlnode_get_child(packet, "see-other-host")) {
2791 text = _("See Other Host");
2792 } else if(xmlnode_get_child(packet, "system-shutdown")) {
2793 text = _("System Shutdown");
2794 } else if(xmlnode_get_child(packet, "undefined-condition")) {
2795 text = _("Undefined Condition");
2796 } else if(xmlnode_get_child(packet, "unsupported-encoding")) {
2797 text = _("Unsupported Encoding");
2798 } else if(xmlnode_get_child(packet, "unsupported-stanza-type")) {
2799 text = _("Unsupported Stanza Type");
2800 } else if(xmlnode_get_child(packet, "unsupported-version")) {
2801 text = _("Unsupported Version");
2802 } else if(xmlnode_get_child(packet, "xml-not-well-formed")) {
2803 text = _("XML Not Well Formed");
2804 } else {
2805 text = _("Stream Error");
2809 #undef SET_REASON
2811 if(text || cdata) {
2812 char *ret = g_strdup_printf("%s%s%s", code ? code : "",
2813 code ? ": " : "", text ? text : cdata);
2814 g_free(cdata);
2815 return ret;
2816 } else {
2817 return NULL;
2821 static PurpleCmdRet jabber_cmd_chat_config(PurpleConversation *conv,
2822 const char *cmd, char **args, char **error, void *data)
2824 JabberChat *chat = jabber_chat_find_by_conv(conv);
2826 if (!chat)
2827 return PURPLE_CMD_RET_FAILED;
2829 jabber_chat_request_room_configure(chat);
2830 return PURPLE_CMD_RET_OK;
2833 static PurpleCmdRet jabber_cmd_chat_register(PurpleConversation *conv,
2834 const char *cmd, char **args, char **error, void *data)
2836 JabberChat *chat = jabber_chat_find_by_conv(conv);
2838 if (!chat)
2839 return PURPLE_CMD_RET_FAILED;
2841 jabber_chat_register(chat);
2842 return PURPLE_CMD_RET_OK;
2845 static PurpleCmdRet jabber_cmd_chat_topic(PurpleConversation *conv,
2846 const char *cmd, char **args, char **error, void *data)
2848 JabberChat *chat = jabber_chat_find_by_conv(conv);
2850 if (!chat)
2851 return PURPLE_CMD_RET_FAILED;
2853 if (args && args[0] && *args[0])
2854 jabber_chat_change_topic(chat, args[0]);
2855 else {
2856 const char *cur = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv));
2857 char *buf, *tmp, *tmp2;
2859 if (cur) {
2860 tmp = g_markup_escape_text(cur, -1);
2861 tmp2 = purple_markup_linkify(tmp);
2862 buf = g_strdup_printf(_("current topic is: %s"), tmp2);
2863 g_free(tmp);
2864 g_free(tmp2);
2865 } else
2866 buf = g_strdup(_("No topic is set"));
2867 purple_conv_chat_write(PURPLE_CONV_CHAT(conv), "", buf,
2868 PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL));
2869 g_free(buf);
2872 return PURPLE_CMD_RET_OK;
2875 static PurpleCmdRet jabber_cmd_chat_nick(PurpleConversation *conv,
2876 const char *cmd, char **args, char **error, void *data)
2878 JabberChat *chat = jabber_chat_find_by_conv(conv);
2880 if(!chat || !args || !args[0])
2881 return PURPLE_CMD_RET_FAILED;
2883 if (!jabber_resourceprep_validate(args[0])) {
2884 *error = g_strdup(_("Invalid nickname"));
2885 return PURPLE_CMD_RET_FAILED;
2888 if (jabber_chat_change_nick(chat, args[0]))
2889 return PURPLE_CMD_RET_OK;
2890 else
2891 return PURPLE_CMD_RET_FAILED;
2894 static PurpleCmdRet jabber_cmd_chat_part(PurpleConversation *conv,
2895 const char *cmd, char **args, char **error, void *data)
2897 JabberChat *chat = jabber_chat_find_by_conv(conv);
2899 if (!chat)
2900 return PURPLE_CMD_RET_FAILED;
2902 jabber_chat_part(chat, args ? args[0] : NULL);
2903 return PURPLE_CMD_RET_OK;
2906 static PurpleCmdRet jabber_cmd_chat_ban(PurpleConversation *conv,
2907 const char *cmd, char **args, char **error, void *data)
2909 JabberChat *chat = jabber_chat_find_by_conv(conv);
2911 if(!chat || !args || !args[0])
2912 return PURPLE_CMD_RET_FAILED;
2914 if(!jabber_chat_ban_user(chat, args[0], args[1])) {
2915 *error = g_strdup_printf(_("Unable to ban user %s"), args[0]);
2916 return PURPLE_CMD_RET_FAILED;
2919 return PURPLE_CMD_RET_OK;
2922 static PurpleCmdRet jabber_cmd_chat_affiliate(PurpleConversation *conv,
2923 const char *cmd, char **args, char **error, void *data)
2925 JabberChat *chat = jabber_chat_find_by_conv(conv);
2927 if (!chat || !args || !args[0])
2928 return PURPLE_CMD_RET_FAILED;
2930 if (strcmp(args[0], "owner") != 0 &&
2931 strcmp(args[0], "admin") != 0 &&
2932 strcmp(args[0], "member") != 0 &&
2933 strcmp(args[0], "outcast") != 0 &&
2934 strcmp(args[0], "none") != 0) {
2935 *error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[0]);
2936 return PURPLE_CMD_RET_FAILED;
2939 if (args[1]) {
2940 int i;
2941 char **nicks = g_strsplit(args[1], " ", -1);
2943 for (i = 0; nicks[i]; ++i)
2944 if (!jabber_chat_affiliate_user(chat, nicks[i], args[0])) {
2945 *error = g_strdup_printf(_("Unable to affiliate user %s as \"%s\""), nicks[i], args[0]);
2946 g_strfreev(nicks);
2947 return PURPLE_CMD_RET_FAILED;
2950 g_strfreev(nicks);
2951 } else {
2952 jabber_chat_affiliation_list(chat, args[0]);
2955 return PURPLE_CMD_RET_OK;
2958 static PurpleCmdRet jabber_cmd_chat_role(PurpleConversation *conv,
2959 const char *cmd, char **args, char **error, void *data)
2961 JabberChat *chat = jabber_chat_find_by_conv(conv);
2963 if (!chat || !args || !args[0])
2964 return PURPLE_CMD_RET_FAILED;
2966 if (strcmp(args[0], "moderator") != 0 &&
2967 strcmp(args[0], "participant") != 0 &&
2968 strcmp(args[0], "visitor") != 0 &&
2969 strcmp(args[0], "none") != 0) {
2970 *error = g_strdup_printf(_("Unknown role: \"%s\""), args[0]);
2971 return PURPLE_CMD_RET_FAILED;
2974 if (args[1]) {
2975 int i;
2976 char **nicks = g_strsplit(args[1], " ", -1);
2978 for (i = 0; nicks[i]; i++)
2979 if (!jabber_chat_role_user(chat, nicks[i], args[0], NULL)) {
2980 *error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
2981 args[0], nicks[i]);
2982 g_strfreev(nicks);
2983 return PURPLE_CMD_RET_FAILED;
2986 g_strfreev(nicks);
2987 } else {
2988 jabber_chat_role_list(chat, args[0]);
2990 return PURPLE_CMD_RET_OK;
2993 static PurpleCmdRet jabber_cmd_chat_invite(PurpleConversation *conv,
2994 const char *cmd, char **args, char **error, void *data)
2996 if(!args || !args[0])
2997 return PURPLE_CMD_RET_FAILED;
2999 jabber_chat_invite(purple_conversation_get_gc(conv),
3000 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), args[1] ? args[1] : "",
3001 args[0]);
3003 return PURPLE_CMD_RET_OK;
3006 static PurpleCmdRet jabber_cmd_chat_join(PurpleConversation *conv,
3007 const char *cmd, char **args, char **error, void *data)
3009 JabberChat *chat = jabber_chat_find_by_conv(conv);
3010 GHashTable *components;
3011 JabberID *jid;
3012 const char *room = NULL, *server = NULL, *handle = NULL;
3014 if (!chat || !args || !args[0])
3015 return PURPLE_CMD_RET_FAILED;
3017 components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
3019 jid = jabber_id_new(args[0]);
3020 if (jid) {
3021 room = jid->node;
3022 server = jid->domain;
3023 handle = jid->resource ? jid->resource : chat->handle;
3024 } else {
3025 /* If jabber_id_new failed, the user may have just passed in
3026 * a room name. For backward compatibility, handle that here.
3028 if (strchr(args[0], '@')) {
3029 *error = g_strdup(_("Invalid XMPP ID"));
3030 return PURPLE_CMD_RET_FAILED;
3033 room = args[0];
3034 server = chat->server;
3035 handle = chat->handle;
3038 g_hash_table_insert(components, "room", (gpointer)room);
3039 g_hash_table_insert(components, "server", (gpointer)server);
3040 g_hash_table_insert(components, "handle", (gpointer)handle);
3042 if (args[1])
3043 g_hash_table_insert(components, "password", args[1]);
3045 jabber_chat_join(purple_conversation_get_gc(conv), components);
3047 g_hash_table_destroy(components);
3048 jabber_id_free(jid);
3049 return PURPLE_CMD_RET_OK;
3052 static PurpleCmdRet jabber_cmd_chat_kick(PurpleConversation *conv,
3053 const char *cmd, char **args, char **error, void *data)
3055 JabberChat *chat = jabber_chat_find_by_conv(conv);
3057 if(!chat || !args || !args[0])
3058 return PURPLE_CMD_RET_FAILED;
3060 if(!jabber_chat_role_user(chat, args[0], "none", args[1])) {
3061 *error = g_strdup_printf(_("Unable to kick user %s"), args[0]);
3062 return PURPLE_CMD_RET_FAILED;
3065 return PURPLE_CMD_RET_OK;
3068 static PurpleCmdRet jabber_cmd_chat_msg(PurpleConversation *conv,
3069 const char *cmd, char **args, char **error, void *data)
3071 JabberChat *chat = jabber_chat_find_by_conv(conv);
3072 char *who;
3074 if (!chat)
3075 return PURPLE_CMD_RET_FAILED;
3077 who = g_strdup_printf("%s@%s/%s", chat->room, chat->server, args[0]);
3079 jabber_message_send_im(purple_conversation_get_gc(conv), who, args[1], 0);
3081 g_free(who);
3082 return PURPLE_CMD_RET_OK;
3085 static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv,
3086 const char *cmd, char **args, char **error, void *data)
3088 PurpleAccount *account;
3089 PurpleConnection *pc;
3091 if(!args || !args[0])
3092 return PURPLE_CMD_RET_FAILED;
3094 account = purple_conversation_get_account(conv);
3095 pc = purple_account_get_connection(account);
3097 if(!jabber_ping_jid(purple_connection_get_protocol_data(pc), args[0])) {
3098 *error = g_strdup_printf(_("Unable to ping user %s"), args[0]);
3099 return PURPLE_CMD_RET_FAILED;
3102 return PURPLE_CMD_RET_OK;
3105 static gboolean _jabber_send_buzz(JabberStream *js, const char *username, char **error) {
3107 JabberBuddy *jb;
3108 JabberBuddyResource *jbr;
3109 PurpleConnection *gc = js->gc;
3110 PurpleBuddy *buddy =
3111 purple_find_buddy(purple_connection_get_account(gc), username);
3112 const gchar *alias =
3113 buddy ? purple_buddy_get_contact_alias(buddy) : username;
3115 if(!username)
3116 return FALSE;
3118 jb = jabber_buddy_find(js, username, FALSE);
3119 if(!jb) {
3120 *error = g_strdup_printf(_("Unable to buzz, because there is nothing "
3121 "known about %s."), alias);
3122 return FALSE;
3125 jbr = jabber_buddy_find_resource(jb, NULL);
3126 if (!jbr) {
3127 *error = g_strdup_printf(_("Unable to buzz, because %s might be offline."),
3128 alias);
3129 return FALSE;
3132 if (jabber_resource_has_capability(jbr, NS_ATTENTION)) {
3133 xmlnode *buzz, *msg = xmlnode_new("message");
3134 gchar *to;
3136 to = g_strdup_printf("%s/%s", username, jbr->name);
3137 xmlnode_set_attrib(msg, "to", to);
3138 g_free(to);
3140 /* avoid offline storage */
3141 xmlnode_set_attrib(msg, "type", "headline");
3143 buzz = xmlnode_new_child(msg, "attention");
3144 xmlnode_set_namespace(buzz, NS_ATTENTION);
3146 jabber_send(js, msg);
3147 xmlnode_free(msg);
3149 return TRUE;
3150 } else {
3151 *error = g_strdup_printf(_("Unable to buzz, because %s does "
3152 "not support it or does not wish to receive buzzes now."), alias);
3153 return FALSE;
3157 static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv,
3158 const char *cmd, char **args, char **error, void *data)
3160 JabberStream *js = conv->account->gc->proto_data;
3161 const gchar *who;
3162 gchar *description;
3163 PurpleBuddy *buddy;
3164 const char *alias;
3165 PurpleAttentionType *attn =
3166 purple_get_attention_type_from_code(conv->account, 0);
3168 if (!args || !args[0]) {
3169 /* use the buddy from conversation, if it's a one-to-one conversation */
3170 if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
3171 who = purple_conversation_get_name(conv);
3172 } else {
3173 return PURPLE_CMD_RET_FAILED;
3175 } else {
3176 who = args[0];
3179 buddy = purple_find_buddy(conv->account, who);
3180 if (buddy != NULL)
3181 alias = purple_buddy_get_contact_alias(buddy);
3182 else
3183 alias = who;
3185 description =
3186 g_strdup_printf(purple_attention_type_get_outgoing_desc(attn), alias);
3187 purple_conversation_write(conv, NULL, description,
3188 PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_SYSTEM, time(NULL));
3189 g_free(description);
3190 return _jabber_send_buzz(js, who, error) ? PURPLE_CMD_RET_OK : PURPLE_CMD_RET_FAILED;
3193 GList *jabber_attention_types(PurpleAccount *account)
3195 static GList *types = NULL;
3197 if (!types) {
3198 types = g_list_append(types, purple_attention_type_new("Buzz", _("Buzz"),
3199 _("%s has buzzed you!"), _("Buzzing %s...")));
3202 return types;
3205 gboolean jabber_send_attention(PurpleConnection *gc, const char *username, guint code)
3207 JabberStream *js = gc->proto_data;
3208 gchar *error = NULL;
3210 if (!_jabber_send_buzz(js, username, &error)) {
3211 PurpleAccount *account = purple_connection_get_account(gc);
3212 PurpleConversation *conv =
3213 purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, username, account);
3214 purple_debug_error("jabber", "jabber_send_attention: jabber_cmd_buzz failed with error: %s\n", error ? error : "(NULL)");
3216 if (conv) {
3217 purple_conversation_write(conv, username, error, PURPLE_MESSAGE_ERROR,
3218 time(NULL));
3221 g_free(error);
3222 return FALSE;
3225 return TRUE;
3229 gboolean jabber_offline_message(const PurpleBuddy *buddy)
3231 return TRUE;
3234 #ifdef USE_VV
3235 gboolean
3236 jabber_audio_enabled(JabberStream *js, const char *namespace)
3238 PurpleMediaManager *manager = purple_media_manager_get();
3239 PurpleMediaCaps caps = purple_media_manager_get_ui_caps(manager);
3241 return (caps & (PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION));
3244 gboolean
3245 jabber_video_enabled(JabberStream *js, const char *namespace)
3247 PurpleMediaManager *manager = purple_media_manager_get();
3248 PurpleMediaCaps caps = purple_media_manager_get_ui_caps(manager);
3250 return (caps & (PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION));
3253 typedef struct {
3254 PurpleAccount *account;
3255 gchar *who;
3256 PurpleMediaSessionType type;
3258 } JabberMediaRequest;
3260 static void
3261 jabber_media_cancel_cb(JabberMediaRequest *request,
3262 PurpleRequestFields *fields)
3264 g_free(request->who);
3265 g_free(request);
3268 static void
3269 jabber_media_ok_cb(JabberMediaRequest *request, PurpleRequestFields *fields)
3271 PurpleRequestField *field =
3272 purple_request_fields_get_field(fields, "resource");
3273 int selected_id = purple_request_field_choice_get_value(field);
3274 GList *labels = purple_request_field_choice_get_labels(field);
3275 gchar *who = g_strdup_printf("%s/%s", request->who,
3276 (gchar*)g_list_nth_data(labels, selected_id));
3277 jabber_initiate_media(request->account, who, request->type);
3279 g_free(who);
3280 g_free(request->who);
3281 g_free(request);
3283 #endif
3285 gboolean
3286 jabber_initiate_media(PurpleAccount *account, const char *who,
3287 PurpleMediaSessionType type)
3289 #ifdef USE_VV
3290 JabberStream *js = (JabberStream *)
3291 purple_account_get_connection(account)->proto_data;
3292 JabberBuddy *jb;
3293 JabberBuddyResource *jbr = NULL;
3294 char *resource;
3296 if (!js) {
3297 purple_debug_error("jabber",
3298 "jabber_initiate_media: NULL stream\n");
3299 return FALSE;
3303 if((resource = jabber_get_resource(who)) != NULL) {
3304 /* they've specified a resource, no need to ask or
3305 * default or anything, just do it */
3307 jb = jabber_buddy_find(js, who, FALSE);
3308 jbr = jabber_buddy_find_resource(jb, resource);
3309 g_free(resource);
3311 if (type & PURPLE_MEDIA_AUDIO &&
3312 !jabber_resource_has_capability(jbr,
3313 JINGLE_APP_RTP_SUPPORT_AUDIO) &&
3314 jabber_resource_has_capability(jbr, NS_GOOGLE_VOICE))
3315 return jabber_google_session_initiate(js, who, type);
3316 else
3317 return jingle_rtp_initiate_media(js, who, type);
3320 jb = jabber_buddy_find(js, who, FALSE);
3322 if(!jb || !jb->resources) {
3323 /* no resources online, we're trying to initiate with someone
3324 * whose presence we're not subscribed to, or
3325 * someone who is offline. Let's inform the user */
3326 char *msg;
3328 if(!jb) {
3329 msg = g_strdup_printf(_("Unable to initiate media with %s: invalid JID"), who);
3330 } else if(jb->subscription & JABBER_SUB_TO) {
3331 msg = g_strdup_printf(_("Unable to initiate media with %s: user is not online"), who);
3332 } else {
3333 msg = g_strdup_printf(_("Unable to initiate media with %s: not subscribed to user presence"), who);
3336 purple_notify_error(account, _("Media Initiation Failed"),
3337 _("Media Initiation Failed"), msg);
3338 g_free(msg);
3339 return FALSE;
3340 } else if(!jb->resources->next) {
3341 /* only 1 resource online (probably our most common case)
3342 * so no need to ask who to initiate with */
3343 gchar *name;
3344 gboolean result;
3345 jbr = jb->resources->data;
3346 name = g_strdup_printf("%s/%s", who, jbr->name);
3347 result = jabber_initiate_media(account, name, type);
3348 g_free(name);
3349 return result;
3350 } else {
3351 /* we've got multiple resources,
3352 * we need to pick one to initiate with */
3353 GList *l;
3354 char *msg;
3355 PurpleRequestFields *fields;
3356 PurpleRequestField *field = purple_request_field_choice_new(
3357 "resource", _("Resource"), 0);
3358 PurpleRequestFieldGroup *group;
3359 JabberMediaRequest *request;
3361 for(l = jb->resources; l; l = l->next)
3363 JabberBuddyResource *ljbr = l->data;
3364 PurpleMediaCaps caps;
3365 gchar *name;
3366 name = g_strdup_printf("%s/%s", who, ljbr->name);
3367 caps = jabber_get_media_caps(account, name);
3368 g_free(name);
3370 if ((type & PURPLE_MEDIA_AUDIO) &&
3371 (type & PURPLE_MEDIA_VIDEO)) {
3372 if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) {
3373 jbr = ljbr;
3374 purple_request_field_choice_add(
3375 field, jbr->name);
3377 } else if (type & (PURPLE_MEDIA_AUDIO) &&
3378 (caps & PURPLE_MEDIA_CAPS_AUDIO)) {
3379 jbr = ljbr;
3380 purple_request_field_choice_add(
3381 field, jbr->name);
3382 }else if (type & (PURPLE_MEDIA_VIDEO) &&
3383 (caps & PURPLE_MEDIA_CAPS_VIDEO)) {
3384 jbr = ljbr;
3385 purple_request_field_choice_add(
3386 field, jbr->name);
3390 if (jbr == NULL) {
3391 purple_debug_error("jabber",
3392 "No resources available\n");
3393 return FALSE;
3396 if (g_list_length(purple_request_field_choice_get_labels(
3397 field)) <= 1) {
3398 gchar *name;
3399 gboolean result;
3400 purple_request_field_destroy(field);
3401 name = g_strdup_printf("%s/%s", who, jbr->name);
3402 result = jabber_initiate_media(account, name, type);
3403 g_free(name);
3404 return result;
3407 msg = g_strdup_printf(_("Please select the resource of %s with which you would like to start a media session."), who);
3408 fields = purple_request_fields_new();
3409 group = purple_request_field_group_new(NULL);
3410 request = g_new0(JabberMediaRequest, 1);
3411 request->account = account;
3412 request->who = g_strdup(who);
3413 request->type = type;
3415 purple_request_field_group_add_field(group, field);
3416 purple_request_fields_add_group(fields, group);
3417 purple_request_fields(account, _("Select a Resource"), msg,
3418 NULL, fields, _("Initiate Media"),
3419 G_CALLBACK(jabber_media_ok_cb), _("Cancel"),
3420 G_CALLBACK(jabber_media_cancel_cb),
3421 account, who, NULL, request);
3423 g_free(msg);
3424 return TRUE;
3426 #endif
3427 return FALSE;
3430 PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who)
3432 #ifdef USE_VV
3433 JabberStream *js = (JabberStream *)
3434 purple_account_get_connection(account)->proto_data;
3435 JabberBuddy *jb;
3436 JabberBuddyResource *jbr;
3437 PurpleMediaCaps total = PURPLE_MEDIA_CAPS_NONE;
3438 gchar *resource;
3439 GList *specific = NULL, *l;
3441 if (!js) {
3442 purple_debug_info("jabber",
3443 "jabber_can_do_media: NULL stream\n");
3444 return FALSE;
3447 jb = jabber_buddy_find(js, who, FALSE);
3449 if (!jb || !jb->resources) {
3450 /* no resources online, we're trying to get caps for someone
3451 * whose presence we're not subscribed to, or
3452 * someone who is offline. */
3453 return total;
3455 } else if ((resource = jabber_get_resource(who)) != NULL) {
3456 /* they've specified a resource, no need to ask or
3457 * default or anything, just do it */
3458 jbr = jabber_buddy_find_resource(jb, resource);
3459 g_free(resource);
3461 if (!jbr) {
3462 purple_debug_error("jabber", "jabber_get_media_caps:"
3463 " Can't find resource %s\n", who);
3464 return total;
3467 l = specific = g_list_prepend(specific, jbr);
3469 } else {
3470 /* we've got multiple resources, combine their caps */
3471 l = jb->resources;
3474 for (; l; l = l->next) {
3475 PurpleMediaCaps caps = PURPLE_MEDIA_CAPS_NONE;
3476 jbr = l->data;
3478 if (jabber_resource_has_capability(jbr,
3479 JINGLE_APP_RTP_SUPPORT_AUDIO))
3480 caps |= PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION |
3481 PURPLE_MEDIA_CAPS_AUDIO;
3482 if (jabber_resource_has_capability(jbr,
3483 JINGLE_APP_RTP_SUPPORT_VIDEO))
3484 caps |= PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION |
3485 PURPLE_MEDIA_CAPS_VIDEO;
3486 if (caps & PURPLE_MEDIA_CAPS_AUDIO && caps &
3487 PURPLE_MEDIA_CAPS_VIDEO)
3488 caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO;
3489 if (caps != PURPLE_MEDIA_CAPS_NONE) {
3490 if (!jabber_resource_has_capability(jbr,
3491 JINGLE_TRANSPORT_ICEUDP) &&
3492 !jabber_resource_has_capability(jbr,
3493 JINGLE_TRANSPORT_RAWUDP)) {
3494 purple_debug_info("jingle-rtp", "Buddy doesn't "
3495 "support the same transport types\n");
3496 caps = PURPLE_MEDIA_CAPS_NONE;
3497 } else
3498 caps |= PURPLE_MEDIA_CAPS_MODIFY_SESSION |
3499 PURPLE_MEDIA_CAPS_CHANGE_DIRECTION;
3501 if (jabber_resource_has_capability(jbr, NS_GOOGLE_VOICE)) {
3502 caps |= PURPLE_MEDIA_CAPS_AUDIO;
3503 if (jabber_resource_has_capability(jbr, NS_GOOGLE_VIDEO))
3504 caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO;
3507 total |= caps;
3510 if (specific) {
3511 g_list_free(specific);
3514 return total;
3515 #else
3516 return PURPLE_MEDIA_CAPS_NONE;
3517 #endif
3520 gboolean jabber_can_receive_file(PurpleConnection *gc, const char *who)
3522 JabberStream *js = gc->proto_data;
3524 if (js) {
3525 JabberBuddy *jb = jabber_buddy_find(js, who, FALSE);
3526 GList *iter;
3527 gboolean has_resources_without_caps = FALSE;
3529 /* if we didn't find a JabberBuddy, we don't have presence for this
3530 buddy, let's assume they can receive files, disco should tell us
3531 when actually trying */
3532 if (jb == NULL)
3533 return TRUE;
3535 /* find out if there is any resources without caps */
3536 for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
3537 JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
3539 if (!jabber_resource_know_capabilities(jbr)) {
3540 has_resources_without_caps = TRUE;
3544 if (has_resources_without_caps) {
3545 /* there is at least one resource which we don't have caps for,
3546 let's assume they can receive files... */
3547 return TRUE;
3548 } else {
3549 /* we have caps for all the resources, see if at least one has
3550 right caps */
3551 for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
3552 JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
3554 if (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER)
3555 && (jabber_resource_has_capability(jbr,
3556 NS_BYTESTREAMS)
3557 || jabber_resource_has_capability(jbr, NS_IBB))) {
3558 return TRUE;
3561 return FALSE;
3563 } else {
3564 return TRUE;
3568 static PurpleCmdRet
3569 jabber_cmd_mood(PurpleConversation *conv,
3570 const char *cmd, char **args, char **error, void *data)
3572 JabberStream *js = conv->account->gc->proto_data;
3574 if (js->pep) {
3575 /* if no argument was given, unset mood */
3576 if (!args || !args[0]) {
3577 jabber_mood_set(js, NULL, NULL);
3578 } else if (!args[1]) {
3579 jabber_mood_set(js, args[0], NULL);
3580 } else {
3581 jabber_mood_set(js, args[0], args[1]);
3584 return PURPLE_CMD_RET_OK;
3585 } else {
3586 /* account does not support PEP, can't set a mood */
3587 purple_conversation_write(conv, NULL,
3588 _("Account does not support PEP, can't set mood"),
3589 PURPLE_MESSAGE_ERROR, time(NULL));
3590 return PURPLE_CMD_RET_FAILED;
3594 static void jabber_register_commands(PurplePlugin *plugin)
3596 GSList *commands = NULL;
3597 PurpleCmdId id;
3598 id = purple_cmd_register("config", "", PURPLE_CMD_P_PRPL,
3599 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
3600 "prpl-jabber", jabber_cmd_chat_config,
3601 _("config: Configure a chat room."), NULL);
3602 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3604 id = purple_cmd_register("configure", "", PURPLE_CMD_P_PRPL,
3605 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
3606 "prpl-jabber", jabber_cmd_chat_config,
3607 _("configure: Configure a chat room."), NULL);
3608 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3610 id = purple_cmd_register("nick", "s", PURPLE_CMD_P_PRPL,
3611 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
3612 "prpl-jabber", jabber_cmd_chat_nick,
3613 _("nick &lt;new nickname&gt;: Change your nickname."),
3614 NULL);
3615 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3617 id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL,
3618 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
3619 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
3620 jabber_cmd_chat_part, _("part [message]: Leave the room."),
3621 NULL);
3622 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3624 id = purple_cmd_register("register", "", PURPLE_CMD_P_PRPL,
3625 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
3626 "prpl-jabber", jabber_cmd_chat_register,
3627 _("register: Register with a chat room."), NULL);
3628 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3630 /* XXX: there needs to be a core /topic cmd, methinks */
3631 id = purple_cmd_register("topic", "s", PURPLE_CMD_P_PRPL,
3632 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
3633 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
3634 jabber_cmd_chat_topic,
3635 _("topic [new topic]: View or change the topic."),
3636 NULL);
3637 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3639 id = purple_cmd_register("ban", "ws", PURPLE_CMD_P_PRPL,
3640 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
3641 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
3642 jabber_cmd_chat_ban,
3643 _("ban &lt;user&gt; [reason]: Ban a user from the room."),
3644 NULL);
3645 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3647 id = purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PRPL,
3648 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
3649 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
3650 jabber_cmd_chat_affiliate,
3651 _("affiliate &lt;owner|admin|member|outcast|none&gt; [nick1] [nick2] ...: Get the users with an affiliation or set users' affiliation with the room."),
3652 NULL);
3653 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3655 id = purple_cmd_register("role", "ws", PURPLE_CMD_P_PRPL,
3656 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
3657 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
3658 jabber_cmd_chat_role,
3659 _("role &lt;moderator|participant|visitor|none&gt; [nick1] [nick2] ...: Get the users with a role or set users' role with the room."),
3660 NULL);
3661 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3663 id = purple_cmd_register("invite", "ws", PURPLE_CMD_P_PRPL,
3664 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
3665 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
3666 jabber_cmd_chat_invite,
3667 _("invite &lt;user&gt; [message]: Invite a user to the room."),
3668 NULL);
3669 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3671 id = purple_cmd_register("join", "ws", PURPLE_CMD_P_PRPL,
3672 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
3673 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
3674 jabber_cmd_chat_join,
3675 _("join: &lt;room&gt; [password]: Join a chat on this server."),
3676 /* _("join: &lt;room[@server]&gt; [password]: Join a chat."), */
3677 NULL);
3678 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3680 id = purple_cmd_register("kick", "ws", PURPLE_CMD_P_PRPL,
3681 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
3682 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
3683 jabber_cmd_chat_kick,
3684 _("kick &lt;user&gt; [reason]: Kick a user from the room."),
3685 NULL);
3686 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3688 id = purple_cmd_register("msg", "ws", PURPLE_CMD_P_PRPL,
3689 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
3690 "prpl-jabber", jabber_cmd_chat_msg,
3691 _("msg &lt;user&gt; &lt;message&gt;: Send a private message to another user."),
3692 NULL);
3693 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3695 id = purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL,
3696 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
3697 PURPLE_CMD_FLAG_PRPL_ONLY,
3698 "prpl-jabber", jabber_cmd_ping,
3699 _("ping &lt;jid&gt;: Ping a user/component/server."),
3700 NULL);
3701 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3703 id = purple_cmd_register("buzz", "w", PURPLE_CMD_P_PRPL,
3704 PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY |
3705 PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
3706 "prpl-jabber", jabber_cmd_buzz,
3707 _("buzz: Buzz a user to get their attention"), NULL);
3708 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3710 id = purple_cmd_register("mood", "ws", PURPLE_CMD_P_PRPL,
3711 PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
3712 PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
3713 "prpl-jabber", jabber_cmd_mood,
3714 _("mood: Set current user mood"), NULL);
3715 commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
3717 g_hash_table_insert(jabber_cmds, plugin, commands);
3720 static void cmds_free_func(gpointer value)
3722 GSList *commands = value;
3723 while (commands) {
3724 purple_cmd_unregister(GPOINTER_TO_UINT(commands->data));
3725 commands = g_slist_delete_link(commands, commands);
3729 static void jabber_unregister_commands(PurplePlugin *plugin)
3731 g_hash_table_remove(jabber_cmds, plugin);
3734 /* IPC functions */
3737 * IPC function for determining if a contact supports a certain feature.
3739 * @param account The PurpleAccount
3740 * @param jid The full JID of the contact.
3741 * @param feature The feature's namespace.
3743 * @return TRUE if supports feature; else FALSE.
3745 static gboolean
3746 jabber_ipc_contact_has_feature(PurpleAccount *account, const gchar *jid,
3747 const gchar *feature)
3749 PurpleConnection *gc = purple_account_get_connection(account);
3750 JabberStream *js;
3751 JabberBuddy *jb;
3752 JabberBuddyResource *jbr;
3753 gchar *resource;
3755 if (!purple_account_is_connected(account))
3756 return FALSE;
3757 js = gc->proto_data;
3759 if (!(resource = jabber_get_resource(jid)) ||
3760 !(jb = jabber_buddy_find(js, jid, FALSE)) ||
3761 !(jbr = jabber_buddy_find_resource(jb, resource))) {
3762 g_free(resource);
3763 return FALSE;
3766 g_free(resource);
3768 return jabber_resource_has_capability(jbr, feature);
3771 static void
3772 jabber_ipc_add_feature(const gchar *feature)
3774 if (!feature)
3775 return;
3776 jabber_add_feature(feature, 0);
3778 /* send presence with new caps info for all connected accounts */
3779 jabber_caps_broadcast_change();
3782 static void
3783 jabber_do_init(void)
3785 GHashTable *ui_info = purple_core_get_ui_info();
3786 const gchar *ui_type;
3787 const gchar *type = "pc"; /* default client type, if unknown or
3788 unspecified */
3789 const gchar *ui_name = NULL;
3790 #ifdef HAVE_CYRUS_SASL
3791 /* We really really only want to do this once per process */
3792 static gboolean sasl_initialized = FALSE;
3793 #ifdef _WIN32
3794 UINT old_error_mode;
3795 gchar *sasldir;
3796 #endif
3797 int ret;
3798 #endif
3800 /* XXX - If any other plugin wants SASL this won't be good ... */
3801 #ifdef HAVE_CYRUS_SASL
3802 if (!sasl_initialized) {
3803 sasl_initialized = TRUE;
3804 #ifdef _WIN32
3805 sasldir = g_build_filename(wpurple_install_dir(), "sasl2", NULL);
3806 sasl_set_path(SASL_PATH_TYPE_PLUGIN, sasldir);
3807 g_free(sasldir);
3808 /* Suppress error popups for failing to load sasl plugins */
3809 old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
3810 #endif
3811 if ((ret = sasl_client_init(NULL)) != SASL_OK) {
3812 purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret);
3814 #ifdef _WIN32
3815 /* Restore the original error mode */
3816 SetErrorMode(old_error_mode);
3817 #endif
3819 #endif
3821 jabber_cmds = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, cmds_free_func);
3823 ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL;
3824 if (ui_type) {
3825 if (strcmp(ui_type, "pc") == 0 ||
3826 strcmp(ui_type, "console") == 0 ||
3827 strcmp(ui_type, "phone") == 0 ||
3828 strcmp(ui_type, "handheld") == 0 ||
3829 strcmp(ui_type, "web") == 0 ||
3830 strcmp(ui_type, "bot") == 0) {
3831 type = ui_type;
3835 if (ui_info)
3836 ui_name = g_hash_table_lookup(ui_info, "name");
3837 if (ui_name == NULL)
3838 ui_name = PACKAGE;
3840 jabber_add_identity("client", type, NULL, ui_name);
3842 /* initialize jabber_features list */
3843 jabber_add_feature(NS_LAST_ACTIVITY, 0);
3844 jabber_add_feature(NS_OOB_IQ_DATA, 0);
3845 jabber_add_feature(NS_ENTITY_TIME, 0);
3846 jabber_add_feature("jabber:iq:version", 0);
3847 jabber_add_feature("jabber:x:conference", 0);
3848 jabber_add_feature(NS_BYTESTREAMS, 0);
3849 jabber_add_feature("http://jabber.org/protocol/caps", 0);
3850 jabber_add_feature("http://jabber.org/protocol/chatstates", 0);
3851 jabber_add_feature(NS_DISCO_INFO, 0);
3852 jabber_add_feature(NS_DISCO_ITEMS, 0);
3853 jabber_add_feature(NS_IBB, 0);
3854 jabber_add_feature("http://jabber.org/protocol/muc", 0);
3855 jabber_add_feature("http://jabber.org/protocol/muc#user", 0);
3856 jabber_add_feature("http://jabber.org/protocol/si", 0);
3857 jabber_add_feature(NS_SI_FILE_TRANSFER, 0);
3858 jabber_add_feature(NS_XHTML_IM, 0);
3859 jabber_add_feature(NS_PING, 0);
3861 /* Buzz/Attention */
3862 jabber_add_feature(NS_ATTENTION, jabber_buzz_isenabled);
3864 /* Bits Of Binary */
3865 jabber_add_feature(NS_BOB, 0);
3867 /* Jingle features! */
3868 jabber_add_feature(JINGLE, 0);
3870 #ifdef USE_VV
3871 jabber_add_feature(NS_GOOGLE_PROTOCOL_SESSION, jabber_audio_enabled);
3872 jabber_add_feature(NS_GOOGLE_VOICE, jabber_audio_enabled);
3873 jabber_add_feature(NS_GOOGLE_VIDEO, jabber_video_enabled);
3874 jabber_add_feature(NS_GOOGLE_CAMERA, jabber_video_enabled);
3875 jabber_add_feature(JINGLE_APP_RTP, 0);
3876 jabber_add_feature(JINGLE_APP_RTP_SUPPORT_AUDIO, jabber_audio_enabled);
3877 jabber_add_feature(JINGLE_APP_RTP_SUPPORT_VIDEO, jabber_video_enabled);
3878 jabber_add_feature(JINGLE_TRANSPORT_RAWUDP, 0);
3879 jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, 0);
3881 g_signal_connect(G_OBJECT(purple_media_manager_get()), "ui-caps-changed",
3882 G_CALLBACK(jabber_caps_broadcast_change), NULL);
3883 #endif
3885 /* reverse order of unload_plugin */
3886 jabber_iq_init();
3887 jabber_presence_init();
3888 jabber_caps_init();
3889 /* PEP things should be init via jabber_pep_init, not here */
3890 jabber_pep_init();
3891 jabber_data_init();
3892 jabber_bosh_init();
3894 /* TODO: Implement adding and retrieving own features via IPC API */
3896 jabber_ibb_init();
3897 jabber_si_init();
3899 jabber_auth_init();
3902 static void
3903 jabber_do_uninit(void)
3905 /* reverse order of jabber_do_init */
3906 jabber_bosh_uninit();
3907 jabber_data_uninit();
3908 jabber_si_uninit();
3909 jabber_ibb_uninit();
3910 /* PEP things should be uninit via jabber_pep_uninit, not here */
3911 jabber_pep_uninit();
3912 jabber_caps_uninit();
3913 jabber_presence_uninit();
3914 jabber_iq_uninit();
3916 #ifdef USE_VV
3917 g_signal_handlers_disconnect_by_func(G_OBJECT(purple_media_manager_get()),
3918 G_CALLBACK(jabber_caps_broadcast_change), NULL);
3919 #endif
3921 jabber_auth_uninit();
3922 jabber_features_destroy();
3923 jabber_identities_destroy();
3925 g_hash_table_destroy(jabber_cmds);
3926 jabber_cmds = NULL;
3929 void jabber_plugin_init(PurplePlugin *plugin)
3931 ++plugin_ref;
3933 if (plugin_ref == 1)
3934 jabber_do_init();
3936 jabber_register_commands(plugin);
3938 /* IPC functions */
3939 purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature),
3940 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER,
3941 purple_value_new(PURPLE_TYPE_BOOLEAN), 3,
3942 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_ACCOUNT),
3943 purple_value_new(PURPLE_TYPE_STRING),
3944 purple_value_new(PURPLE_TYPE_STRING));
3946 purple_plugin_ipc_register(plugin, "add_feature", PURPLE_CALLBACK(jabber_ipc_add_feature),
3947 purple_marshal_VOID__POINTER,
3948 NULL, 1,
3949 purple_value_new(PURPLE_TYPE_STRING));
3951 purple_plugin_ipc_register(plugin, "register_namespace_watcher",
3952 PURPLE_CALLBACK(jabber_iq_signal_register),
3953 purple_marshal_VOID__POINTER_POINTER,
3954 NULL, 2,
3955 purple_value_new(PURPLE_TYPE_STRING), /* node */
3956 purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
3958 purple_plugin_ipc_register(plugin, "unregister_namespace_watcher",
3959 PURPLE_CALLBACK(jabber_iq_signal_unregister),
3960 purple_marshal_VOID__POINTER_POINTER,
3961 NULL, 2,
3962 purple_value_new(PURPLE_TYPE_STRING), /* node */
3963 purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
3965 purple_signal_register(plugin, "jabber-register-namespace-watcher",
3966 purple_marshal_VOID__POINTER_POINTER,
3967 NULL, 2,
3968 purple_value_new(PURPLE_TYPE_STRING), /* node */
3969 purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
3971 purple_signal_register(plugin, "jabber-unregister-namespace-watcher",
3972 purple_marshal_VOID__POINTER_POINTER,
3973 NULL, 2,
3974 purple_value_new(PURPLE_TYPE_STRING), /* node */
3975 purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
3977 purple_signal_connect(plugin, "jabber-register-namespace-watcher",
3978 plugin, PURPLE_CALLBACK(jabber_iq_signal_register), NULL);
3979 purple_signal_connect(plugin, "jabber-unregister-namespace-watcher",
3980 plugin, PURPLE_CALLBACK(jabber_iq_signal_unregister), NULL);
3983 purple_signal_register(plugin, "jabber-receiving-xmlnode",
3984 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
3985 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
3986 purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
3988 purple_signal_register(plugin, "jabber-sending-xmlnode",
3989 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
3990 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
3991 purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
3994 * Do not remove this or the plugin will fail. Completely. You have been
3995 * warned!
3997 purple_signal_connect_priority(plugin, "jabber-sending-xmlnode",
3998 plugin, PURPLE_CALLBACK(jabber_send_signal_cb),
3999 NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
4001 purple_signal_register(plugin, "jabber-sending-text",
4002 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
4003 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
4004 purple_value_new_outgoing(PURPLE_TYPE_STRING));
4006 purple_signal_register(plugin, "jabber-receiving-message",
4007 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER_POINTER,
4008 purple_value_new(PURPLE_TYPE_BOOLEAN), 6,
4009 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
4010 purple_value_new(PURPLE_TYPE_STRING), /* type */
4011 purple_value_new(PURPLE_TYPE_STRING), /* id */
4012 purple_value_new(PURPLE_TYPE_STRING), /* from */
4013 purple_value_new(PURPLE_TYPE_STRING), /* to */
4014 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
4016 purple_signal_register(plugin, "jabber-receiving-iq",
4017 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
4018 purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
4019 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
4020 purple_value_new(PURPLE_TYPE_STRING), /* type */
4021 purple_value_new(PURPLE_TYPE_STRING), /* id */
4022 purple_value_new(PURPLE_TYPE_STRING), /* from */
4023 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
4025 purple_signal_register(plugin, "jabber-watched-iq",
4026 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
4027 purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
4028 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
4029 purple_value_new(PURPLE_TYPE_STRING), /* type */
4030 purple_value_new(PURPLE_TYPE_STRING), /* id */
4031 purple_value_new(PURPLE_TYPE_STRING), /* from */
4032 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); /* child */
4034 purple_signal_register(plugin, "jabber-receiving-presence",
4035 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER,
4036 purple_value_new(PURPLE_TYPE_BOOLEAN), 4,
4037 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
4038 purple_value_new(PURPLE_TYPE_STRING), /* type */
4039 purple_value_new(PURPLE_TYPE_STRING), /* from */
4040 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
4043 void jabber_plugin_uninit(PurplePlugin *plugin)
4045 g_return_if_fail(plugin_ref > 0);
4047 purple_signals_unregister_by_instance(plugin);
4048 purple_plugin_ipc_unregister_all(plugin);
4050 jabber_unregister_commands(plugin);
4052 --plugin_ref;
4053 if (plugin_ref == 0)
4054 jabber_do_uninit();