grim/pidgin

10b60bd1cafa
Fix a meson warning about concatenating lists and strings

This is actually allowed in newer versions, but this variable should be a list
anyways.

Testing Done:
Ran `ninja reconfigure` and verified that the warning was gone.

Reviewed at https://reviews.imfreedom.org/r/2493/
/*
* 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 <config.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <gplugin.h>
#include <gplugin-native.h>
#include <purple.h>
#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("<stream:stream to='%s' "
"xmlns='" NS_XMPP_CLIENT "' "
"xmlns:stream='" NS_XMPP_STREAMS "' "
"version='1.0'>",
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,
"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -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 <failure/>, 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 <auth> or <query><password>... */
(((tag_start = strstr(data, "<auth ")) &&
strstr(data, "xmlns='" NS_XMPP_SASL "'")) ||
((tag_start = strstr(data, "<query ")) &&
strstr(data, "xmlns='jabber:iq:auth'>") &&
(tag_start = strstr(tag_start, "<password>"))))) {
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,
G_SOURCE_FUNC(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, "<?xml version='1.0' ?>", -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, "</stream:stream>", -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 <block/> 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 &lt;new nickname&gt;: "
"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 &lt;user&gt; [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 "
"&lt;owner|admin|member|outcast|none&gt; [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 &lt;moderator|participant|visitor|none&gt; [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 &lt;user&gt; [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: &lt;room[@server]&gt; [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 &lt;user&gt; [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 &lt;user&gt; &lt;message&gt;: "
"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 &lt;jid&gt;: 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)