/* * purple - Jabber Protocol Plugin * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include #include #include #include #include #include #include "auth.h" #include "buddy.h" #include "caps.h" #include "chat.h" #include "data.h" #include "disco.h" #include "ibb.h" #include "iq.h" #include "jutil.h" #include "message.h" #include "parser.h" #include "presence.h" #include "jabber.h" #include "roster.h" #include "oob.h" #include "ping.h" #include "si.h" #include "xdata.h" #include "pep.h" #include "adhoccommands.h" #include "xmpp.h" #include "jingle/jingle.h" #include "jingle/content.h" #include "jingle/iceudp.h" #include "jingle/rawudp.h" #include "jingle/rtp.h" #include "jingle/session.h" #define PING_TIMEOUT 60 /* Send a whitespace keepalive to the server if we haven't sent * anything in the last 120 seconds */ #define DEFAULT_INACTIVITY_TIME 120 GList *jabber_features = NULL; GList *jabber_identities = NULL; static PurpleProtocol *xmpp_protocol = NULL; static GHashTable *jabber_cmds = NULL; /* PurpleProtocol * => GSList of ids */ static gint plugin_ref = 0; static void jabber_send_raw(PurpleProtocolServer *protocol_server, JabberStream *js, const gchar *data, gint len); static void jabber_remove_feature(const gchar *namespace); static gboolean jabber_initiate_media(PurpleProtocolMedia *media, PurpleAccount *account, const char *who, PurpleMediaSessionType type); static PurpleMediaCaps jabber_get_media_caps(PurpleProtocolMedia *media, PurpleAccount *account, const char *who); static void jabber_stream_init(JabberStream *js) { char *open_stream; g_free(js->stream_id); js->stream_id = NULL; open_stream = g_strdup_printf("", js->user->domain); /* setup the parser fresh for each stream */ jabber_parser_setup(js); jabber_send_raw(NULL, js, open_stream, -1); js->reinit = FALSE; g_free(open_stream); } static void jabber_session_initialized_cb(JabberStream *js, G_GNUC_UNUSED const char *from, JabberIqType type, G_GNUC_UNUSED const char *id, G_GNUC_UNUSED PurpleXmlNode *packet, G_GNUC_UNUSED gpointer data) { if (type == JABBER_IQ_RESULT) { jabber_disco_items_server(js); } else { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, ("Error initializing session")); } } static void jabber_session_init(JabberStream *js) { JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); PurpleXmlNode *session; jabber_iq_set_callback(iq, jabber_session_initialized_cb, NULL); session = purple_xmlnode_new_child(iq->node, "session"); purple_xmlnode_set_namespace(session, NS_XMPP_SESSION); jabber_iq_send(iq); } static void jabber_bind_result_cb(JabberStream *js, G_GNUC_UNUSED const char *from, JabberIqType type, G_GNUC_UNUSED const char *id, PurpleXmlNode *packet, G_GNUC_UNUSED gpointer data) { PurpleXmlNode *bind; if (type == JABBER_IQ_RESULT && (bind = purple_xmlnode_get_child_with_namespace(packet, "bind", NS_XMPP_BIND))) { PurpleXmlNode *jid; char *full_jid; if((jid = purple_xmlnode_get_child(bind, "jid")) && (full_jid = purple_xmlnode_get_data(jid))) { jabber_id_free(js->user); js->user = jabber_id_new(full_jid); if (js->user == NULL) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); g_free(full_jid); return; } js->user_jb = jabber_buddy_find(js, full_jid, TRUE); js->user_jb->subscription |= JABBER_SUB_BOTH; purple_connection_set_display_name(js->gc, full_jid); g_free(full_jid); } } else { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; char *msg = jabber_parse_error(js, packet, &reason); purple_connection_error(js->gc, reason, msg); g_free(msg); return; } jabber_session_init(js); } static char * jabber_prep_resource(char *input) { const gchar *hostname = NULL, *dot = NULL; gchar *result = NULL; /* Empty resource == don't send any */ if (input == NULL || *input == '\0') return NULL; if (strstr(input, "__HOSTNAME__") == NULL) return g_strdup(input); /* Replace __HOSTNAME__ with hostname */ hostname = g_get_host_name(); /* We want only the short hostname, not the FQDN - this will prevent the * resource string from being unreasonably long on systems which stuff the * whole FQDN in the hostname */ if ((dot = strchr(hostname, '.')) != NULL) { gchar *short_hostname = g_strndup(hostname, dot - hostname); result = purple_strreplace(input, "__HOSTNAME__", short_hostname); g_free(short_hostname); } else { result = purple_strreplace(input, "__HOSTNAME__", hostname); } return result; } static gboolean jabber_process_starttls(JabberStream *js, PurpleXmlNode *packet) { PurpleAccount *account = NULL; PurpleXmlNode *starttls = NULL; /* It's a secure BOSH connection, just return FALSE and skip, without doing * anything extra. XEP-0206 (XMPP Over BOSH): The client SHOULD ignore any * Transport Layer Security (TLS) feature since BOSH channel encryption * SHOULD be negotiated at the HTTP layer. * * Note: we are already receiving STARTTLS at this point from a SSL/TLS BOSH * connection, so it is not necessary to check if SSL is supported. */ if (js->bosh && jabber_bosh_connection_is_ssl(js->bosh)) { return FALSE; } /* Otherwise, it's a standard XMPP connection, or a HTTP (insecure) BOSH connection. * We request STARTTLS for standard XMPP connections, but we do nothing for insecure * BOSH connections, per XEP-0206. */ if(!js->bosh) { jabber_send_raw(NULL, js, "", -1); return TRUE; } /* It's an insecure standard XMPP connection, or an insecure BOSH connection, let's * ignore STARTTLS even it's required by the server to prevent disabling HTTP BOSH * entirely (sysadmin is responsible to provide HTTPS-only BOSH if security is required), * and emit errors if encryption is required by the user. */ starttls = purple_xmlnode_get_child(packet, "starttls"); if(!js->bosh && purple_xmlnode_get_child(starttls, "required")) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("Server requires TLS/SSL, but no TLS/SSL support was found.")); return TRUE; } account = purple_connection_get_account(js->gc); if (purple_strequal("require_tls", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("You require encryption, but no TLS/SSL support was found.")); return TRUE; } return FALSE; } void jabber_stream_features_parse(JabberStream *js, PurpleXmlNode *packet) { PurpleAccount *account = purple_connection_get_account(js->gc); const char *connection_security = purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS); if (purple_xmlnode_get_child(packet, "starttls")) { if (jabber_process_starttls(js, packet)) { jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); return; } } else if (purple_strequal(connection_security, "require_tls") && !jabber_stream_is_ssl(js)) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); return; } if(purple_xmlnode_get_child(packet, "mechanisms")) { jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); jabber_auth_start(js, packet); } else if(purple_xmlnode_get_child(packet, "bind")) { PurpleXmlNode *bind, *resource; char *requested_resource; JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); bind = purple_xmlnode_new_child(iq->node, "bind"); purple_xmlnode_set_namespace(bind, NS_XMPP_BIND); requested_resource = jabber_prep_resource(js->user->resource); if (requested_resource != NULL) { resource = purple_xmlnode_new_child(bind, "resource"); purple_xmlnode_insert_data(resource, requested_resource, -1); g_free(requested_resource); } jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL); jabber_iq_send(iq); } else if (purple_xmlnode_get_child_with_namespace(packet, "ver", NS_ROSTER_VERSIONING)) { js->server_caps |= JABBER_CAP_ROSTER_VERSIONING; } else /* if(purple_xmlnode_get_child_with_namespace(packet, "auth")) */ { /* If we get an empty stream:features packet, or we explicitly get * an auth feature with namespace http://jabber.org/features/iq-auth * we should revert back to iq:auth authentication, even though we're * connecting to an XMPP server. */ jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); jabber_auth_start_old(js); } } static void jabber_stream_handle_error(JabberStream *js, PurpleXmlNode *packet) { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; char *msg = jabber_parse_error(js, packet, &reason); purple_connection_error(js->gc, reason, msg); g_free(msg); } static void tls_init(JabberStream *js); void jabber_process_packet(JabberStream *js, PurpleXmlNode **packet) { const char *name; const char *xmlns; purple_signal_emit(purple_connection_get_protocol(js->gc), "jabber-receiving-xmlnode", js->gc, packet); /* if the signal leaves us with a null packet, we're done */ if(NULL == *packet) return; name = (*packet)->name; xmlns = purple_xmlnode_get_namespace(*packet); if (purple_strequal(name, "iq")) { jabber_iq_parse(js, *packet); } else if (purple_strequal(name, "presence")) { jabber_presence_parse(js, *packet); } else if (purple_strequal(name, "message")) { jabber_message_parse(js, *packet); } else if (purple_strequal(xmlns, NS_XMPP_STREAMS)) { if (purple_strequal(name, "features")) jabber_stream_features_parse(js, *packet); else if (purple_strequal(name, "error")) jabber_stream_handle_error(js, *packet); } else if (purple_strequal(xmlns, NS_XMPP_SASL)) { if (js->state != JABBER_STREAM_AUTHENTICATING) purple_debug_warning("jabber", "Ignoring spurious SASL stanza %s\n", name); else { if (purple_strequal(name, "challenge")) jabber_auth_handle_challenge(js, *packet); else if (purple_strequal(name, "success")) jabber_auth_handle_success(js, *packet); else if (purple_strequal(name, "failure")) jabber_auth_handle_failure(js, *packet); } } else if (purple_strequal(xmlns, NS_XMPP_TLS)) { if (js->state != JABBER_STREAM_INITIALIZING_ENCRYPTION || G_IS_TLS_CONNECTION(js->stream)) { purple_debug_warning("jabber", "Ignoring spurious %s\n", name); } else { if (purple_strequal(name, "proceed")) tls_init(js); /* TODO: Handle , I guess? */ } } else { purple_debug_warning("jabber", "Unknown packet: %s\n", name); } } static void jabber_push_bytes_cb(GObject *source, GAsyncResult *res, gpointer data) { PurpleQueuedOutputStream *stream = PURPLE_QUEUED_OUTPUT_STREAM(source); JabberStream *js = data; gboolean result; GError *error = NULL; result = purple_queued_output_stream_push_bytes_finish(stream, res, &error); if (!result) { purple_queued_output_stream_clear_queue(stream); if (error->code != G_IO_ERROR_CANCELLED) { g_prefix_error(&error, "%s", _("Lost connection with server: ")); purple_connection_take_error(js->gc, error); } else { g_error_free(error); } } } static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len) { GBytes *output; gboolean success = TRUE; g_return_val_if_fail(len > 0, FALSE); if (js->state == JABBER_STREAM_CONNECTED) jabber_stream_restart_inactivity_timer(js); output = g_bytes_new(data, len); purple_queued_output_stream_push_bytes_async( js->output, output, G_PRIORITY_DEFAULT, js->cancellable, jabber_push_bytes_cb, js); g_bytes_unref(output); return success; } static void jabber_send_raw(G_GNUC_UNUSED PurpleProtocolServer *protocol_server, JabberStream *js, const char *data, gint len) { PurpleConnection *gc; PurpleAccount *account; gc = js->gc; account = purple_connection_get_account(gc); g_return_if_fail(data != NULL); /* because printing a tab to debug every minute gets old */ if (!purple_strequal(data, "\t")) { const char *username; char *text = NULL, *last_part = NULL, *tag_start = NULL; /* Because debug logs with plaintext passwords make me sad */ if (!purple_debug_is_unsafe() && js->state != JABBER_STREAM_CONNECTED && /* Either or ... */ (((tag_start = strstr(data, "") && (tag_start = strstr(tag_start, ""))))) { char *data_start, *tag_end = strchr(tag_start, '>'); text = g_strdup(data); /* Better to print out some wacky debugging than crash * due to a plugin sending bad xml */ if (tag_end == NULL) tag_end = tag_start; data_start = text + (tag_end - data) + 1; last_part = strchr(data_start, '<'); *data_start = '\0'; } username = purple_connection_get_display_name(gc); if(username == NULL) { PurpleContactInfo *info = PURPLE_CONTACT_INFO(account); username = purple_contact_info_get_username(info); } purple_debug_misc("jabber", "Sending%s (%s): %s%s%s\n", jabber_stream_is_ssl(js) ? " (ssl)" : "", username, text ? text : data, last_part ? "password removed" : "", last_part ? last_part : ""); g_free(text); } purple_signal_emit(purple_connection_get_protocol(gc), "jabber-sending-text", gc, &data); if (data == NULL) return; if (len == -1) len = strlen(data); if (js->bosh) jabber_bosh_connection_send(js->bosh, data); else do_jabber_send_raw(js, data, len); } static gint jabber_protocol_send_raw(G_GNUC_UNUSED PurpleProtocolServer *protocol_server, PurpleConnection *gc, const gchar *buf, gint len) { JabberStream *js = purple_connection_get_protocol_data(gc); g_return_val_if_fail(js != NULL, -1); /* TODO: It's probably worthwhile to restrict this to when the account * state is CONNECTED, but I can /almost/ envision reasons for wanting * to do things during the connection process. */ jabber_send_raw(NULL, js, buf, len); return (len < 0 ? (int)strlen(buf) : len); } static void jabber_send_signal_cb(PurpleConnection *pc, PurpleXmlNode **packet, G_GNUC_UNUSED gpointer unused) { JabberStream *js; char *txt; int len; if (NULL == packet) return; PURPLE_ASSERT_CONNECTION_IS_VALID(pc); js = purple_connection_get_protocol_data(pc); if (NULL == js) return; if (js->bosh) if (purple_strequal((*packet)->name, "message") || purple_strequal((*packet)->name, "iq") || purple_strequal((*packet)->name, "presence")) purple_xmlnode_set_namespace(*packet, NS_XMPP_CLIENT); txt = purple_xmlnode_to_str(*packet, &len); jabber_send_raw(NULL, js, txt, len); g_free(txt); } void jabber_send(JabberStream *js, PurpleXmlNode *packet) { purple_signal_emit(purple_connection_get_protocol(js->gc), "jabber-sending-xmlnode", js->gc, &packet); } static gboolean jabber_keepalive_timeout(PurpleConnection *gc) { JabberStream *js = purple_connection_get_protocol_data(gc); purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Ping timed out")); js->keepalive_timeout = 0; return FALSE; } static void jabber_keepalive(G_GNUC_UNUSED PurpleProtocolServer *protocol_server, PurpleConnection *gc) { JabberStream *js = purple_connection_get_protocol_data(gc); if (js->keepalive_timeout == 0) { jabber_keepalive_ping(js); js->keepalive_timeout = g_timeout_add_seconds(120, (GSourceFunc)(jabber_keepalive_timeout), gc); } } static int jabber_get_keepalive_interval(G_GNUC_UNUSED PurpleProtocolServer *protocol_server) { return PING_TIMEOUT; } static gboolean jabber_recv_cb(GObject *stream, gpointer data) { PurpleConnection *gc = data; JabberStream *js = purple_connection_get_protocol_data(gc); gssize len; gchar buf[4096]; GError *error = NULL; PURPLE_ASSERT_CONNECTION_IS_VALID(gc); do { len = g_pollable_input_stream_read_nonblocking( G_POLLABLE_INPUT_STREAM(stream), buf, sizeof(buf) - 1, js->cancellable, &error); if (len == 0) { purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Server closed the connection")); js->inpa = 0; return G_SOURCE_REMOVE; } else if (len < 0) { if (error->code == G_IO_ERROR_WOULD_BLOCK) { g_error_free(error); return G_SOURCE_CONTINUE; } else if (error->code == G_IO_ERROR_CANCELLED) { g_error_free(error); } else { g_prefix_error(&error, "%s", _("Lost connection with server: ")); purple_connection_take_error(js->gc, error); } js->inpa = 0; return G_SOURCE_REMOVE; } purple_connection_update_last_received(gc); buf[len] = '\0'; purple_debug_misc("jabber", "Recv (%" G_GSSIZE_FORMAT "): %s", len, buf); jabber_parser_process(js, buf, len); if(js->reinit) jabber_stream_init(js); } while (len > 0); return G_SOURCE_CONTINUE; } static void jabber_stream_connect_finish(JabberStream *js, GIOStream *stream) { GSource *source; js->stream = stream; js->input = g_object_ref(g_io_stream_get_input_stream(js->stream)); js->output = purple_queued_output_stream_new( g_io_stream_get_output_stream(js->stream)); if (js->state == JABBER_STREAM_CONNECTING) { jabber_send_raw(NULL, js, "", -1); } jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING); source = g_pollable_input_stream_create_source( G_POLLABLE_INPUT_STREAM(js->input), js->cancellable); g_source_set_callback(source, G_SOURCE_FUNC(jabber_recv_cb), js->gc, NULL); js->inpa = g_source_attach(source, NULL); g_source_unref(source); } static void jabber_login_callback(GObject *source_object, GAsyncResult *res, gpointer data) { GSocketClient *client = G_SOCKET_CLIENT(source_object); JabberStream *js = data; GSocketConnection *conn; GIOStream *stream; gboolean is_old_ssl = g_socket_client_get_tls(client); GError *error = NULL; conn = g_socket_client_connect_to_host_finish(client, res, &error); if (conn == NULL) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_error_free(error); return; } else if (is_old_ssl) { /* Old-style SSL only makes a direct connection, or fails. */ purple_connection_take_error(js->gc, error); return; } g_error_free(error); purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); return; } if (is_old_ssl) { stream = G_IO_STREAM(g_tcp_wrapper_connection_get_base_io_stream( G_TCP_WRAPPER_CONNECTION(conn))); } else { stream = G_IO_STREAM(conn); } jabber_stream_connect_finish(js, stream); if (is_old_ssl) { /* Tell the app that we're doing encryption */ jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); } } static void tls_handshake_cb(GObject *source_object, GAsyncResult *res, gpointer data) { JabberStream *js = data; GError *error = NULL; if (!g_tls_connection_handshake_finish(G_TLS_CONNECTION(source_object), res, &error)) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { /* Connection already closed/freed. Escape. */ } else if (g_error_matches(error, G_TLS_ERROR, G_TLS_ERROR_HANDSHAKE)) { /* In Gio, a handshake error is because of the cert */ purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR, _("SSL peer presented an invalid certificate")); } else { /* Report any other errors as handshake failing */ purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("SSL Handshake Failed")); } g_error_free(error); return; } jabber_stream_connect_finish(js, js->stream); /* Tell the app that we're doing encryption */ jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); } static void tls_init(JabberStream *js) { GSocketConnectable *identity; GIOStream *tls_conn; GError *error = NULL; g_clear_handle_id(&js->inpa, g_source_remove); js->input = NULL; g_filter_output_stream_set_close_base_stream( G_FILTER_OUTPUT_STREAM(js->output), FALSE); g_output_stream_close(G_OUTPUT_STREAM(js->output), js->cancellable, NULL); js->output = NULL; identity = g_network_address_new(js->certificate_CN, 0); tls_conn = g_tls_client_connection_new(js->stream, identity, &error); g_object_unref(identity); if (tls_conn == NULL) { purple_debug_warning("jabber", "Error creating TLS client connection: %s", error->message); g_clear_error(&error); purple_connection_error(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("SSL Connection Failed")); return; } g_clear_object(&js->stream); js->stream = G_IO_STREAM(tls_conn); g_tls_connection_handshake_async(G_TLS_CONNECTION(tls_conn), G_PRIORITY_DEFAULT, js->cancellable, tls_handshake_cb, js); } static void srv_resolved_cb(GObject *source_object, GAsyncResult *result, gpointer data) { GSocketClient *client = G_SOCKET_CLIENT(source_object); JabberStream *js = data; GSocketConnection *conn; GError *error = NULL; conn = g_socket_client_connect_to_service_finish(client, result, &error); if (error) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { /* Do nothing; cancelled. */ } else if (g_error_matches(error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND)) { /* If there was no response, then attempt fallback behaviour of XMPP * Core 3.2.2. */ purple_debug_warning( "jabber", "SRV lookup failed, proceeding with normal connection : %s", error->message); g_socket_client_connect_to_host_async( js->client, js->user->domain, purple_account_get_int( purple_connection_get_account(js->gc), "port", 5222), js->cancellable, jabber_login_callback, js); } else { /* If resolving failed or connecting failed, then just error out, as * in XMPP Core 3.2.1 step 8. */ purple_connection_g_error(js->gc, error); } g_error_free(error); return; } jabber_stream_connect_finish(js, G_IO_STREAM(conn)); } static JabberStream * jabber_stream_new(PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); PurpleContactInfo *info = PURPLE_CONTACT_INFO(account); GProxyResolver *resolver; GError *error = NULL; JabberStream *js; PurplePresence *presence; gchar *user; gchar *slash; resolver = purple_proxy_get_proxy_resolver(account, &error); if (resolver == NULL) { purple_debug_error("jabber", "Unable to get account proxy resolver: %s", error->message); g_error_free(error); return NULL; } js = g_new0(JabberStream, 1); purple_connection_set_protocol_data(gc, js); js->gc = gc; js->http_conns = soup_session_new_with_options("proxy-resolver", resolver, NULL); g_object_unref(resolver); /* we might want to expose this at some point */ js->cancellable = g_cancellable_new(); user = g_strdup(purple_contact_info_get_username(info)); /* jabber_id_new doesn't accept "user@domain/" as valid */ slash = strchr(user, '/'); if (slash && *(slash + 1) == '\0') *slash = '\0'; js->user = jabber_id_new(user); if (!js->user) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, _("Invalid XMPP ID")); g_free(user); /* Destroying the connection will free the JabberStream */ return NULL; } if (!js->user->node || *(js->user->node) == '\0') { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, _("Invalid XMPP ID. Username portion must be set.")); g_free(user); /* Destroying the connection will free the JabberStream */ return NULL; } if (!js->user->domain || *(js->user->domain) == '\0') { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, _("Invalid XMPP ID. Domain must be set.")); g_free(user); /* Destroying the connection will free the JabberStream */ return NULL; } js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_buddy_free); /* This is overridden during binding, but we need it here * in case the server only does legacy non-sasl auth!. */ purple_connection_set_display_name(gc, user); js->user_jb = jabber_buddy_find(js, user, TRUE); g_free(user); if (!js->user_jb) { /* This basically *can't* fail, but for good measure... */ purple_connection_error(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, _("Invalid XMPP ID")); /* Destroying the connection will free the JabberStream */ g_return_val_if_reached(NULL); } js->user_jb->subscription |= JABBER_SUB_BOTH; js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_iq_callbackdata_free); js->chats = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_chat_free); js->next_id = g_random_int(); js->keepalive_timeout = 0; js->max_inactivity = DEFAULT_INACTIVITY_TIME; /* Set the default protocol version to 1.0. Overridden in parser.c. */ js->protocol_version.major = 1; js->protocol_version.minor = 0; js->sessions = NULL; /* if we are idle, set idle-ness on the stream (this could happen if we get disconnected and the reconnects while being idle. I don't think it makes sense to do this when registering a new account... */ presence = purple_account_get_presence(account); if (purple_presence_is_idle(presence)) { GDateTime *idle = purple_presence_get_idle_time(presence); js->idle = 0; if(idle != NULL) { js->idle = g_date_time_to_unix(idle); } } return js; } static void jabber_stream_connect(JabberStream *js) { PurpleConnection *gc = js->gc; PurpleAccount *account = purple_connection_get_account(gc); const char *connect_server = purple_account_get_string(account, "connect_server", ""); const char *bosh_url = purple_account_get_string(account, "bosh_url", ""); GError *error = NULL; jabber_stream_set_state(js, JABBER_STREAM_CONNECTING); /* If both BOSH and a Connect Server are specified, we prefer BOSH. I'm not * attached to that choice, though. */ if (*bosh_url) { js->bosh = jabber_bosh_connection_new(js, bosh_url); if (!js->bosh) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, _("Malformed BOSH URL")); } return; } js->client = purple_gio_socket_client_new(account, &error); if (js->client == NULL) { purple_connection_take_error(gc, error); return; } js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain); /* if they've got old-ssl mode going, we probably want to ignore SRV lookups */ if (purple_strequal("old_ssl", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) { g_socket_client_set_tls(js->client, TRUE); g_socket_client_connect_to_host_async( js->client, js->certificate_CN, purple_account_get_int(account, "port", 5223), js->cancellable, jabber_login_callback, js); return; } /* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll * invoke the magic of SRV lookups, to figure out host and port */ if(connect_server[0]) { g_socket_client_connect_to_host_async( js->client, connect_server, purple_account_get_int(account, "port", 5222), js->cancellable, jabber_login_callback, js); } else { g_socket_client_connect_to_service_async(js->client, js->user->domain, "xmpp-client", js->cancellable, srv_resolved_cb, js); } } static void jabber_login(G_GNUC_UNUSED PurpleProtocol *protocol, PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); JabberStream *js; PurpleImage *image; purple_connection_set_flags(gc, PURPLE_CONNECTION_FLAG_HTML | PURPLE_CONNECTION_FLAG_NO_IMAGES); js = jabber_stream_new(account); if (js == NULL) return; /* replace old default proxies with the new default: NULL * TODO: these can eventually be removed */ if (purple_strequal("proxy.jabber.org", purple_account_get_string(account, "ft_proxies", "")) || purple_strequal("proxy.eu.jabber.org", purple_account_get_string(account, "ft_proxies", ""))) purple_account_set_string(account, "ft_proxies", NULL); /* * Calculate the avatar hash for our current image so we know (when we * fetch our vCard and PEP avatar) if we should send our avatar to the * server. */ image = purple_buddy_icons_find_account_icon(account); if (image != NULL) { js->initial_avatar_hash = g_compute_checksum_for_data( G_CHECKSUM_SHA1, purple_image_get_data(image), purple_image_get_data_size(image) ); g_object_unref(image); } jabber_stream_connect(js); } /* TODO: As Will pointed out in IRC, after being notified by the core to * shutdown, we should async. wait for the server to send us the stream * termination before destroying everything. That seems like it would require * changing the semantics of protocol's close(), so it's a good idea for 3.0.0. */ static void jabber_close(G_GNUC_UNUSED PurpleProtocol *protocol, PurpleConnection *gc) { JabberStream *js = purple_connection_get_protocol_data(gc); /* Close all of the open Jingle sessions on this stream */ jingle_terminate_sessions(js); if (js->bosh) { jabber_bosh_connection_destroy(js->bosh); js->bosh = NULL; } else if (js->output != NULL) { /* We should emit the stream termination message here * normally, but since we destroy the jabber stream just * after, it has no way to effectively go out on the * wire. Moreover, it causes a connection lost error in * the output queued stream that triggers an * heap-use-after-free error in jabber_push_bytes_cb(). * * This case happens when disabling the jabber account * from the dialog box. * * jabber_send_raw(js, "", -1); */ g_clear_handle_id(&js->inpa, g_source_remove); purple_gio_graceful_close(js->stream, js->input, G_OUTPUT_STREAM(js->output)); } g_clear_object(&js->output); g_clear_object(&js->input); g_clear_object(&js->stream); jabber_buddy_remove_all_pending_buddy_info_requests(js); jabber_parser_free(js); g_clear_pointer(&js->iq_callbacks, g_hash_table_destroy); g_clear_pointer(&js->buddies, g_hash_table_destroy); g_clear_pointer(&js->chats, g_hash_table_destroy); g_list_free_full(js->chat_servers, g_free); g_list_free_full(js->bs_proxies, (GDestroyNotify)jabber_bytestreams_streamhost_free); if (js->http_conns) { soup_session_abort(js->http_conns); g_object_unref(js->http_conns); } g_free(js->stream_id); g_clear_pointer(&js->user, jabber_id_free); g_free(js->initial_avatar_hash); g_free(js->avatar_hash); g_free(js->caps_hash); if (js->auth_mech && js->auth_mech->dispose) js->auth_mech->dispose(js); g_free(js->serverFQDN); g_list_free_full(js->commands, (GDestroyNotify)jabber_adhoc_commands_free); g_free(js->server_name); g_free(js->certificate_CN); g_free(js->old_msg); g_free(js->old_avatarhash); g_clear_handle_id(&js->keepalive_timeout, g_source_remove); g_clear_handle_id(&js->inactivity_timer, g_source_remove); g_clear_handle_id(&js->conn_close_timeout, g_source_remove); g_cancellable_cancel(js->cancellable); g_object_unref(G_OBJECT(js->cancellable)); g_free(js); purple_connection_set_protocol_data(gc, NULL); } void jabber_stream_set_state(JabberStream *js, JabberStreamState state) { js->state = state; if(state == JABBER_STREAM_INITIALIZING) { jabber_stream_init(js); } else if(state == JABBER_STREAM_CONNECTED) { /* Send initial presence */ jabber_presence_send(js, TRUE); /* Start up the inactivity timer */ jabber_stream_restart_inactivity_timer(js); purple_connection_set_state(js->gc, PURPLE_CONNECTION_STATE_CONNECTED); } } char *jabber_get_next_id(JabberStream *js) { return g_strdup_printf("purple%x", js->next_id++); } static void jabber_idle_set(G_GNUC_UNUSED PurpleProtocolServer *protocol_server, PurpleConnection *gc, gint idle) { JabberStream *js = purple_connection_get_protocol_data(gc); js->idle = idle ? time(NULL) - idle : idle; /* send out an updated prescence */ purple_debug_info("jabber", "sending updated presence for idle\n"); jabber_presence_send(js, FALSE); } void jabber_blocklist_parse_push(G_GNUC_UNUSED JabberStream *js, G_GNUC_UNUSED const char *from, G_GNUC_UNUSED JabberIqType type, G_GNUC_UNUSED const char *id, G_GNUC_UNUSED PurpleXmlNode *child) { #if 0 JabberIq *result; PurpleXmlNode *item; PurpleAccount *account; gboolean is_block; GSList *deny; if (!jabber_is_own_account(js, from)) { PurpleXmlNode *error, *x; result = jabber_iq_new(js, JABBER_IQ_ERROR); purple_xmlnode_set_attrib(result->node, "id", id); if (from) purple_xmlnode_set_attrib(result->node, "to", from); error = purple_xmlnode_new_child(result->node, "error"); purple_xmlnode_set_attrib(error, "type", "cancel"); x = purple_xmlnode_new_child(error, "not-allowed"); purple_xmlnode_set_namespace(x, NS_XMPP_STANZAS); jabber_iq_send(result); return; } account = purple_connection_get_account(js->gc); is_block = purple_strequal(child->name, "block"); item = purple_xmlnode_get_child(child, "item"); if (!is_block && item == NULL) { /* Unblock everyone */ purple_debug_info("jabber", "Received unblock push. Unblocking everyone.\n"); while ((deny = purple_account_privacy_get_denied(account)) != NULL) { purple_account_privacy_deny_remove(account, deny->data, TRUE); } } else if (item == NULL) { /* An empty is bogus */ PurpleXmlNode *error, *x; result = jabber_iq_new(js, JABBER_IQ_ERROR); purple_xmlnode_set_attrib(result->node, "id", id); error = purple_xmlnode_new_child(result->node, "error"); purple_xmlnode_set_attrib(error, "type", "modify"); x = purple_xmlnode_new_child(error, "bad-request"); purple_xmlnode_set_namespace(x, NS_XMPP_STANZAS); jabber_iq_send(result); return; } else { for ( ; item; item = purple_xmlnode_get_next_twin(item)) { const char *jid = purple_xmlnode_get_attrib(item, "jid"); if (jid == NULL || *jid == '\0') continue; if (is_block) purple_account_privacy_deny_add(account, jid, TRUE); else purple_account_privacy_deny_remove(account, jid, TRUE); } } result = jabber_iq_new(js, JABBER_IQ_RESULT); purple_xmlnode_set_attrib(result->node, "id", id); jabber_iq_send(result); #endif } #if 0 static void jabber_blocklist_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, PurpleXmlNode *packet, gpointer data) { PurpleXmlNode *blocklist, *item; PurpleAccount *account; GSList *deny; blocklist = purple_xmlnode_get_child_with_namespace(packet, "blocklist", NS_SIMPLE_BLOCKING); account = purple_connection_get_account(js->gc); if (type == JABBER_IQ_ERROR || blocklist == NULL) return; /* This is the only privacy method supported by XEP-0191 */ purple_account_set_privacy_type(account, PURPLE_ACCOUNT_PRIVACY_DENY_USERS); /* * TODO: When account->deny is something more than a hash table, this can * be re-written to find the set intersection and difference. */ while ((deny = purple_account_privacy_get_denied(account))) purple_account_privacy_deny_remove(account, deny->data, TRUE); item = purple_xmlnode_get_child(blocklist, "item"); while (item != NULL) { const char *jid = purple_xmlnode_get_attrib(item, "jid"); purple_account_privacy_deny_add(account, jid, TRUE); item = purple_xmlnode_get_next_twin(item); } } #endif void jabber_request_block_list(G_GNUC_UNUSED JabberStream *js) { #if 0 JabberIq *iq; PurpleXmlNode *blocklist; iq = jabber_iq_new(js, JABBER_IQ_GET); blocklist = purple_xmlnode_new_child(iq->node, "blocklist"); purple_xmlnode_set_namespace(blocklist, NS_SIMPLE_BLOCKING); jabber_iq_set_callback(iq, jabber_blocklist_parse, NULL); jabber_iq_send(iq); #endif } #if 0 static void jabber_add_deny(PurpleProtocolPrivacy *privacy, PurpleConnection *gc, const char *who) { JabberStream *js; JabberIq *iq; PurpleXmlNode *block, *item; g_return_if_fail(who != NULL && *who != '\0'); js = purple_connection_get_protocol_data(gc); if (js == NULL) return; if (!(js->server_caps & JABBER_CAP_BLOCKING)) { purple_notify_error(NULL, _("Server doesn't support blocking"), _("Server doesn't support blocking"), NULL, purple_request_cpar_from_connection(gc)); return; } iq = jabber_iq_new(js, JABBER_IQ_SET); block = purple_xmlnode_new_child(iq->node, "block"); purple_xmlnode_set_namespace(block, NS_SIMPLE_BLOCKING); item = purple_xmlnode_new_child(block, "item"); purple_xmlnode_set_attrib(item, "jid", who); jabber_iq_send(iq); } static void jabber_remove_deny(PurpleProtocolPrivacy *privacy, PurpleConnection *gc, const char *who) { JabberStream *js; JabberIq *iq; PurpleXmlNode *unblock, *item; g_return_if_fail(who != NULL && *who != '\0'); js = purple_connection_get_protocol_data(gc); if (js == NULL) return; if (!(js->server_caps & JABBER_CAP_BLOCKING)) return; iq = jabber_iq_new(js, JABBER_IQ_SET); unblock = purple_xmlnode_new_child(iq->node, "unblock"); purple_xmlnode_set_namespace(unblock, NS_SIMPLE_BLOCKING); item = purple_xmlnode_new_child(unblock, "item"); purple_xmlnode_set_attrib(item, "jid", who); jabber_iq_send(iq); } #endif void jabber_add_feature(const char *namespace, JabberFeatureEnabled cb) { JabberFeature *feat; g_return_if_fail(namespace != NULL); feat = g_new0(JabberFeature,1); feat->namespace = g_strdup(namespace); feat->is_enabled = cb; /* try to remove just in case it already exists in the list */ jabber_remove_feature(namespace); jabber_features = g_list_append(jabber_features, feat); } static void jabber_feature_free(JabberFeature *feature) { g_return_if_fail(feature != NULL); g_free(feature->namespace); g_free(feature); } static void jabber_remove_feature(const char *namespace) { GList *feature; for(feature = jabber_features; feature; feature = feature->next) { JabberFeature *feat = (JabberFeature*)feature->data; if(purple_strequal(feat->namespace, namespace)) { jabber_feature_free(feat); jabber_features = g_list_delete_link(jabber_features, feature); break; } } } gint jabber_identity_compare(gconstpointer a, gconstpointer b) { const JabberIdentity *ac; const JabberIdentity *bc; gint cat_cmp; gint typ_cmp; ac = a; bc = b; cat_cmp = g_strcmp0(ac->category, bc->category); if (cat_cmp != 0) { return cat_cmp; } typ_cmp = g_strcmp0(ac->type, bc->type); if (typ_cmp != 0) { return typ_cmp; } return g_strcmp0(ac->lang, bc->lang); } JabberIdentity *jabber_identity_new(const gchar *category, const gchar *type, const gchar *lang, const gchar *name) { JabberIdentity *id = g_new0(JabberIdentity, 1); id->category = g_strdup(category); id->type = g_strdup(type); id->lang = g_strdup(lang); id->name = g_strdup(name); return id; } void jabber_identity_free(JabberIdentity *id) { g_return_if_fail(id != NULL); g_free(id->category); g_free(id->type); g_free(id->lang); g_free(id->name); g_free(id); } /* * jabber_add_identity: * @category: The category of the identity. * @type: The type of the identity. * @language: (nullable): The language localization of the name. * @name: The name of the identity. * * Adds an identity to this jabber library instance. For list of valid values * visit the website of the XMPP Registrar * (http://xmpp.org/registrar/disco-categories.html#client) * * Like with jabber_add_feature, if you call this while accounts are connected, * Bad Things will happen. */ static void jabber_add_identity(const gchar *category, const gchar *type, const gchar *lang, const gchar *name) { GList *identity; JabberIdentity *ident; /* both required according to XEP-0030 */ g_return_if_fail(category != NULL); g_return_if_fail(type != NULL); ident = jabber_identity_new(category, type, lang, name); /* Check if this identity is already there... */ identity = g_list_find_custom(jabber_identities, ident, jabber_identity_compare); if (identity != NULL) { jabber_identity_free(ident); return; } jabber_identities = g_list_insert_sorted(jabber_identities, ident, jabber_identity_compare); } void jabber_bytestreams_streamhost_free(JabberBytestreamsStreamhost *sh) { g_return_if_fail(sh != NULL); g_free(sh->jid); g_free(sh->host); g_free(sh->zeroconf); g_free(sh); } gboolean jabber_stream_is_ssl(JabberStream *js) { return (js->bosh && jabber_bosh_connection_is_ssl(js->bosh)) || (!js->bosh && G_IS_TLS_CONNECTION(js->stream)); } static gboolean inactivity_cb(gpointer data) { JabberStream *js = data; /* We want whatever is sent to set this. It's okay because * the eventloop unsets it via the return FALSE. */ js->inactivity_timer = 0; if (js->bosh) { jabber_bosh_connection_send_keepalive(js->bosh); } else { jabber_send_raw(NULL, js, "\t", 1); } return FALSE; } void jabber_stream_restart_inactivity_timer(JabberStream *js) { g_clear_handle_id(&js->inactivity_timer, g_source_remove); g_return_if_fail(js->max_inactivity > 0); js->inactivity_timer = g_timeout_add_seconds(js->max_inactivity, inactivity_cb, js); } static const char * jabber_list_emblem(G_GNUC_UNUSED PurpleProtocolClient *client, PurpleBuddy *b) { JabberStream *js; JabberBuddy *jb = NULL; PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(b)); if(!gc) return NULL; js = purple_connection_get_protocol_data(gc); if(js) jb = jabber_buddy_find(js, purple_buddy_get_name(b), FALSE); if(!PURPLE_BUDDY_IS_ONLINE(b)) { if(jb && (jb->subscription & JABBER_SUB_PENDING || !(jb->subscription & JABBER_SUB_TO))) return "not-authorized"; } if (jb) { JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, NULL); if (jbr) { const gchar *client_type = jabber_resource_get_identity_category_type(jbr, "client"); if (client_type) { if (purple_strequal(client_type, "phone")) { return "mobile"; } else if (purple_strequal(client_type, "web")) { return "external"; } else if (purple_strequal(client_type, "handheld")) { return "hiptop"; } else if (purple_strequal(client_type, "bot")) { return "bot"; } /* the default value "pc" falls through and has no emblem */ } } } return NULL; } static GList * jabber_status_types(G_GNUC_UNUSED PurpleProtocol *protocol, G_GNUC_UNUSED PurpleAccount *account) { PurpleStatusType *type; GList *types = NULL; GValue *priority_value; GValue *buzz_enabled; priority_value = purple_value_new(G_TYPE_INT); g_value_set_int(priority_value, 1); buzz_enabled = purple_value_new(G_TYPE_BOOLEAN); g_value_set_boolean(buzz_enabled, TRUE); type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_ONLINE), NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(G_TYPE_STRING), "nick", _("Nickname"), purple_value_new(G_TYPE_STRING), "buzz", _("Allow Buzz"), buzz_enabled, NULL); types = g_list_prepend(types, type); priority_value = purple_value_new(G_TYPE_INT); g_value_set_int(priority_value, 1); buzz_enabled = purple_value_new(G_TYPE_BOOLEAN); g_value_set_boolean(buzz_enabled, TRUE); type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT), _("Chatty"), TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(G_TYPE_STRING), "nick", _("Nickname"), purple_value_new(G_TYPE_STRING), "buzz", _("Allow Buzz"), buzz_enabled, NULL); types = g_list_prepend(types, type); priority_value = purple_value_new(G_TYPE_INT); g_value_set_int(priority_value, 0); buzz_enabled = purple_value_new(G_TYPE_BOOLEAN); g_value_set_boolean(buzz_enabled, TRUE); type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY), NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(G_TYPE_STRING), "nick", _("Nickname"), purple_value_new(G_TYPE_STRING), "buzz", _("Allow Buzz"), buzz_enabled, NULL); types = g_list_prepend(types, type); priority_value = purple_value_new(G_TYPE_INT); g_value_set_int(priority_value, 0); buzz_enabled = purple_value_new(G_TYPE_BOOLEAN); g_value_set_boolean(buzz_enabled, TRUE); type = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA), NULL, TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(G_TYPE_STRING), "nick", _("Nickname"), purple_value_new(G_TYPE_STRING), "buzz", _("Allow Buzz"), buzz_enabled, NULL); types = g_list_prepend(types, type); priority_value = purple_value_new(G_TYPE_INT); g_value_set_int(priority_value, 0); type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND), _("Do Not Disturb"), TRUE, TRUE, FALSE, "priority", _("Priority"), priority_value, "message", _("Message"), purple_value_new(G_TYPE_STRING), "nick", _("Nickname"), purple_value_new(G_TYPE_STRING), NULL); types = g_list_prepend(types, type); /* if(js->protocol_version == JABBER_PROTO_0_9) "Invisible" */ type = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE), NULL, TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(G_TYPE_STRING), NULL); types = g_list_prepend(types, type); return g_list_reverse(types); } static void jabber_password_change_result_cb(JabberStream *js, G_GNUC_UNUSED const char *from, JabberIqType type, G_GNUC_UNUSED const char *id, PurpleXmlNode *packet, gpointer data) { if (type == JABBER_IQ_RESULT) { PurpleAccount *account = purple_connection_get_account(js->gc); PurpleCredentialManager *manager = NULL; purple_notify_info(js->gc, _("Password Changed"), _("Password " "Changed"), _("Your password has been changed."), purple_request_cpar_from_connection(js->gc)); manager = purple_credential_manager_get_default(); purple_credential_manager_write_password_async(manager, account, (const gchar *)data, NULL, NULL, NULL); } else { char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error changing password"), _("Error changing password"), msg, purple_request_cpar_from_connection(js->gc)); g_free(msg); } g_free(data); } static void jabber_password_change_cb(JabberStream *js, PurpleRequestPage *page) { const char *p1, *p2; JabberIq *iq; PurpleXmlNode *query, *y; p1 = purple_request_page_get_string(page, "password1"); p2 = purple_request_page_get_string(page, "password2"); if(!purple_strequal(p1, p2)) { purple_notify_error(js->gc, NULL, _("New passwords do not match."), NULL, purple_request_cpar_from_connection(js->gc)); return; } iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register"); purple_xmlnode_set_attrib(iq->node, "to", js->user->domain); query = purple_xmlnode_get_child(iq->node, "query"); y = purple_xmlnode_new_child(query, "username"); purple_xmlnode_insert_data(y, js->user->node, -1); y = purple_xmlnode_new_child(query, "password"); purple_xmlnode_insert_data(y, p1, -1); jabber_iq_set_callback(iq, jabber_password_change_result_cb, g_strdup(p1)); jabber_iq_send(iq); } static void jabber_password_change(G_GNUC_UNUSED GSimpleAction *action, GVariant *parameter, G_GNUC_UNUSED gpointer data) { const char *account_id = NULL; PurpleAccountManager *manager = NULL; PurpleAccount *account = NULL; PurpleConnection *connection = NULL; JabberStream *js = NULL; PurpleRequestPage *page; PurpleRequestGroup *group; PurpleRequestField *field; if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { g_critical("XMPP Change Password action parameter is of incorrect type %s", g_variant_get_type_string(parameter)); } account_id = g_variant_get_string(parameter, NULL); manager = purple_account_manager_get_default(); account = purple_account_manager_find_by_id(manager, account_id); connection = purple_account_get_connection(account); js = purple_connection_get_protocol_data(connection); page = purple_request_page_new(); group = purple_request_group_new(NULL); purple_request_page_add_group(page, group); field = purple_request_field_string_new("password1", _("Password"), "", FALSE); purple_request_field_string_set_masked(PURPLE_REQUEST_FIELD_STRING(field), TRUE); purple_request_field_set_required(field, TRUE); purple_request_group_add_field(group, field); field = purple_request_field_string_new("password2", _("Password (again)"), "", FALSE); purple_request_field_string_set_masked(PURPLE_REQUEST_FIELD_STRING(field), TRUE); purple_request_field_set_required(field, TRUE); purple_request_group_add_field(group, field); purple_request_fields(connection, _("Change XMPP Password"), _("Change XMPP Password"), _("Please enter your new password"), page, _("OK"), G_CALLBACK(jabber_password_change_cb), _("Cancel"), NULL, purple_request_cpar_from_connection(connection), js); } static const gchar * xmpp_protocol_actions_get_prefix(G_GNUC_UNUSED PurpleProtocolActions *actions) { return "prpl-xmpp"; } static GActionGroup * xmpp_protocol_actions_get_action_group(G_GNUC_UNUSED PurpleProtocolActions *actions, PurpleConnection *connection) { JabberStream *js = purple_connection_get_protocol_data(connection); GSimpleActionGroup *group = NULL; GActionEntry entries[] = { { .name = "set-user-info", .activate = jabber_setup_set_info, .parameter_type = "s", }, { .name = "change-password", .activate = jabber_password_change, .parameter_type = "s", }, }; gsize nentries = G_N_ELEMENTS(entries); group = g_simple_action_group_new(); g_action_map_add_action_entries(G_ACTION_MAP(group), entries, nentries, NULL); if(js->pep) { jabber_pep_add_action_entries(group); } #if 0 if(js->commands) { jabber_adhoc_add_server_action_entries(js, group); } #endif return G_ACTION_GROUP(group); } static GMenu * xmpp_protocol_actions_get_menu(G_GNUC_UNUSED PurpleProtocolActions *actions, G_GNUC_UNUSED PurpleConnection *connection) { GMenu *menu = NULL; GMenuItem *item = NULL; menu = g_menu_new(); item = g_menu_item_new(_("Set User Info..."), "prpl-xmpp.set-user-info"); g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", "account"); g_menu_append_item(menu, item); g_object_unref(item); item = g_menu_item_new(_("Change Password..."), "prpl-xmpp.change-password"); g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", "account"); g_menu_append_item(menu, item); g_object_unref(item); jabber_pep_append_menu(menu); #if 0 if(js->commands) { jabber_adhoc_append_server_menu(js, menu); } #endif return menu; } static PurpleChat * jabber_find_blist_chat(G_GNUC_UNUSED PurpleProtocolClient *client, PurpleAccount *account, const char *name) { PurpleBlistNode *gnode, *cnode; JabberID *jid; if(!(jid = jabber_id_new(name))) return NULL; for (gnode = purple_blist_get_default_root(); gnode; gnode = purple_blist_node_get_sibling_next(gnode)) { for(cnode = purple_blist_node_get_first_child(gnode); cnode; cnode = purple_blist_node_get_sibling_next(cnode)) { PurpleChat *chat = (PurpleChat*)cnode; const char *room, *server; GHashTable *components; if(!PURPLE_IS_CHAT(cnode)) continue; if (purple_chat_get_account(chat) != account) continue; components = purple_chat_get_components(chat); if(!(room = g_hash_table_lookup(components, "room"))) continue; if(!(server = g_hash_table_lookup(components, "server"))) continue; /* FIXME: Collate is wrong in a few cases here; this should be prepped */ if(jid->node && jid->domain && !g_utf8_collate(room, jid->node) && !g_utf8_collate(server, jid->domain)) { jabber_id_free(jid); return chat; } } } jabber_id_free(jid); return NULL; } static void jabber_convo_closed(G_GNUC_UNUSED PurpleProtocolClient *client, PurpleConnection *gc, const char *who) { JabberStream *js = purple_connection_get_protocol_data(gc); JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr; if(!(jid = jabber_id_new(who))) return; if((jb = jabber_buddy_find(js, who, TRUE)) && (jbr = jabber_buddy_find_resource(jb, jid->resource))) { g_free(jbr->thread_id); jbr->thread_id = NULL; } jabber_id_free(jid); } static const gchar * jabber_client_normalize(G_GNUC_UNUSED PurpleProtocolClient *client, PurpleAccount *account, const char *who) { return jabber_normalize(account, who); } char *jabber_parse_error(JabberStream *js, PurpleXmlNode *packet, PurpleConnectionError *reason) { PurpleXmlNode *error; const char *code = NULL, *text = NULL; const char *xmlns = purple_xmlnode_get_namespace(packet); char *cdata = NULL; #define SET_REASON(x) \ if(reason != NULL) { *reason = x; } if((error = purple_xmlnode_get_child(packet, "error"))) { PurpleXmlNode *t = purple_xmlnode_get_child_with_namespace(error, "text", NS_XMPP_STANZAS); if (t) cdata = purple_xmlnode_get_data(t); code = purple_xmlnode_get_attrib(error, "code"); /* Stanza errors */ if(purple_xmlnode_get_child(error, "bad-request")) { text = _("Bad Request"); } else if(purple_xmlnode_get_child(error, "conflict")) { SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE); text = _("Conflict"); } else if(purple_xmlnode_get_child(error, "feature-not-implemented")) { text = _("Feature Not Implemented"); } else if(purple_xmlnode_get_child(error, "forbidden")) { text = _("Forbidden"); } else if(purple_xmlnode_get_child(error, "gone")) { text = _("Gone"); } else if(purple_xmlnode_get_child(error, "internal-server-error")) { text = _("Internal Server Error"); } else if(purple_xmlnode_get_child(error, "item-not-found")) { text = _("Item Not Found"); } else if(purple_xmlnode_get_child(error, "jid-malformed")) { text = _("Malformed XMPP ID"); } else if(purple_xmlnode_get_child(error, "not-acceptable")) { text = _("Not Acceptable"); } else if(purple_xmlnode_get_child(error, "not-allowed")) { text = _("Not Allowed"); } else if(purple_xmlnode_get_child(error, "not-authorized")) { text = _("Not Authorized"); } else if(purple_xmlnode_get_child(error, "payment-required")) { text = _("Payment Required"); } else if(purple_xmlnode_get_child(error, "recipient-unavailable")) { text = _("Recipient Unavailable"); } else if(purple_xmlnode_get_child(error, "redirect")) { /* XXX */ } else if(purple_xmlnode_get_child(error, "registration-required")) { text = _("Registration Required"); } else if(purple_xmlnode_get_child(error, "remote-server-not-found")) { text = _("Remote Server Not Found"); } else if(purple_xmlnode_get_child(error, "remote-server-timeout")) { text = _("Remote Server Timeout"); } else if(purple_xmlnode_get_child(error, "resource-constraint")) { text = _("Server Overloaded"); } else if(purple_xmlnode_get_child(error, "service-unavailable")) { text = _("Service Unavailable"); } else if(purple_xmlnode_get_child(error, "subscription-required")) { text = _("Subscription Required"); } else if(purple_xmlnode_get_child(error, "unexpected-request")) { text = _("Unexpected Request"); } else if(purple_xmlnode_get_child(error, "undefined-condition")) { text = _("Unknown Error"); } } else if(purple_strequal(xmlns, NS_XMPP_SASL)) { /* Most common reason can be the default */ SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR); if(purple_xmlnode_get_child(packet, "aborted")) { text = _("Authorization Aborted"); } else if(purple_xmlnode_get_child(packet, "incorrect-encoding")) { text = _("Incorrect encoding in authorization"); } else if(purple_xmlnode_get_child(packet, "invalid-authzid")) { text = _("Invalid authzid"); } else if(purple_xmlnode_get_child(packet, "invalid-mechanism")) { text = _("Invalid Authorization Mechanism"); } else if(purple_xmlnode_get_child(packet, "mechanism-too-weak")) { SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE); text = _("Authorization mechanism too weak"); } else if(purple_xmlnode_get_child(packet, "not-authorized")) { SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED); /* Clear the password if it isn't being saved */ if (!purple_account_get_remember_password(purple_connection_get_account(js->gc))) { PurpleAccount *account = purple_connection_get_account(js->gc); PurpleCredentialManager *manager = NULL; manager = purple_credential_manager_get_default(); purple_credential_manager_clear_password_async(manager, account, NULL, NULL, NULL); } text = _("Not Authorized"); } else if(purple_xmlnode_get_child(packet, "temporary-auth-failure")) { text = _("Temporary Authentication Failure"); } else { SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED); text = _("Authentication Failure"); } } else if(purple_strequal(packet->name, "stream:error") || (purple_strequal(packet->name, "error") && purple_strequal(xmlns, NS_XMPP_STREAMS))) { /* Most common reason as default: */ SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR); if(purple_xmlnode_get_child(packet, "bad-format")) { text = _("Bad Format"); } else if(purple_xmlnode_get_child(packet, "bad-namespace-prefix")) { text = _("Bad Namespace Prefix"); } else if(purple_xmlnode_get_child(packet, "conflict")) { SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE); text = _("Resource Conflict"); } else if(purple_xmlnode_get_child(packet, "connection-timeout")) { text = _("Connection Timeout"); } else if(purple_xmlnode_get_child(packet, "host-gone")) { text = _("Host Gone"); } else if(purple_xmlnode_get_child(packet, "host-unknown")) { text = _("Host Unknown"); } else if(purple_xmlnode_get_child(packet, "improper-addressing")) { text = _("Improper Addressing"); } else if(purple_xmlnode_get_child(packet, "internal-server-error")) { text = _("Internal Server Error"); } else if(purple_xmlnode_get_child(packet, "invalid-id")) { text = _("Invalid ID"); } else if(purple_xmlnode_get_child(packet, "invalid-namespace")) { text = _("Invalid Namespace"); } else if(purple_xmlnode_get_child(packet, "invalid-xml")) { text = _("Invalid XML"); } else if(purple_xmlnode_get_child(packet, "nonmatching-hosts")) { text = _("Non-matching Hosts"); } else if(purple_xmlnode_get_child(packet, "not-authorized")) { text = _("Not Authorized"); } else if(purple_xmlnode_get_child(packet, "policy-violation")) { text = _("Policy Violation"); } else if(purple_xmlnode_get_child(packet, "remote-connection-failed")) { text = _("Remote Connection Failed"); } else if(purple_xmlnode_get_child(packet, "resource-constraint")) { text = _("Resource Constraint"); } else if(purple_xmlnode_get_child(packet, "restricted-xml")) { text = _("Restricted XML"); } else if(purple_xmlnode_get_child(packet, "see-other-host")) { text = _("See Other Host"); } else if(purple_xmlnode_get_child(packet, "system-shutdown")) { text = _("System Shutdown"); } else if(purple_xmlnode_get_child(packet, "undefined-condition")) { text = _("Undefined Condition"); } else if(purple_xmlnode_get_child(packet, "unsupported-encoding")) { text = _("Unsupported Encoding"); } else if(purple_xmlnode_get_child(packet, "unsupported-stanza-type")) { text = _("Unsupported Stanza Type"); } else if(purple_xmlnode_get_child(packet, "unsupported-version")) { text = _("Unsupported Version"); } else if(purple_xmlnode_get_child(packet, "xml-not-well-formed")) { text = _("XML Not Well Formed"); } else { text = _("Stream Error"); } } #undef SET_REASON if(text || cdata) { char *ret = g_strdup_printf("%s%s%s", code ? code : "", code ? ": " : "", text ? text : cdata); g_free(cdata); return ret; } else { return NULL; } } static PurpleCmdRet jabber_cmd_chat_config(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, G_GNUC_UNUSED char **args, G_GNUC_UNUSED char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if (!chat) return PURPLE_CMD_RET_FAILED; jabber_chat_request_room_configure(chat); return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_register(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, G_GNUC_UNUSED char **args, G_GNUC_UNUSED char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if (!chat) return PURPLE_CMD_RET_FAILED; jabber_chat_register(chat); return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_topic(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, G_GNUC_UNUSED char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if (!chat) return PURPLE_CMD_RET_FAILED; if (args && args[0] && *args[0]) jabber_chat_change_topic(chat, args[0]); else { const char *cur = purple_chat_conversation_get_topic(PURPLE_CHAT_CONVERSATION(conv)); char *buf, *tmp, *tmp2; if (cur) { tmp = g_markup_escape_text(cur, -1); tmp2 = purple_markup_linkify(tmp); buf = g_strdup_printf(_("current topic is: %s"), tmp2); g_free(tmp); g_free(tmp2); } else buf = g_strdup(_("No topic is set")); purple_conversation_write_system_message(conv, buf, PURPLE_MESSAGE_NO_LOG); g_free(buf); } return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_nick(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if(!chat || !args || !args[0]) return PURPLE_CMD_RET_FAILED; if (!jabber_resourceprep_validate(args[0])) { *error = g_strdup(_("Invalid nickname")); return PURPLE_CMD_RET_FAILED; } if (jabber_chat_change_nick(chat, args[0])) return PURPLE_CMD_RET_OK; else return PURPLE_CMD_RET_FAILED; } static PurpleCmdRet jabber_cmd_chat_part(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, G_GNUC_UNUSED char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if (!chat) return PURPLE_CMD_RET_FAILED; jabber_chat_part(chat, args ? args[0] : NULL); return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_ban(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if(!chat || !args || !args[0]) return PURPLE_CMD_RET_FAILED; if(!jabber_chat_ban_user(chat, args[0], args[1])) { *error = g_strdup_printf(_("Unable to ban user %s"), args[0]); return PURPLE_CMD_RET_FAILED; } return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_affiliate(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if (!chat || !args || !args[0]) return PURPLE_CMD_RET_FAILED; if (!purple_strequal(args[0], "owner") && !purple_strequal(args[0], "admin") && !purple_strequal(args[0], "member") && !purple_strequal(args[0], "outcast") && !purple_strequal(args[0], "none")) { *error = g_strdup_printf(_("Unknown affiliation: \"%s\""), args[0]); return PURPLE_CMD_RET_FAILED; } if (args[1]) { int i; char **nicks = g_strsplit(args[1], " ", -1); for (i = 0; nicks[i]; ++i) if (!jabber_chat_affiliate_user(chat, nicks[i], args[0])) { *error = g_strdup_printf(_("Unable to affiliate user %s as \"%s\""), nicks[i], args[0]); g_strfreev(nicks); return PURPLE_CMD_RET_FAILED; } g_strfreev(nicks); } else { jabber_chat_affiliation_list(chat, args[0]); } return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_role(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if (!chat || !args || !args[0]) return PURPLE_CMD_RET_FAILED; if (!purple_strequal(args[0], "moderator") && !purple_strequal(args[0], "participant") && !purple_strequal(args[0], "visitor") && !purple_strequal(args[0], "none")) { *error = g_strdup_printf(_("Unknown role: \"%s\""), args[0]); return PURPLE_CMD_RET_FAILED; } if (args[1]) { int i; char **nicks = g_strsplit(args[1], " ", -1); for (i = 0; nicks[i]; i++) if (!jabber_chat_role_user(chat, nicks[i], args[0], NULL)) { *error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"), args[0], nicks[i]); g_strfreev(nicks); return PURPLE_CMD_RET_FAILED; } g_strfreev(nicks); } else { jabber_chat_role_list(chat, args[0]); } return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_invite(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, G_GNUC_UNUSED char **error, G_GNUC_UNUSED gpointer data) { if(!args || !args[0]) return PURPLE_CMD_RET_FAILED; jabber_chat_invite(purple_conversation_get_connection(conv), purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv)), args[1] ? args[1] : "", args[0]); return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_join(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, char **error, G_GNUC_UNUSED gpointer data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); GHashTable *components; JabberID *jid = NULL; const char *room = NULL, *server = NULL, *handle = NULL; if (!chat || !args || !args[0]) return PURPLE_CMD_RET_FAILED; components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); if (strchr(args[0], '@')) jid = jabber_id_new(args[0]); if (jid) { room = jid->node; server = jid->domain; handle = jid->resource ? jid->resource : chat->handle; } else { /* If jabber_id_new failed, the user may have just passed in * a room name. For backward compatibility, handle that here. */ if (strchr(args[0], '@')) { *error = g_strdup(_("Invalid XMPP ID")); return PURPLE_CMD_RET_FAILED; } room = args[0]; server = chat->server; handle = chat->handle; } g_hash_table_insert(components, "room", (gpointer)room); g_hash_table_insert(components, "server", (gpointer)server); g_hash_table_insert(components, "handle", (gpointer)handle); if (args[1]) g_hash_table_insert(components, "password", args[1]); jabber_chat_join(purple_conversation_get_connection(conv), components); g_hash_table_destroy(components); jabber_id_free(jid); return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_kick(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, char **error, G_GNUC_UNUSED void *data) { JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); if(!chat || !args || !args[0]) return PURPLE_CMD_RET_FAILED; if(!jabber_chat_role_user(chat, args[0], "none", args[1])) { *error = g_strdup_printf(_("Unable to kick user %s"), args[0]); return PURPLE_CMD_RET_FAILED; } return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_chat_msg(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, G_GNUC_UNUSED char **error, G_GNUC_UNUSED void *data) { PurpleAccount *account = NULL; PurpleConnection *pc = NULL; PurpleProtocol *prpl = NULL; PurpleMessage *msg = NULL; JabberChat *chat = jabber_chat_find_by_conv(PURPLE_CHAT_CONVERSATION(conv)); char *who; const gchar *me = NULL; if (!chat) return PURPLE_CMD_RET_FAILED; account = purple_connection_get_account(pc); me = purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(account)); who = g_strdup_printf("%s@%s/%s", chat->room, chat->server, args[0]); pc = purple_conversation_get_connection(conv); prpl = purple_connection_get_protocol(pc); msg = purple_message_new_outgoing(account, me, who, args[1], 0); jabber_message_send_im(PURPLE_PROTOCOL_IM(prpl), pc, msg); g_free(who); return PURPLE_CMD_RET_OK; } static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv, G_GNUC_UNUSED const char *cmd, char **args, char **error, G_GNUC_UNUSED void *data) { PurpleAccount *account; PurpleConnection *pc; if(!args || !args[0]) return PURPLE_CMD_RET_FAILED; account = purple_conversation_get_account(conv); pc = purple_account_get_connection(account); if(!jabber_ping_jid(purple_connection_get_protocol_data(pc), args[0])) { *error = g_strdup_printf(_("Unable to ping user %s"), args[0]); return PURPLE_CMD_RET_FAILED; } return PURPLE_CMD_RET_OK; } static gboolean jabber_offline_message(G_GNUC_UNUSED PurpleProtocolClient *client, G_GNUC_UNUSED PurpleBuddy *buddy) { return TRUE; } static gboolean jabber_audio_enabled(G_GNUC_UNUSED JabberStream *js, G_GNUC_UNUSED const char *namespace) { PurpleMediaManager *manager = purple_media_manager_get(); PurpleMediaCaps caps = purple_media_manager_get_ui_caps(manager); return (caps & (PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION)); } static gboolean jabber_video_enabled(G_GNUC_UNUSED JabberStream *js, G_GNUC_UNUSED const char *namespace) { PurpleMediaManager *manager = purple_media_manager_get(); PurpleMediaCaps caps = purple_media_manager_get_ui_caps(manager); return (caps & (PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION)); } typedef struct { PurpleProtocolMedia *media; PurpleAccount *account; gchar *who; PurpleMediaSessionType type; } JabberMediaRequest; static void jabber_media_cancel_cb(JabberMediaRequest *request, G_GNUC_UNUSED PurpleRequestPage *page) { g_free(request->who); g_free(request); } static void jabber_media_ok_cb(JabberMediaRequest *request, PurpleRequestPage *page) { const gchar *selected = purple_request_page_get_choice(page, "resource"); gchar *who = g_strdup_printf("%s/%s", request->who, selected); jabber_initiate_media(request->media, request->account, who, request->type); g_free(who); g_free(request->who); g_free(request); } static gboolean jabber_initiate_media(PurpleProtocolMedia *media, PurpleAccount *account, const gchar *who, PurpleMediaSessionType type) { PurpleConnection *gc = purple_account_get_connection(account); JabberStream *js = purple_connection_get_protocol_data(gc); JabberBuddy *jb; JabberBuddyResource *jbr = NULL; char *resource = NULL; if (!js) { purple_debug_error("jabber", "jabber_initiate_media: NULL stream\n"); return FALSE; } jb = jabber_buddy_find(js, who, FALSE); if(!jb || !jb->resources || (((resource = jabber_get_resource(who)) != NULL) && (jbr = jabber_buddy_find_resource(jb, resource)) == NULL)) { /* no resources online, we're trying to initiate with someone * whose presence we're not subscribed to, or * someone who is offline. Let's inform the user */ char *msg; if(!jb) { msg = g_strdup_printf(_("Unable to initiate media with %s: invalid JID"), who); } else if(jb->subscription & JABBER_SUB_TO && !jb->resources) { msg = g_strdup_printf(_("Unable to initiate media with %s: user is not online"), who); } else if(resource) { msg = g_strdup_printf(_("Unable to initiate media with %s: resource is not online"), who); } else { msg = g_strdup_printf(_("Unable to initiate media with %s: not subscribed to user presence"), who); } purple_notify_error(account, _("Media Initiation Failed"), _("Media Initiation Failed"), msg, purple_request_cpar_from_connection(gc)); g_free(msg); g_free(resource); return FALSE; } else if(jbr != NULL) { /* they've specified a resource, no need to ask or * default or anything, just do it */ g_free(resource); return jingle_rtp_initiate_media(js, who, type); } else if(!jb->resources->next) { /* only 1 resource online (probably our most common case) * so no need to ask who to initiate with */ gchar *name; gboolean result; jbr = jb->resources->data; name = g_strdup_printf("%s/%s", who, jbr->name); result = jabber_initiate_media(media, account, name, type); g_free(name); return result; } else { /* we've got multiple resources, * we need to pick one to initiate with */ GList *l; char *msg; PurpleRequestPage *page = NULL; PurpleRequestField *field = NULL; PurpleRequestFieldChoice *choice = NULL; PurpleRequestGroup *group = NULL; JabberMediaRequest *request; field = purple_request_field_choice_new("resource", _("Resource"), 0); choice = PURPLE_REQUEST_FIELD_CHOICE(field); for(l = jb->resources; l; l = l->next) { JabberBuddyResource *ljbr = l->data; PurpleMediaCaps caps; gchar *name; name = g_strdup_printf("%s/%s", who, ljbr->name); caps = jabber_get_media_caps(media, account, name); g_free(name); if ((type & PURPLE_MEDIA_AUDIO) && (type & PURPLE_MEDIA_VIDEO)) { if (caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO) { jbr = ljbr; purple_request_field_choice_add_full(choice, jbr->name, g_strdup(jbr->name), g_free); } } else if (type & (PURPLE_MEDIA_AUDIO) && (caps & PURPLE_MEDIA_CAPS_AUDIO)) { jbr = ljbr; purple_request_field_choice_add_full(choice, jbr->name, g_strdup(jbr->name), g_free); }else if (type & (PURPLE_MEDIA_VIDEO) && (caps & PURPLE_MEDIA_CAPS_VIDEO)) { jbr = ljbr; purple_request_field_choice_add_full(choice, jbr->name, g_strdup(jbr->name), g_free); } } if (jbr == NULL) { purple_debug_error("jabber", "No resources available\n"); return FALSE; } if(g_list_length(purple_request_field_choice_get_elements(choice)) <= 1) { gchar *name; gboolean result; g_object_unref(field); name = g_strdup_printf("%s/%s", who, jbr->name); result = jabber_initiate_media(media, account, name, type); g_free(name); return result; } msg = g_strdup_printf(_("Please select the resource of %s with which you would like to start a media session."), who); page = purple_request_page_new(); group = purple_request_group_new(NULL); request = g_new0(JabberMediaRequest, 1); request->media = media; request->account = account; request->who = g_strdup(who); request->type = type; purple_request_group_add_field(group, field); purple_request_page_add_group(page, group); purple_request_fields(account, _("Select a Resource"), msg, NULL, page, _("Initiate Media"), G_CALLBACK(jabber_media_ok_cb), _("Cancel"), G_CALLBACK(jabber_media_cancel_cb), purple_request_cpar_from_account(account), request); g_free(msg); return TRUE; } return FALSE; } static PurpleMediaCaps jabber_get_media_caps(G_GNUC_UNUSED PurpleProtocolMedia *media, PurpleAccount *account, const char *who) { PurpleConnection *gc = purple_account_get_connection(account); JabberStream *js = purple_connection_get_protocol_data(gc); JabberBuddy *jb; JabberBuddyResource *jbr; PurpleMediaCaps total = PURPLE_MEDIA_CAPS_NONE; gchar *resource; GList *specific = NULL, *l; if (!js) { purple_debug_info("jabber", "jabber_can_do_media: NULL stream\n"); return FALSE; } jb = jabber_buddy_find(js, who, FALSE); if (!jb || !jb->resources) { /* no resources online, we're trying to get caps for someone * whose presence we're not subscribed to, or * someone who is offline. */ return total; } else if ((resource = jabber_get_resource(who)) != NULL) { /* they've specified a resource, no need to ask or * default or anything, just do it */ jbr = jabber_buddy_find_resource(jb, resource); g_free(resource); if (!jbr) { purple_debug_error("jabber", "jabber_get_media_caps:" " Can't find resource %s\n", who); return total; } l = specific = g_list_prepend(specific, jbr); } else { /* we've got multiple resources, combine their caps */ l = jb->resources; } for (; l; l = l->next) { PurpleMediaCaps caps = PURPLE_MEDIA_CAPS_NONE; jbr = l->data; if (jabber_resource_has_capability(jbr, JINGLE_APP_RTP_SUPPORT_AUDIO)) caps |= PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION | PURPLE_MEDIA_CAPS_AUDIO; if (jabber_resource_has_capability(jbr, JINGLE_APP_RTP_SUPPORT_VIDEO)) caps |= PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION | PURPLE_MEDIA_CAPS_VIDEO; if (caps & PURPLE_MEDIA_CAPS_AUDIO && caps & PURPLE_MEDIA_CAPS_VIDEO) caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO; if (caps != PURPLE_MEDIA_CAPS_NONE) { if (!jabber_resource_has_capability(jbr, JINGLE_TRANSPORT_ICEUDP) && !jabber_resource_has_capability(jbr, JINGLE_TRANSPORT_RAWUDP)) { purple_debug_info("jingle-rtp", "Buddy doesn't " "support the same transport types\n"); caps = PURPLE_MEDIA_CAPS_NONE; } else caps |= PURPLE_MEDIA_CAPS_MODIFY_SESSION | PURPLE_MEDIA_CAPS_CHANGE_DIRECTION; } total |= caps; } g_clear_list(&specific, NULL); return total; } static gboolean jabber_can_receive_file(G_GNUC_UNUSED PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who) { JabberStream *js = purple_connection_get_protocol_data(gc); if (js) { JabberBuddy *jb = jabber_buddy_find(js, who, FALSE); GList *iter; gboolean has_resources_without_caps = FALSE; /* if we didn't find a JabberBuddy, we don't have presence for this buddy, let's assume they can receive files, disco should tell us when actually trying */ if (jb == NULL) return TRUE; /* find out if there is any resources without caps */ for (iter = jb->resources; iter ; iter = g_list_next(iter)) { JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data; if (!jabber_resource_know_capabilities(jbr)) { has_resources_without_caps = TRUE; } } if (has_resources_without_caps) { /* there is at least one resource which we don't have caps for, let's assume they can receive files... */ return TRUE; } else { /* we have caps for all the resources, see if at least one has right caps */ for (iter = jb->resources; iter ; iter = g_list_next(iter)) { JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data; if (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER) && (jabber_resource_has_capability(jbr, NS_BYTESTREAMS) || jabber_resource_has_capability(jbr, NS_IBB))) { return TRUE; } } return FALSE; } } else { return TRUE; } } static void jabber_register_commands(PurpleProtocol *protocol) { GSList *commands = NULL; PurpleCmdId id; const gchar *proto_id = purple_protocol_get_id(protocol); id = purple_cmd_register("config", "", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id, jabber_cmd_chat_config, _("config: Configure a chat room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("configure", "", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id, jabber_cmd_chat_config, _("configure: Configure a chat room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("nick", "s", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id, jabber_cmd_chat_nick, _("nick <new nickname>: " "Change your nickname."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("part", "s", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_part, _("part [message]: Leave the room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("register", "", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id, jabber_cmd_chat_register, _("register: Register with a chat room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); /* XXX: there needs to be a core /topic cmd, methinks */ id = purple_cmd_register("topic", "s", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_topic, _("topic [new topic]: View or change the topic."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("ban", "ws", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_ban, _("ban <user> [reason]: Ban a user from the room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_affiliate, _("affiliate " "<owner|admin|member|outcast|none> [nick1] [nick2] ...: " "Get the users with an affiliation or set users' affiliation " "with the room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("role", "ws", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_role, _("role <moderator|participant|visitor|none> [nick1] " "[nick2] ...: Get the users with a role or set users' role " "with the room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("invite", "ws", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_invite, _("invite <user> [message]: Invite a user to the room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("join", "ws", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_join, _("join: <room[@server]> [password]: Join a chat."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("kick", "ws", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, proto_id, jabber_cmd_chat_kick, _("kick <user> [reason]: Kick a user from the room."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("msg", "ws", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id, jabber_cmd_chat_msg, _("msg <user> <message>: " "Send a private message to another user."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); id = purple_cmd_register("ping", "w", PURPLE_CMD_P_PROTOCOL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PROTOCOL_ONLY, proto_id, jabber_cmd_ping, _("ping <jid>: Ping a user/component/server."), NULL); commands = g_slist_prepend(commands, GUINT_TO_POINTER(id)); g_hash_table_insert(jabber_cmds, protocol, commands); } static void cmds_free_func(gpointer value) { GSList *commands = value; g_slist_free_full(commands, (GDestroyNotify)(GCallback)purple_cmd_unregister); } static void jabber_unregister_commands(PurpleProtocol *protocol) { g_hash_table_remove(jabber_cmds, protocol); } static gboolean find_acct_cb(PurpleAccount *account, const gchar *protocol) { const gchar *account_protocol_id = NULL; account_protocol_id = purple_account_get_protocol_id(account); return (purple_strequal(protocol, account_protocol_id) && purple_account_is_connected(account)); } static PurpleAccount *find_acct(const char *protocol, const char *acct_id) { PurpleAccountManager *manager = NULL; PurpleAccount *acct = NULL; manager = purple_account_manager_get_default(); /* If we have a specific acct, use it */ if (acct_id) { acct = purple_account_manager_find(manager, acct_id, protocol); if (acct && !purple_account_is_connected(acct)) { acct = NULL; } } else { /* Otherwise find an active account for the protocol */ acct = purple_account_manager_find_custom(manager, (GEqualFunc)find_acct_cb, protocol); } return acct; } static gboolean xmpp_uri_handler(const char *proto, const char *user, GHashTable *params, gpointer user_data) { PurpleProtocol *protocol = (PurpleProtocol *)user_data; const gchar *acct_id = NULL; PurpleAccount *acct; g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE); if (g_ascii_strcasecmp(proto, "xmpp")) return FALSE; if (params != NULL) { acct_id = g_hash_table_lookup(params, "account"); } acct = find_acct(XMPP_PROTOCOL_ID, acct_id); if (!acct) return FALSE; /* xmpp:romeo@montague.net?message;subject=Test%20Message;body=Here%27s%20a%20test%20message */ /* params is NULL if the URI has no '?' (or anything after it) */ if (!params || g_hash_table_lookup_extended(params, "message", NULL, NULL)) { if (user && *user) { PurpleConversation *im = purple_im_conversation_new(acct, user); const gchar *body = NULL; purple_conversation_present(im); if (params != NULL) { body = g_hash_table_lookup(params, "body"); } if (body && *body) purple_conversation_send_confirm(im, body); return TRUE; } } else if (g_hash_table_lookup_extended(params, "roster", NULL, NULL)) { char *name = g_hash_table_lookup(params, "name"); if (user && *user) { purple_blist_request_add_buddy(acct, user, NULL, name); return TRUE; } } else if (g_hash_table_lookup_extended(params, "join", NULL, NULL)) { PurpleConnection *gc = purple_account_get_connection(acct); if (user && *user) { GHashTable *params = jabber_chat_info_defaults(gc, user); jabber_chat_join(gc, params); } return TRUE; } return FALSE; } static void jabber_do_init(void) { PurpleUi *ui = purple_core_get_ui(); const gchar *ui_type; const gchar *type = "pc"; /* default client type, if unknown or unspecified */ const gchar *ui_name = NULL; jabber_cmds = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, cmds_free_func); ui_type = ui ? purple_ui_get_client_type(ui) : NULL; if (ui_type) { if (purple_strequal(ui_type, "pc") || purple_strequal(ui_type, "console") || purple_strequal(ui_type, "phone") || purple_strequal(ui_type, "handheld") || purple_strequal(ui_type, "web") || purple_strequal(ui_type, "bot")) { type = ui_type; } } if (ui) ui_name = purple_ui_get_name(ui); if (ui_name == NULL) ui_name = PACKAGE; jabber_add_identity("client", type, NULL, ui_name); /* initialize jabber_features list */ jabber_add_feature(NS_LAST_ACTIVITY, NULL); jabber_add_feature(NS_OOB_IQ_DATA, NULL); jabber_add_feature(NS_ENTITY_TIME, NULL); jabber_add_feature("jabber:iq:version", NULL); jabber_add_feature("jabber:x:conference", NULL); jabber_add_feature(NS_BYTESTREAMS, NULL); jabber_add_feature("http://jabber.org/protocol/caps", NULL); jabber_add_feature("http://jabber.org/protocol/chatstates", NULL); jabber_add_feature(NS_DISCO_INFO, NULL); jabber_add_feature(NS_DISCO_ITEMS, NULL); jabber_add_feature(NS_IBB, NULL); jabber_add_feature("http://jabber.org/protocol/muc", NULL); jabber_add_feature("http://jabber.org/protocol/muc#user", NULL); jabber_add_feature("http://jabber.org/protocol/si", NULL); jabber_add_feature(NS_SI_FILE_TRANSFER, NULL); jabber_add_feature(NS_XHTML_IM, NULL); jabber_add_feature(NS_PING, NULL); /* Bits Of Binary */ jabber_add_feature(NS_BOB, NULL); /* Jingle features! */ jabber_add_feature(JINGLE, NULL); jabber_add_feature(JINGLE_APP_RTP, NULL); jabber_add_feature(JINGLE_APP_RTP_SUPPORT_AUDIO, jabber_audio_enabled); jabber_add_feature(JINGLE_APP_RTP_SUPPORT_VIDEO, jabber_video_enabled); jabber_add_feature(JINGLE_TRANSPORT_RAWUDP, NULL); jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, NULL); g_signal_connect(G_OBJECT(purple_media_manager_get()), "ui-caps-changed", G_CALLBACK(jabber_caps_broadcast_change), NULL); /* reverse order of unload_plugin */ jabber_iq_init(); jabber_presence_init(); jabber_caps_init(); /* PEP things should be init via jabber_pep_init, not here */ jabber_pep_init(); jabber_data_init(); jabber_bosh_init(); /* TODO: Implement adding and retrieving own features via IPC API */ jabber_ibb_init(); jabber_si_init(); jabber_auth_init(); } static void jabber_do_uninit(void) { /* reverse order of jabber_do_init */ jabber_bosh_uninit(); jabber_data_uninit(); jabber_si_uninit(); jabber_ibb_uninit(); /* PEP things should be uninit via jabber_pep_uninit, not here */ jabber_pep_uninit(); jabber_caps_uninit(); jabber_presence_uninit(); jabber_iq_uninit(); g_signal_handlers_disconnect_by_func(G_OBJECT(purple_media_manager_get()), G_CALLBACK(jabber_caps_broadcast_change), NULL); jabber_auth_uninit(); g_clear_list(&jabber_features, (GDestroyNotify)jabber_feature_free); g_clear_list(&jabber_identities, (GDestroyNotify)jabber_identity_free); g_clear_pointer(&jabber_cmds, g_hash_table_destroy); } static void jabber_init_protocol(PurpleProtocol *protocol) { ++plugin_ref; if (plugin_ref == 1) jabber_do_init(); jabber_register_commands(protocol); purple_signal_register(protocol, "jabber-register-namespace-watcher", purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2, G_TYPE_STRING, /* node */ G_TYPE_STRING); /* namespace */ purple_signal_register(protocol, "jabber-unregister-namespace-watcher", purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2, G_TYPE_STRING, /* node */ G_TYPE_STRING); /* namespace */ purple_signal_connect(protocol, "jabber-register-namespace-watcher", protocol, G_CALLBACK(jabber_iq_signal_register), NULL); purple_signal_connect(protocol, "jabber-unregister-namespace-watcher", protocol, G_CALLBACK(jabber_iq_signal_unregister), NULL); purple_signal_register(protocol, "jabber-receiving-xmlnode", purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2, PURPLE_TYPE_CONNECTION, G_TYPE_POINTER); /* pointer to a PurpleXmlNode* */ purple_signal_register(protocol, "jabber-sending-xmlnode", purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2, PURPLE_TYPE_CONNECTION, G_TYPE_POINTER); /* pointer to a PurpleXmlNode* */ /* * Do not remove this or the plugin will fail. Completely. You have been * warned! */ purple_signal_connect_priority(protocol, "jabber-sending-xmlnode", protocol, G_CALLBACK(jabber_send_signal_cb), NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST); purple_signal_register(protocol, "jabber-sending-text", purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2, PURPLE_TYPE_CONNECTION, G_TYPE_POINTER); /* pointer to a string */ purple_signal_register(protocol, "jabber-receiving-message", purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER_POINTER, G_TYPE_BOOLEAN, 6, PURPLE_TYPE_CONNECTION, G_TYPE_STRING, /* type */ G_TYPE_STRING, /* id */ G_TYPE_STRING, /* from */ G_TYPE_STRING, /* to */ PURPLE_TYPE_XMLNODE); purple_signal_register(protocol, "jabber-receiving-iq", purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, G_TYPE_BOOLEAN, 5, PURPLE_TYPE_CONNECTION, G_TYPE_STRING, /* type */ G_TYPE_STRING, /* id */ G_TYPE_STRING, /* from */ PURPLE_TYPE_XMLNODE); purple_signal_register(protocol, "jabber-watched-iq", purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER, G_TYPE_BOOLEAN, 5, PURPLE_TYPE_CONNECTION, G_TYPE_STRING, /* type */ G_TYPE_STRING, /* id */ G_TYPE_STRING, /* from */ PURPLE_TYPE_XMLNODE); /* child */ purple_signal_register(protocol, "jabber-receiving-presence", purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER, G_TYPE_BOOLEAN, 4, PURPLE_TYPE_CONNECTION, G_TYPE_STRING, /* type */ G_TYPE_STRING, /* from */ PURPLE_TYPE_XMLNODE); } static void jabber_uninit_protocol(PurpleProtocol *protocol) { g_return_if_fail(plugin_ref > 0); purple_signals_unregister_by_instance(protocol); jabber_unregister_commands(protocol); --plugin_ref; if (plugin_ref == 0) jabber_do_uninit(); } static PurpleBuddyIconSpec * jabber_protocol_get_buddy_icon_spec(G_GNUC_UNUSED PurpleProtocol *protocol) { return purple_buddy_icon_spec_new("png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY); } static void jabber_protocol_init(G_GNUC_UNUSED JabberProtocol *self) { } static void jabber_protocol_class_init(JabberProtocolClass *klass) { PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass); protocol_class->get_buddy_icon_spec = jabber_protocol_get_buddy_icon_spec; protocol_class->login = jabber_login; protocol_class->close = jabber_close; protocol_class->status_types = jabber_status_types; } static void jabber_protocol_class_finalize(G_GNUC_UNUSED JabberProtocolClass *klass) { } static void xmpp_protocol_actions_iface_init(PurpleProtocolActionsInterface *iface) { iface->get_prefix = xmpp_protocol_actions_get_prefix; iface->get_action_group = xmpp_protocol_actions_get_action_group; iface->get_menu = xmpp_protocol_actions_get_menu; } static void jabber_protocol_client_iface_init(PurpleProtocolClientInterface *client_iface) { client_iface->list_emblem = jabber_list_emblem; client_iface->blist_node_menu = jabber_blist_node_menu; client_iface->convo_closed = jabber_convo_closed; client_iface->normalize = jabber_client_normalize; client_iface->find_blist_chat = jabber_find_blist_chat; client_iface->offline_message = jabber_offline_message; } static void jabber_protocol_server_iface_init(PurpleProtocolServerInterface *server_iface) { server_iface->set_info = jabber_set_info; server_iface->get_info = jabber_buddy_get_info; server_iface->set_status = jabber_set_status; server_iface->set_idle = jabber_idle_set; server_iface->add_buddy = jabber_roster_add_buddy; server_iface->remove_buddy = jabber_roster_remove_buddy; server_iface->keepalive = jabber_keepalive; server_iface->get_keepalive_interval = jabber_get_keepalive_interval; server_iface->alias_buddy = jabber_roster_alias_change; server_iface->group_buddy = jabber_roster_group_change; server_iface->rename_group = jabber_roster_group_rename; server_iface->set_buddy_icon = jabber_set_buddy_icon; server_iface->send_raw = jabber_protocol_send_raw; } static void jabber_protocol_im_iface_init(PurpleProtocolIMInterface *im_iface) { im_iface->send = jabber_message_send_im; im_iface->send_typing = jabber_send_typing; } static GHashTable * jabber_protocol_chat_info_defaults(G_GNUC_UNUSED PurpleProtocolChat *protocol_chat, PurpleConnection *connection, const gchar *name) { return jabber_chat_info_defaults(connection, name); } static void jabber_protocol_chat_join(G_GNUC_UNUSED PurpleProtocolChat *protocol_chat, PurpleConnection *connection, GHashTable *components) { jabber_chat_join(connection, components); } static void jabber_protocol_chat_invite(G_GNUC_UNUSED PurpleProtocolChat *protocol_chat, PurpleConnection *connection, gint id, const gchar *message, const gchar *who) { jabber_chat_invite(connection, id, message, who); } static void jabber_protocol_chat_iface_init(PurpleProtocolChatInterface *chat_iface) { chat_iface->info = jabber_chat_info; chat_iface->info_defaults = jabber_protocol_chat_info_defaults; chat_iface->join = jabber_protocol_chat_join; chat_iface->get_name = jabber_get_chat_name; chat_iface->invite = jabber_protocol_chat_invite; chat_iface->leave = jabber_chat_leave; chat_iface->send = jabber_message_send_chat; chat_iface->get_user_real_name = jabber_chat_user_real_name; chat_iface->set_topic = jabber_chat_set_topic; } static void jabber_protocol_roomlist_iface_init(PurpleProtocolRoomlistInterface *roomlist_iface) { roomlist_iface->get_list = jabber_roomlist_get_list; roomlist_iface->cancel = jabber_roomlist_cancel; roomlist_iface->room_serialize = jabber_roomlist_room_serialize; } static void jabber_protocol_media_iface_init(PurpleProtocolMediaInterface *media_iface) { media_iface->initiate_session = jabber_initiate_media; media_iface->get_caps = jabber_get_media_caps; } static void jabber_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface) { xfer_iface->can_receive = jabber_can_receive_file; xfer_iface->send_file = jabber_si_xfer_send; xfer_iface->new_xfer = jabber_si_new_xfer; } G_DEFINE_DYNAMIC_TYPE_EXTENDED( JabberProtocol, jabber_protocol, PURPLE_TYPE_PROTOCOL, G_TYPE_FLAG_ABSTRACT, G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ACTIONS, xmpp_protocol_actions_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CLIENT, jabber_protocol_client_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_SERVER, jabber_protocol_server_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_IM, jabber_protocol_im_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CHAT, jabber_protocol_chat_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ROOMLIST, jabber_protocol_roomlist_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_MEDIA, jabber_protocol_media_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_XFER, jabber_protocol_xfer_iface_init)) static GPluginPluginInfo * jabber_query(G_GNUC_UNUSED GError **error) { return purple_plugin_info_new( "id", "prpl-xmpp", "name", "XMPP Protocols", "version", DISPLAY_VERSION, "category", N_("Protocol"), "summary", N_("XMPP Protocol Plugin"), "description", N_("XMPP Protocol Plugin"), "website", PURPLE_WEBSITE, "abi-version", PURPLE_ABI_VERSION, "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD, NULL ); } static gboolean jabber_load(GPluginPlugin *plugin, GError **error) { PurpleProtocolManager *manager = purple_protocol_manager_get_default(); jingle_session_register(plugin); jingle_transport_register(plugin); jingle_iceudp_register(plugin); jingle_rawudp_register(plugin); jingle_content_register(plugin); jingle_rtp_register(plugin); jabber_protocol_register_type(G_TYPE_MODULE(plugin)); xmpp_protocol_register(plugin); jabber_oob_xfer_register(G_TYPE_MODULE(plugin)); jabber_si_xfer_register(G_TYPE_MODULE(plugin)); xmpp_protocol = xmpp_protocol_new(); if(!purple_protocol_manager_register(manager, xmpp_protocol, error)) { g_clear_object(&xmpp_protocol); return FALSE; } purple_signal_connect(purple_get_core(), "uri-handler", xmpp_protocol, G_CALLBACK(xmpp_uri_handler), xmpp_protocol); jabber_init_protocol(xmpp_protocol); return TRUE; } static gboolean jabber_unload(G_GNUC_UNUSED GPluginPlugin *plugin, G_GNUC_UNUSED gboolean shutdown, GError **error) { PurpleProtocolManager *manager = purple_protocol_manager_get_default(); if(!purple_protocol_manager_unregister(manager, xmpp_protocol, error)) { return FALSE; } purple_signal_disconnect(purple_get_core(), "uri-handler", xmpp_protocol, G_CALLBACK(xmpp_uri_handler)); jabber_uninit_protocol(xmpp_protocol); g_clear_object(&xmpp_protocol); return TRUE; } GPLUGIN_NATIVE_PLUGIN_DECLARE(jabber)