2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "accountopt.h"
30 #include "connection.h"
31 #include "conversation.h"
37 #include "pluginpref.h"
54 #include "google/google.h"
55 #include "google/google_roster.h"
56 #include "google/google_session.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
)
96 g_free(js
->stream_id
);
100 open_stream
= g_strdup_printf("<stream:stream to='%s' "
101 "xmlns='" NS_XMPP_CLIENT
"' "
102 "xmlns:stream='" NS_XMPP_STREAMS
"' "
105 /* setup the parser fresh for each stream */
106 jabber_parser_setup(js
);
107 jabber_send_raw(js
, open_stream
, -1);
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
);
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
);
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
);
141 static void jabber_bind_result_cb(JabberStream
*js
, const char *from
,
142 JabberIqType type
, const char *id
,
143 xmlnode
*packet
, gpointer data
)
147 if (type
== JABBER_IQ_RESULT
&&
148 (bind
= xmlnode_get_child_with_namespace(packet
, "bind", NS_XMPP_BIND
))) {
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"));
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
);
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
);
179 jabber_session_init(js
);
182 static char *jabber_prep_resource(char *input
) {
183 char hostname
[256], /* current hostname */
186 /* Empty resource == don't send any */
187 if (input
== NULL
|| *input
== '\0')
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
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
, '.')))
212 return purple_strreplace(input
, "__HOSTNAME__", hostname
);
216 jabber_process_starttls(JabberStream
*js
, xmlnode
*packet
)
218 PurpleAccount
*account
;
221 account
= purple_connection_get_account(js
->gc
);
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()) {
234 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1);
239 if(purple_ssl_is_supported()) {
241 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1);
244 purple_debug_warning("jabber", "No libpurple TLS/SSL support found.");
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."));
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."));
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
);
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."));
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
);
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
);
328 static void tls_init(JabberStream
*js
);
330 void jabber_process_packet(JabberStream
*js
, xmlnode
**packet
)
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 */
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
);
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
);
370 if (g_str_equal(name
, "proceed"))
372 /* TODO: Handle <failure/>, I guess? */
375 purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet
)->name
);
379 static int jabber_do_send(JabberStream
*js
, const char *data
, int len
)
384 ret
= purple_ssl_write(js
->gsc
, data
, len
);
386 ret
= write(js
->fd
, data
, len
);
391 static void jabber_send_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
393 JabberStream
*js
= data
;
395 writelen
= purple_circ_buffer_get_max_read(js
->write_buffer
);
398 purple_input_remove(js
->writeh
);
403 ret
= jabber_do_send(js
, js
->write_buffer
->outptr
, writelen
);
405 if (ret
< 0 && errno
== EAGAIN
)
408 gchar
*tmp
= g_strdup_printf(_("Lost connection with server: %s"),
410 purple_connection_error_reason(js
->gc
,
411 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
416 purple_circ_buffer_mark_read(js
->write_buffer
, ret
);
419 static gboolean
do_jabber_send_raw(JabberStream
*js
, const char *data
, int len
)
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
);
430 ret
= jabber_do_send(js
, data
, len
);
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"),
446 purple_connection_error_reason(js
->gc
,
447 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
452 } else if (ret
< len
) {
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
);
466 void jabber_send_raw(JabberStream
*js
, const char *data
, int len
)
468 PurpleConnection
*gc
;
469 PurpleAccount
*account
;
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 */
495 data_start
= text
+ (tag_end
- data
) + 1;
497 last_part
= strchr(data_start
, '<');
501 username
= purple_connection_get_display_name(gc
);
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
,
508 last_part
? "password removed" : "",
509 last_part
? last_part
: "");
514 purple_signal_emit(purple_connection_get_prpl(gc
), "jabber-sending-text", gc
, &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) {
527 if (!js
->gsc
&& js
->fd
<0)
528 g_return_if_reached();
536 towrite
= MIN((len
- pos
), js
->sasl_maxbuf
);
538 rc
= sasl_encode(js
->sasl
, &data
[pos
], towrite
,
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
,
555 /* do_jabber_send_raw returns FALSE when it throws a
558 if (!do_jabber_send_raw(js
, out
, olen
))
566 jabber_bosh_connection_send_raw(js
->bosh
, data
);
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
);
585 void jabber_send_signal_cb(PurpleConnection
*pc
, xmlnode
**packet
,
595 g_return_if_fail(PURPLE_CONNECTION_IS_VALID(pc
));
597 js
= purple_connection_get_protocol_data(pc
);
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
);
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;
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
) {
634 jabber_keepalive_ping(js
);
635 js
->keepalive_timeout
= purple_timeout_add_seconds(120,
636 (GSourceFunc
)(jabber_keepalive_timeout
), gc
);
641 jabber_recv_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
642 PurpleInputCondition cond
)
644 PurpleConnection
*gc
= data
;
645 JabberStream
*js
= gc
->proto_data
;
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
);
658 purple_debug_info("jabber", "Recv (ssl)(%d): %s\n", len
, buf
);
659 jabber_parser_process(js
, buf
, len
);
661 jabber_stream_init(js
);
664 if(len
< 0 && errno
== EAGAIN
)
669 tmp
= g_strdup_printf(_("Server closed the connection"));
671 tmp
= g_strdup_printf(_("Lost connection with server: %s"),
673 purple_connection_error_reason(js
->gc
,
674 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
680 jabber_recv_cb(gpointer data
, gint source
, PurpleInputCondition condition
)
682 PurpleConnection
*gc
= data
;
683 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
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) {
697 rc
= sasl_decode(js
->sasl
, buf
, len
, &out
, &olen
);
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
,
708 } else if (olen
> 0) {
709 purple_debug_info("jabber", "RecvSASL (%u): %s\n", olen
, out
);
710 jabber_parser_process(js
, out
, olen
);
712 jabber_stream_init(js
);
718 purple_debug_info("jabber", "Recv (%d): %s\n", len
, buf
);
719 jabber_parser_process(js
, buf
, len
);
721 jabber_stream_init(js
);
722 } else if(len
< 0 && errno
== EAGAIN
) {
727 tmp
= g_strdup_printf(_("Server closed the connection"));
729 tmp
= g_strdup_printf(_("Lost connection with server: %s"),
731 purple_connection_error_reason(js
->gc
,
732 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
738 jabber_login_callback_ssl(gpointer data
, PurpleSslConnection
*gsc
,
739 PurpleInputCondition cond
)
741 PurpleConnection
*gc
= data
;
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();
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
);
762 txt_resolved_cb(GList
*responses
, gpointer data
)
764 JabberStream
*js
= data
;
765 gboolean found
= FALSE
;
767 js
->srv_query_data
= NULL
;
770 PurpleTxtResponse
*resp
= responses
->data
;
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]);
780 purple_txt_response_destroy(resp
);
781 responses
= g_list_delete_link(responses
, responses
);
786 jabber_bosh_connection_connect(js
->bosh
);
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"));
799 g_list_foreach(responses
, (GFunc
)purple_txt_response_destroy
, NULL
);
800 g_list_free(responses
);
805 jabber_login_callback(gpointer data
, gint source
, const gchar
*error
)
807 PurpleConnection
*gc
= data
;
808 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
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
);
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
);
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
);
835 jabber_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
838 PurpleConnection
*gc
= data
;
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
));
847 purple_connection_ssl_error (gc
, error
);
850 static void tls_init(JabberStream
*js
)
852 purple_input_remove(js
->gc
->inpa
);
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 */
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
);
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
) {
876 purple_connection_error_reason(js
->gc
,
877 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
878 _("Unable to connect"));
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
))
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),
904 static void srv_resolved_cb(PurpleSrvResponse
*resp
, int results
, gpointer data
)
906 JabberStream
*js
= data
;
907 js
->srv_query_data
= NULL
;
912 js
->max_srv_rec_idx
= results
;
915 jabber_login_connect(js
, js
->user
->domain
, js
->user
->domain
,
916 purple_account_get_int(purple_connection_get_account(js
->gc
), "port", 5222),
921 static JabberStream
*
922 jabber_stream_new(PurpleAccount
*account
)
924 PurpleConnection
*gc
= purple_account_get_connection(account
);
926 PurplePresence
*presence
;
930 js
= gc
->proto_data
= g_new0(JabberStream
, 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')
939 js
->user
= jabber_id_new(user
);
942 purple_connection_error_reason(gc
,
943 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
944 _("Invalid XMPP ID"));
946 /* Destroying the connection will free the JabberStream */
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."));
955 /* Destroying the connection will free the JabberStream */
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."));
964 /* Destroying the connection will free the JabberStream */
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
);
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
,
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);
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
;
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
);
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
,
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.
1035 js
->bosh
= jabber_bosh_connection_init(js
, bosh_url
);
1037 jabber_bosh_connection_connect(js
->bosh
);
1039 purple_connection_error_reason(gc
,
1040 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
1041 _("Malformed BOSH URL"));
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
);
1056 purple_connection_error_reason(gc
,
1057 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT
,
1058 _("Unable to establish SSL connection"));
1061 purple_connection_error_reason(gc
,
1062 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT
,
1063 _("SSL support unavailable"));
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
);
1075 js
->srv_query_data
= purple_srv_resolve("xmpp-client",
1076 "tcp", js
->user
->domain
, srv_resolved_cb
, js
);
1081 jabber_login(PurpleAccount
*account
)
1083 PurpleConnection
*gc
= purple_account_get_connection(account
);
1085 PurpleStoredImage
*image
;
1087 gc
->flags
|= PURPLE_CONNECTION_HTML
|
1088 PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
1089 js
= jabber_stream_new(account
);
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
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
);
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
);
1128 jabber_connection_schedule_close(JabberStream
*js
)
1130 purple_timeout_add(0, conn_close_cb
, js
);
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
);
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
);
1149 g_return_if_fail(to
!= NULL
);
1150 buf
= g_strdup_printf(_("Registration to %s successful"),
1153 purple_notify_info(NULL
, _("Registration Successful"),
1154 _("Registration Successful"), buf
);
1157 char *msg
= jabber_parse_error(js
, packet
, NULL
);
1160 msg
= g_strdup(_("Unknown Error"));
1162 purple_notify_error(NULL
, _("Registration Failed"),
1163 _("Registration Failed"), msg
);
1165 if(account
->registration_cb
)
1166 (account
->registration_cb
)(account
, FALSE
, account
->registration_cb_user_data
);
1169 if(js
->registration
)
1170 jabber_connection_schedule_close(js
);
1174 jabber_unregistration_result_cb(JabberStream
*js
, const char *from
,
1175 JabberIqType type
, const char *id
,
1176 xmlnode
*packet
, gpointer 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"),
1188 purple_notify_info(NULL
, _("Unregistration Successful"),
1189 _("Unregistration Successful"), buf
);
1192 char *msg
= jabber_parse_error(js
, packet
, NULL
);
1195 msg
= g_strdup(_("Unknown Error"));
1197 purple_notify_error(NULL
, _("Unregistration Failed"),
1198 _("Unregistration Failed"), msg
);
1204 typedef struct _JabberRegisterCBData
{
1207 } JabberRegisterCBData
;
1210 jabber_register_cb(JabberRegisterCBData
*cbdata
, PurpleRequestFields
*fields
)
1212 GList
*groups
, *flds
;
1217 iq
= jabber_iq_new_query(cbdata
->js
, JABBER_IQ_SET
, "jabber:iq:register");
1218 query
= xmlnode_get_child(iq
->node
, "query");
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
);
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) */
1234 iq
= jabber_iq_new_query(cbdata
->js
, JABBER_IQ_SET
, "jabber:iq:register");
1235 query
= xmlnode_get_child(iq
->node
, "query");
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
);
1247 const char *ids
[] = {"username", "password", "name", "email", "nick", "first",
1248 "last", "address", "city", "state", "zip", "phone", "url", "date",
1250 const char *value
= purple_request_field_string_get_value(field
);
1252 for (i
= 0; ids
[i
]; i
++) {
1253 if (!strcmp(id
, ids
[i
]))
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
);
1279 jabber_iq_set_callback(iq
, jabber_registration_result_cb
, cbdata
->who
);
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
);
1298 static void jabber_register_x_data_cb(JabberStream
*js
, xmlnode
*result
, gpointer data
)
1304 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:register");
1305 query
= xmlnode_get_child(iq
->node
, "query");
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
);
1315 static const struct {
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") },
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
;
1342 JabberRegisterCBData
*cbdata
;
1343 gboolean registered
= FALSE
;
1346 if (type
!= JABBER_IQ_RESULT
)
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")) {
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
);
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
));
1371 } else if((x
= xmlnode_get_child_with_namespace(query
, "x", NS_OOB_X_DATA
))) {
1374 if((url
= xmlnode_get_child(x
, "url"))) {
1376 if((href
= xmlnode_get_data(url
))) {
1377 purple_notify_uri(NULL
, 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
);
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
);
1402 field
= purple_request_field_string_new("username", _("Username"), data
, FALSE
);
1404 purple_request_field_group_add_field(group
, field
);
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
);
1412 char *data
= xmlnode_get_data(node
);
1413 field
= purple_request_field_string_new("password", _("Password"), data
, FALSE
);
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
);
1426 char *data
= xmlnode_get_data(node
);
1427 field
= purple_request_field_string_new("name", _("Name"), data
, FALSE
);
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
),
1439 purple_request_field_group_add_field(group
, field
);
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
);
1452 instructions
= g_strdup(_("Please fill out the information below "
1453 "to change your account registration."));
1455 instructions
= g_strdup(_("Please fill out the information below "
1456 "to register your new account."));
1458 cbdata
= g_new0(JabberRegisterCBData
, 1);
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
,
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
,
1483 g_free(instructions
);
1486 void jabber_register_start(JabberStream
*js
)
1490 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:register");
1494 void jabber_register_gateway(JabberStream
*js
, const char *gateway
) {
1497 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:register");
1498 xmlnode_set_attrib(iq
->node
, "to", gateway
);
1502 void jabber_register_account(PurpleAccount
*account
)
1506 js
= jabber_stream_new(account
);
1510 js
->registration
= TRUE
;
1511 jabber_stream_connect(js
);
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
);
1527 if(js
->unregistration_cb
)
1528 js
->unregistration_cb(account
, FALSE
, js
->unregistration_user_data
);
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
) {
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
);
1554 void jabber_unregister_account(PurpleAccount
*account
, PurpleAccountUnregistrationCb cb
, void *user_data
) {
1555 PurpleConnection
*gc
= purple_account_get_connection(account
);
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
;
1568 js
= gc
->proto_data
;
1570 if (js
->unregistration
) {
1571 purple_debug_error("jabber", "Unregistration in process; ignoring duplicate request.\n");
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
);
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
);
1603 purple_ssl_close(js
->gsc
);
1604 } else if (js
->fd
> 0) {
1606 purple_input_remove(js
->gc
->inpa
);
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
);
1620 g_hash_table_destroy(js
->buddies
);
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
;
1638 g_free(sh
->zeroconf
);
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
);
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
);
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
1663 sasl_dispose(&js
->sasl
);
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
);
1670 g_free(js
->serverFQDN
);
1671 while(js
->commands
) {
1672 JabberAdHocCommands
*cmd
= js
->commands
->data
;
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
);
1702 g_free(js
->stun_ip
);
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
);
1720 js
->google_relay_requests
=
1721 g_list_delete_link(js
->google_relay_requests
,
1722 js
->google_relay_requests
);
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)
1737 case JABBER_STREAM_OFFLINE
:
1739 case JABBER_STREAM_CONNECTING
:
1740 purple_connection_update_progress(js
->gc
, _("Connecting"), 1,
1741 JABBER_CONNECT_STEPS
);
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
);
1748 case JABBER_STREAM_INITIALIZING_ENCRYPTION
:
1749 purple_connection_update_progress(js
->gc
, _("Initializing SSL/TLS"),
1750 6, JABBER_CONNECT_STEPS
);
1752 case JABBER_STREAM_AUTHENTICATING
:
1753 purple_connection_update_progress(js
->gc
, _("Authenticating"),
1754 js
->gsc
? 7 : 3, JABBER_CONNECT_STEPS
);
1756 case JABBER_STREAM_POST_AUTH
:
1757 purple_connection_update_progress(js
->gc
, _("Re-initializing Stream"),
1758 (js
->gsc
? 8 : 4), JABBER_CONNECT_STEPS
);
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
);
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
,
1797 PurpleAccount
*account
;
1800 if (!jabber_is_own_account(js
, from
)) {
1802 result
= jabber_iq_new(js
, JABBER_IQ_ERROR
);
1803 xmlnode_set_attrib(result
->node
, "id", id
);
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
);
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 */
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
);
1841 for ( ; item
; item
= xmlnode_get_next_twin(item
)) {
1842 const char *jid
= xmlnode_get_attrib(item
, "jid");
1843 if (jid
== NULL
|| *jid
== '\0')
1847 purple_privacy_deny_add(account
, jid
, TRUE
);
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
)
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
)
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
);
1906 void jabber_add_deny(PurpleConnection
*gc
, const char *who
)
1910 xmlnode
*block
, *item
;
1912 g_return_if_fail(who
!= NULL
&& *who
!= '\0');
1914 js
= purple_connection_get_protocol_data(gc
);
1918 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
)
1920 jabber_google_roster_add_deny(js
, who
);
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
);
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
);
1942 void jabber_rem_deny(PurpleConnection
*gc
, const char *who
)
1946 xmlnode
*unblock
, *item
;
1948 g_return_if_fail(who
!= NULL
&& *who
!= '\0');
1950 js
= purple_connection_get_protocol_data(gc
);
1954 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
)
1956 jabber_google_roster_rem_deny(js
, who
);
1960 if (!(js
->server_caps
& JABBER_CAP_BLOCKING
))
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
);
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) {
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
);
2002 static void jabber_features_destroy(void)
2004 while (jabber_features
) {
2005 JabberFeature
*feature
= jabber_features
->data
;
2006 g_free(feature
->namespace);
2008 jabber_features
= g_list_delete_link(jabber_features
, jabber_features
);
2013 jabber_identity_compare(gconstpointer a
, gconstpointer b
)
2015 const JabberIdentity
*ac
;
2016 const JabberIdentity
*bc
;
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
) {
2027 } else if (ac
->lang
&& !bc
->lang
) {
2029 } else if (!ac
->lang
&& bc
->lang
) {
2032 return strcmp(ac
->lang
, bc
->lang
);
2042 void jabber_add_identity(const gchar
*category
, const gchar
*type
,
2043 const gchar
*lang
, const gchar
*name
)
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
))
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
);
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
);
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;
2100 jabber_bosh_connection_send_keepalive(js
->bosh
);
2102 jabber_send_raw(js
, "\t", 1);
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
,
2121 const char *jabber_list_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
2126 const char* jabber_list_emblem(PurpleBuddy
*b
)
2129 JabberBuddy
*jb
= NULL
;
2130 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(b
));
2135 js
= gc
->proto_data
;
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";
2146 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jb
, NULL
);
2148 const gchar
*client_type
=
2149 jabber_resource_get_identity_category_type(jbr
, "client");
2152 if (strcmp(client_type
, "phone") == 0) {
2154 } else if (strcmp(client_type
, "web") == 0) {
2156 } else if (strcmp(client_type
, "handheld") == 0) {
2158 } else if (strcmp(client_type
, "bot") == 0) {
2161 /* the default value "pc" falls through and has no emblem */
2169 char *jabber_status_text(PurpleBuddy
*b
)
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
);
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
);
2203 jabber_tooltip_add_resource_text(JabberBuddyResource
*jbr
,
2204 PurpleNotifyUserInfo
*user_info
, gboolean multiple_resources
)
2208 char *label
, *value
;
2212 text
= g_markup_escape_text(jbr
->status
, -1);
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
)) {
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
);
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
) {
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
);
2247 void jabber_tooltip_text(PurpleBuddy
*b
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
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
);
2265 JabberBuddyResource
*jbr
= NULL
;
2266 PurplePresence
*presence
= purple_buddy_get_presence(b
);
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 */
2276 jabber_tooltip_add_resource_text(top_jbr
, user_info
,
2277 multiple_resources
);
2280 for(l
=jb
->resources
; l
; l
= l
->next
) {
2282 /* the remaining resources */
2283 if (jbr
!= top_jbr
) {
2284 jabber_tooltip_add_resource_text(jbr
, user_info
,
2285 multiple_resources
);
2290 PurpleStatus
*status
;
2292 status
= purple_presence_get_status(presence
, "mood");
2293 mood
= purple_status_get_attr_string(status
, PURPLE_MOOD_NAME
);
2295 const char *moodtext
;
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
;
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
);
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
);
2325 purple_notify_user_info_add_pair(user_info
, _("Now Listening"), playing
);
2330 if(jb
->subscription
& JABBER_SUB_FROM
) {
2331 if(jb
->subscription
& JABBER_SUB_TO
)
2333 else if(jb
->subscription
& JABBER_SUB_PENDING
)
2334 sub
= _("From (To pending)");
2338 if(jb
->subscription
& JABBER_SUB_TO
)
2340 else if(jb
->subscription
& JABBER_SUB_PENDING
)
2341 sub
= _("None (To pending)");
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
,
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
),
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
,
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
,
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
,
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
),
2446 types
= g_list_prepend(types
, type
);
2449 if(js->protocol_version == JABBER_PROTO_0_9)
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
),
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
),
2472 types
= g_list_prepend(types
, type
);
2474 return g_list_reverse(types
);
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
);
2488 char *msg
= jabber_parse_error(js
, packet
, NULL
);
2490 purple_notify_error(js
->gc
, _("Error changing password"),
2491 _("Error changing password"), msg
);
2498 static void jabber_password_change_cb(JabberStream
*js
,
2499 PurpleRequestFields
*fields
)
2501 const char *p1
, *p2
;
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
);
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
));
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"),
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)"),
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
),
2558 purple_connection_get_account(gc
), NULL
, NULL
,
2562 GList
*jabber_actions(PurplePlugin
*plugin
, gpointer context
)
2564 PurpleConnection
*gc
= (PurpleConnection
*) context
;
2565 JabberStream
*js
= gc
->proto_data
;
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
);
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");
2586 jabber_pep_init_actions(&m
);
2589 jabber_adhoc_init_server_commands(js
, &m
);
2594 PurpleChat
*jabber_find_blist_chat(PurpleAccount
*account
, const char *name
)
2596 PurpleBlistNode
*gnode
, *cnode
;
2599 if(!(jid
= jabber_id_new(name
)))
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
);
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
))
2613 if (purple_chat_get_account(chat
) != account
)
2616 components
= purple_chat_get_components(chat
);
2617 if(!(room
= g_hash_table_lookup(components
, "room")))
2619 if(!(server
= g_hash_table_lookup(components
, "server")))
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
);
2630 jabber_id_free(jid
);
2634 void jabber_convo_closed(PurpleConnection
*gc
, const char *who
)
2636 JabberStream
*js
= gc
->proto_data
;
2639 JabberBuddyResource
*jbr
;
2641 if(!(jid
= jabber_id_new(who
)))
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
,
2658 PurpleConnectionError
*reason
)
2661 const char *code
= NULL
, *text
= NULL
;
2662 const char *xmlns
= xmlnode_get_namespace(packet
);
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
);
2671 cdata
= xmlnode_get_data(t
);
2673 cdata
= xmlnode_get_data(error
);
2675 code
= xmlnode_get_attrib(error
, "code");
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")) {
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")) {
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");
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");
2805 text
= _("Stream Error");
2812 char *ret
= g_strdup_printf("%s%s%s", code
? code
: "",
2813 code
? ": " : "", text
? text
: cdata
);
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
);
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
);
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
);
2851 return PURPLE_CMD_RET_FAILED
;
2853 if (args
&& args
[0] && *args
[0])
2854 jabber_chat_change_topic(chat
, args
[0]);
2856 const char *cur
= purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv
));
2857 char *buf
, *tmp
, *tmp2
;
2860 tmp
= g_markup_escape_text(cur
, -1);
2861 tmp2
= purple_markup_linkify(tmp
);
2862 buf
= g_strdup_printf(_("current topic is: %s"), tmp2
);
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
));
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
;
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
);
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
;
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]);
2947 return PURPLE_CMD_RET_FAILED
;
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
;
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"),
2983 return PURPLE_CMD_RET_FAILED
;
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] : "",
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
;
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]);
3022 server
= jid
->domain
;
3023 handle
= jid
->resource
? jid
->resource
: chat
->handle
;
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
;
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
);
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
);
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);
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
) {
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
;
3118 jb
= jabber_buddy_find(js
, username
, FALSE
);
3120 *error
= g_strdup_printf(_("Unable to buzz, because there is nothing "
3121 "known about %s."), alias
);
3125 jbr
= jabber_buddy_find_resource(jb
, NULL
);
3127 *error
= g_strdup_printf(_("Unable to buzz, because %s might be offline."),
3132 if (jabber_resource_has_capability(jbr
, NS_ATTENTION
)) {
3133 xmlnode
*buzz
, *msg
= xmlnode_new("message");
3136 to
= g_strdup_printf("%s/%s", username
, jbr
->name
);
3137 xmlnode_set_attrib(msg
, "to", 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
);
3151 *error
= g_strdup_printf(_("Unable to buzz, because %s does "
3152 "not support it or does not wish to receive buzzes now."), alias
);
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
;
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
);
3173 return PURPLE_CMD_RET_FAILED
;
3179 buddy
= purple_find_buddy(conv
->account
, who
);
3181 alias
= purple_buddy_get_contact_alias(buddy
);
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
;
3198 types
= g_list_append(types
, purple_attention_type_new("Buzz", _("Buzz"),
3199 _("%s has buzzed you!"), _("Buzzing %s...")));
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)");
3217 purple_conversation_write(conv
, username
, error
, PURPLE_MESSAGE_ERROR
,
3229 gboolean
jabber_offline_message(const PurpleBuddy
*buddy
)
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
));
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
));
3254 PurpleAccount
*account
;
3256 PurpleMediaSessionType type
;
3258 } JabberMediaRequest
;
3261 jabber_media_cancel_cb(JabberMediaRequest
*request
,
3262 PurpleRequestFields
*fields
)
3264 g_free(request
->who
);
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
);
3280 g_free(request
->who
);
3286 jabber_initiate_media(PurpleAccount
*account
, const char *who
,
3287 PurpleMediaSessionType type
)
3290 JabberStream
*js
= (JabberStream
*)
3291 purple_account_get_connection(account
)->proto_data
;
3293 JabberBuddyResource
*jbr
= NULL
;
3297 purple_debug_error("jabber",
3298 "jabber_initiate_media: NULL stream\n");
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
);
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
);
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 */
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
);
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
);
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 */
3345 jbr
= jb
->resources
->data
;
3346 name
= g_strdup_printf("%s/%s", who
, jbr
->name
);
3347 result
= jabber_initiate_media(account
, name
, type
);
3351 /* we've got multiple resources,
3352 * we need to pick one to initiate with */
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
;
3366 name
= g_strdup_printf("%s/%s", who
, ljbr
->name
);
3367 caps
= jabber_get_media_caps(account
, name
);
3370 if ((type
& PURPLE_MEDIA_AUDIO
) &&
3371 (type
& PURPLE_MEDIA_VIDEO
)) {
3372 if (caps
& PURPLE_MEDIA_CAPS_AUDIO_VIDEO
) {
3374 purple_request_field_choice_add(
3377 } else if (type
& (PURPLE_MEDIA_AUDIO
) &&
3378 (caps
& PURPLE_MEDIA_CAPS_AUDIO
)) {
3380 purple_request_field_choice_add(
3382 }else if (type
& (PURPLE_MEDIA_VIDEO
) &&
3383 (caps
& PURPLE_MEDIA_CAPS_VIDEO
)) {
3385 purple_request_field_choice_add(
3391 purple_debug_error("jabber",
3392 "No resources available\n");
3396 if (g_list_length(purple_request_field_choice_get_labels(
3400 purple_request_field_destroy(field
);
3401 name
= g_strdup_printf("%s/%s", who
, jbr
->name
);
3402 result
= jabber_initiate_media(account
, name
, type
);
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
);
3430 PurpleMediaCaps
jabber_get_media_caps(PurpleAccount
*account
, const char *who
)
3433 JabberStream
*js
= (JabberStream
*)
3434 purple_account_get_connection(account
)->proto_data
;
3436 JabberBuddyResource
*jbr
;
3437 PurpleMediaCaps total
= PURPLE_MEDIA_CAPS_NONE
;
3439 GList
*specific
= NULL
, *l
;
3442 purple_debug_info("jabber",
3443 "jabber_can_do_media: NULL stream\n");
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. */
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
);
3462 purple_debug_error("jabber", "jabber_get_media_caps:"
3463 " Can't find resource %s\n", who
);
3467 l
= specific
= g_list_prepend(specific
, jbr
);
3470 /* we've got multiple resources, combine their caps */
3474 for (; l
; l
= l
->next
) {
3475 PurpleMediaCaps caps
= PURPLE_MEDIA_CAPS_NONE
;
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
;
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
;
3511 g_list_free(specific
);
3516 return PURPLE_MEDIA_CAPS_NONE
;
3520 gboolean
jabber_can_receive_file(PurpleConnection
*gc
, const char *who
)
3522 JabberStream
*js
= gc
->proto_data
;
3525 JabberBuddy
*jb
= jabber_buddy_find(js
, who
, FALSE
);
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 */
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... */
3549 /* we have caps for all the resources, see if at least one has
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
,
3557 || jabber_resource_has_capability(jbr
, NS_IBB
))) {
3569 jabber_cmd_mood(PurpleConversation
*conv
,
3570 const char *cmd
, char **args
, char **error
, void *data
)
3572 JabberStream
*js
= conv
->account
->gc
->proto_data
;
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
);
3581 jabber_mood_set(js
, args
[0], args
[1]);
3584 return PURPLE_CMD_RET_OK
;
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
;
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 <new nickname>: Change your nickname."),
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."),
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."),
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 <user> [reason]: Ban a user from the room."),
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 <owner|admin|member|outcast|none> [nick1] [nick2] ...: Get the users with an affiliation or set users' affiliation with the room."),
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 <moderator|participant|visitor|none> [nick1] [nick2] ...: Get the users with a role or set users' role with the room."),
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 <user> [message]: Invite a user to the room."),
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: <room> [password]: Join a chat on this server."),
3676 /* _("join: <room[@server]> [password]: Join a chat."), */
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 <user> [reason]: Kick a user from the room."),
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 <user> <message>: Send a private message to another user."),
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 <jid>: Ping a user/component/server."),
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
;
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
);
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.
3746 jabber_ipc_contact_has_feature(PurpleAccount
*account
, const gchar
*jid
,
3747 const gchar
*feature
)
3749 PurpleConnection
*gc
= purple_account_get_connection(account
);
3752 JabberBuddyResource
*jbr
;
3755 if (!purple_account_is_connected(account
))
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
))) {
3768 return jabber_resource_has_capability(jbr
, feature
);
3772 jabber_ipc_add_feature(const gchar
*feature
)
3776 jabber_add_feature(feature
, 0);
3778 /* send presence with new caps info for all connected accounts */
3779 jabber_caps_broadcast_change();
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
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
;
3794 UINT old_error_mode
;
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
;
3805 sasldir
= g_build_filename(wpurple_install_dir(), "sasl2", NULL
);
3806 sasl_set_path(SASL_PATH_TYPE_PLUGIN
, sasldir
);
3808 /* Suppress error popups for failing to load sasl plugins */
3809 old_error_mode
= SetErrorMode(SEM_FAILCRITICALERRORS
);
3811 if ((ret
= sasl_client_init(NULL
)) != SASL_OK
) {
3812 purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret
);
3815 /* Restore the original error mode */
3816 SetErrorMode(old_error_mode
);
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
;
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) {
3836 ui_name
= g_hash_table_lookup(ui_info
, "name");
3837 if (ui_name
== NULL
)
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);
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
);
3885 /* reverse order of unload_plugin */
3887 jabber_presence_init();
3889 /* PEP things should be init via jabber_pep_init, not here */
3894 /* TODO: Implement adding and retrieving own features via IPC API */
3903 jabber_do_uninit(void)
3905 /* reverse order of jabber_do_init */
3906 jabber_bosh_uninit();
3907 jabber_data_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();
3917 g_signal_handlers_disconnect_by_func(G_OBJECT(purple_media_manager_get()),
3918 G_CALLBACK(jabber_caps_broadcast_change
), NULL
);
3921 jabber_auth_uninit();
3922 jabber_features_destroy();
3923 jabber_identities_destroy();
3925 g_hash_table_destroy(jabber_cmds
);
3929 void jabber_plugin_init(PurplePlugin
*plugin
)
3933 if (plugin_ref
== 1)
3936 jabber_register_commands(plugin
);
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
,
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
,
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
,
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
,
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
,
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
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
);
4053 if (plugin_ref
== 0)