pidgin/pidgin

1896a80ff8e3
Route GLib debug logging directly to the Finch debug window

Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.

Testing Done:
Open the Debug window an see some `GLib-*` outputs.

Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* 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 <glib/gi18n-lib.h>
#include <purple.h>
#include "buddy.h"
#include "disco.h"
#include "iq.h"
#include "jingle/jingle.h"
#include "oob.h"
#include "roster.h"
#include "si.h"
#include "ping.h"
#include "adhoccommands.h"
#include "data.h"
#include "ibb.h"
static GHashTable *iq_handlers = NULL;
static GHashTable *signal_iq_handlers = NULL;
struct _JabberIqCallbackData {
JabberIqCallback *callback;
gpointer data;
JabberID *to;
};
void jabber_iq_callbackdata_free(JabberIqCallbackData *jcd)
{
jabber_id_free(jcd->to);
g_free(jcd);
}
JabberIq *jabber_iq_new(JabberStream *js, JabberIqType type)
{
JabberIq *iq;
iq = g_new0(JabberIq, 1);
iq->type = type;
iq->node = purple_xmlnode_new("iq");
switch(iq->type) {
case JABBER_IQ_SET:
purple_xmlnode_set_attrib(iq->node, "type", "set");
break;
case JABBER_IQ_GET:
purple_xmlnode_set_attrib(iq->node, "type", "get");
break;
case JABBER_IQ_ERROR:
purple_xmlnode_set_attrib(iq->node, "type", "error");
break;
case JABBER_IQ_RESULT:
purple_xmlnode_set_attrib(iq->node, "type", "result");
break;
case JABBER_IQ_NONE:
/* this shouldn't ever happen */
break;
}
iq->js = js;
if(type == JABBER_IQ_GET || type == JABBER_IQ_SET) {
iq->id = jabber_get_next_id(js);
purple_xmlnode_set_attrib(iq->node, "id", iq->id);
}
return iq;
}
JabberIq *jabber_iq_new_query(JabberStream *js, JabberIqType type,
const char *xmlns)
{
JabberIq *iq = jabber_iq_new(js, type);
PurpleXmlNode *query;
query = purple_xmlnode_new_child(iq->node, "query");
purple_xmlnode_set_namespace(query, xmlns);
return iq;
}
void
jabber_iq_set_callback(JabberIq *iq, JabberIqCallback *callback, gpointer data)
{
iq->callback = callback;
iq->callback_data = data;
}
void jabber_iq_set_id(JabberIq *iq, const char *id)
{
g_free(iq->id);
if(id) {
purple_xmlnode_set_attrib(iq->node, "id", id);
iq->id = g_strdup(id);
} else {
purple_xmlnode_remove_attrib(iq->node, "id");
iq->id = NULL;
}
}
void jabber_iq_send(JabberIq *iq)
{
JabberIqCallbackData *jcd;
g_return_if_fail(iq != NULL);
jabber_send(iq->js, iq->node);
if(iq->id && iq->callback) {
jcd = g_new0(JabberIqCallbackData, 1);
jcd->callback = iq->callback;
jcd->data = iq->callback_data;
jcd->to = jabber_id_new(purple_xmlnode_get_attrib(iq->node, "to"));
g_hash_table_insert(iq->js->iq_callbacks, g_strdup(iq->id), jcd);
}
jabber_iq_free(iq);
}
void jabber_iq_free(JabberIq *iq)
{
g_return_if_fail(iq != NULL);
g_free(iq->id);
purple_xmlnode_free(iq->node);
g_free(iq);
}
static void jabber_iq_last_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet)
{
JabberIq *iq;
PurpleXmlNode *query;
char *idle_time;
if(type == JABBER_IQ_GET) {
iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, NS_LAST_ACTIVITY);
jabber_iq_set_id(iq, id);
if (from)
purple_xmlnode_set_attrib(iq->node, "to", from);
query = purple_xmlnode_get_child(iq->node, "query");
idle_time =
g_strdup_printf("%" G_GINT64_FORMAT,
(gint64)(js->idle ? time(NULL) - js->idle : 0));
purple_xmlnode_set_attrib(query, "seconds", idle_time);
g_free(idle_time);
jabber_iq_send(iq);
}
}
static void jabber_time_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *child)
{
JabberIq *iq;
if(type == JABBER_IQ_GET) {
PurpleXmlNode *tzo, *utc;
GDateTime *now, *now_utc;
gchar *date, *tz;
iq = jabber_iq_new(js, JABBER_IQ_RESULT);
jabber_iq_set_id(iq, id);
if (from)
purple_xmlnode_set_attrib(iq->node, "to", from);
child = purple_xmlnode_new_child(iq->node, child->name);
purple_xmlnode_set_namespace(child, NS_ENTITY_TIME);
/* <tzo>-06:00</tzo> */
now = g_date_time_new_now_local();
tz = g_date_time_format(now, "%:z");
tzo = purple_xmlnode_new_child(child, "tzo");
purple_xmlnode_insert_data(tzo, tz, -1);
g_free(tz);
/* <utc>2006-12-19T17:58:35Z</utc> */
now_utc = g_date_time_to_utc(now);
date = g_date_time_format(now_utc, "%FT%TZ");
utc = purple_xmlnode_new_child(child, "utc");
purple_xmlnode_insert_data(utc, date, -1);
g_free(date);
g_date_time_unref(now);
g_date_time_unref(now_utc);
jabber_iq_send(iq);
} else {
/* TODO: Errors */
}
}
static void jabber_iq_version_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet)
{
JabberIq *iq;
PurpleXmlNode *query;
if(type == JABBER_IQ_GET) {
PurpleUiInfo *ui_info;
const char *ui_name = NULL, *ui_version = NULL;
iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:version");
if (from)
purple_xmlnode_set_attrib(iq->node, "to", from);
jabber_iq_set_id(iq, id);
query = purple_xmlnode_get_child(iq->node, "query");
ui_info = purple_core_get_ui_info();
if(PURPLE_IS_UI_INFO(ui_info)) {
ui_name = purple_ui_info_get_name(ui_info);
ui_version = purple_ui_info_get_version(ui_info);
}
if(NULL != ui_name && NULL != ui_version) {
char *version_complete = g_strdup_printf("%s (libpurple " VERSION ")", ui_version);
purple_xmlnode_insert_data(purple_xmlnode_new_child(query, "name"), ui_name, -1);
purple_xmlnode_insert_data(purple_xmlnode_new_child(query, "version"), version_complete, -1);
g_free(version_complete);
} else {
purple_xmlnode_insert_data(purple_xmlnode_new_child(query, "name"), "libpurple", -1);
purple_xmlnode_insert_data(purple_xmlnode_new_child(query, "version"), VERSION, -1);
}
jabber_iq_send(iq);
if(PURPLE_IS_UI_INFO(ui_info)) {
g_object_unref(G_OBJECT(ui_info));
}
}
}
void jabber_iq_remove_callback_by_id(JabberStream *js, const char *id)
{
g_hash_table_remove(js->iq_callbacks, id);
}
/**
* Verify that the 'from' attribute of an IQ reply is a valid match for
* a given IQ request. The expected behavior is outlined in section
* 8.1.2.1 of the XMPP CORE spec (RFC 6120). We consider the reply to
* be a valid match if any of the following is true:
* - Request 'to' matches reply 'from' (including the case where
* neither are set).
* - Request 'to' was my JID (bare or full) and reply 'from' is empty.
* - Request 'to' was empty and reply 'from' is my JID. The spec says
* we should only allow bare JID, but we also allow full JID for
* compatibility with some servers.
* - Request 'to' was empty and reply 'from' is server JID. Not allowed by
* any spec, but for compatibility with some servers.
*
* These rules should allow valid IQ replies while preventing spoofed
* ones.
*
* For more discussion see the "Spoofing of iq ids and misbehaving
* servers" email thread from January 2014 on the jdev and security
* mailing lists. Also see https://developer.pidgin.im/ticket/15879
*
* @return TRUE if this reply is valid for the given request.
*/
static gboolean does_reply_from_match_request_to(JabberStream *js, JabberID *to, JabberID *from)
{
if (jabber_id_equal(to, from)) {
/* Request 'to' matches reply 'from' */
return TRUE;
}
if (!from && purple_strequal(to->node, js->user->node)
&& purple_strequal(to->domain, js->user->domain)) {
/* Request 'to' was my JID (bare or full) and reply 'from' is empty */
return TRUE;
}
if (!to && purple_strequal(from->domain, js->user->domain)) {
/* Request 'to' is empty and reply 'from' domain matches our domain */
if (!from->node && !from->resource) {
/* Reply 'from' is server bare JID */
return TRUE;
}
if (purple_strequal(from->node, js->user->node)
&& (!from->resource || purple_strequal(from->resource, js->user->resource))) {
/* Reply 'from' is my full or bare JID */
return TRUE;
}
}
return FALSE;
}
void jabber_iq_parse(JabberStream *js, PurpleXmlNode *packet)
{
JabberIqCallbackData *jcd;
PurpleXmlNode *child, *error, *x;
const char *xmlns;
const char *iq_type, *id, *from;
JabberIqType type = JABBER_IQ_NONE;
gboolean signal_return;
JabberID *from_id;
from = purple_xmlnode_get_attrib(packet, "from");
id = purple_xmlnode_get_attrib(packet, "id");
iq_type = purple_xmlnode_get_attrib(packet, "type");
/*
* Ensure the 'from' attribute is valid. No point in handling a stanza
* of which we don't understand where it came from.
*/
from_id = jabber_id_new(from);
if (from && !from_id) {
purple_debug_error("jabber", "Received an iq with an invalid from: %s\n", from);
return;
}
/*
* child will be either the first tag child or NULL if there is no child.
* Historically, we used just the 'query' subchild, but newer XEPs use
* differently named children. Grabbing the first child is (for the time
* being) sufficient.
*/
for (child = packet->child; child; child = child->next) {
if (child->type == PURPLE_XMLNODE_TYPE_TAG)
break;
}
if (iq_type) {
if (purple_strequal(iq_type, "get"))
type = JABBER_IQ_GET;
else if (purple_strequal(iq_type, "set"))
type = JABBER_IQ_SET;
else if (purple_strequal(iq_type, "result"))
type = JABBER_IQ_RESULT;
else if (purple_strequal(iq_type, "error"))
type = JABBER_IQ_ERROR;
}
if (type == JABBER_IQ_NONE) {
purple_debug_error("jabber", "IQ with invalid type ('%s') - ignoring.\n",
iq_type ? iq_type : "(null)");
jabber_id_free(from_id);
return;
}
/* All IQs must have an ID, so send an error for a set/get that doesn't */
if(!id || !*id) {
if(type == JABBER_IQ_SET || type == JABBER_IQ_GET) {
JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
purple_xmlnode_free(iq->node);
iq->node = purple_xmlnode_copy(packet);
if (from) {
purple_xmlnode_set_attrib(iq->node, "to", from);
purple_xmlnode_remove_attrib(iq->node, "from");
}
purple_xmlnode_set_attrib(iq->node, "type", "error");
/* This id is clearly not useful, but we must put something there for a valid stanza */
iq->id = jabber_get_next_id(js);
purple_xmlnode_set_attrib(iq->node, "id", iq->id);
error = purple_xmlnode_new_child(iq->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(iq);
} else
purple_debug_error("jabber", "IQ of type '%s' missing id - ignoring.\n",
iq_type);
jabber_id_free(from_id);
return;
}
signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_protocol(js->gc),
"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
if (signal_return) {
jabber_id_free(from_id);
return;
}
/* First, lets see if a special callback got registered */
if(type == JABBER_IQ_RESULT || type == JABBER_IQ_ERROR) {
jcd = g_hash_table_lookup(js->iq_callbacks, id);
if (jcd) {
if (does_reply_from_match_request_to(js, jcd->to, from_id)) {
jcd->callback(js, from, type, id, packet, jcd->data);
jabber_iq_remove_callback_by_id(js, id);
jabber_id_free(from_id);
return;
} else {
char *expected_to;
if (jcd->to) {
expected_to = jabber_id_get_full_jid(jcd->to);
} else {
expected_to = jabber_id_get_bare_jid(js->user);
}
purple_debug_error("jabber", "Got a result iq with id %s from %s instead of expected %s!\n", id, from ? from : "(null)", expected_to);
g_free(expected_to);
}
}
}
/*
* Apparently not, so let's see if we have a pre-defined handler
* or if an outside plugin is interested.
*/
if(child && (xmlns = purple_xmlnode_get_namespace(child))) {
char *key = g_strdup_printf("%s %s", child->name, xmlns);
JabberIqHandler *jih = g_hash_table_lookup(iq_handlers, key);
int signal_ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
g_free(key);
if (signal_ref > 0) {
signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_protocol(js->gc), "jabber-watched-iq",
js->gc, iq_type, id, from, child));
if (signal_return) {
jabber_id_free(from_id);
return;
}
}
if(jih) {
jih(js, from, type, id, child);
jabber_id_free(from_id);
return;
}
}
purple_debug_misc("jabber", "Unhandled IQ with id %s\n", id);
/* If we get here, send the default error reply mandated by XMPP-CORE */
if(type == JABBER_IQ_SET || type == JABBER_IQ_GET) {
JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
purple_xmlnode_free(iq->node);
iq->node = purple_xmlnode_copy(packet);
if (from) {
purple_xmlnode_set_attrib(iq->node, "to", from);
purple_xmlnode_remove_attrib(iq->node, "from");
}
purple_xmlnode_set_attrib(iq->node, "type", "error");
error = purple_xmlnode_new_child(iq->node, "error");
purple_xmlnode_set_attrib(error, "type", "cancel");
purple_xmlnode_set_attrib(error, "code", "501");
x = purple_xmlnode_new_child(error, "feature-not-implemented");
purple_xmlnode_set_namespace(x, NS_XMPP_STANZAS);
jabber_iq_send(iq);
}
jabber_id_free(from_id);
}
void jabber_iq_register_handler(const char *node, const char *xmlns, JabberIqHandler *handlerfunc)
{
/*
* This is valid because nodes nor namespaces cannot have spaces in them
* (see http://www.w3.org/TR/2006/REC-xml-20060816/ and
* http://www.w3.org/TR/REC-xml-names/)
*/
char *key = g_strdup_printf("%s %s", node, xmlns);
g_hash_table_replace(iq_handlers, key, handlerfunc);
}
void jabber_iq_signal_register(const gchar *node, const gchar *xmlns)
{
gchar *key;
int ref;
g_return_if_fail(node != NULL && *node != '\0');
g_return_if_fail(xmlns != NULL && *xmlns != '\0');
key = g_strdup_printf("%s %s", node, xmlns);
ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
if (ref == 0) {
g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(1));
} else {
g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(ref + 1));
g_free(key);
}
}
void jabber_iq_signal_unregister(const gchar *node, const gchar *xmlns)
{
gchar *key;
int ref;
g_return_if_fail(node != NULL && *node != '\0');
g_return_if_fail(xmlns != NULL && *xmlns != '\0');
key = g_strdup_printf("%s %s", node, xmlns);
ref = GPOINTER_TO_INT(g_hash_table_lookup(signal_iq_handlers, key));
if (ref == 1) {
g_hash_table_remove(signal_iq_handlers, key);
} else if (ref > 1) {
g_hash_table_insert(signal_iq_handlers, key, GINT_TO_POINTER(ref - 1));
}
g_free(key);
}
void jabber_iq_init(void)
{
iq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
signal_iq_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
jabber_iq_register_handler("jingle", JINGLE, jingle_parse);
jabber_iq_register_handler("ping", NS_PING, jabber_ping_parse);
jabber_iq_register_handler("query", NS_BYTESTREAMS,
jabber_bytestreams_parse);
jabber_iq_register_handler("query", NS_DISCO_INFO, jabber_disco_info_parse);
jabber_iq_register_handler("query", NS_DISCO_ITEMS, jabber_disco_items_parse);
jabber_iq_register_handler("query", NS_LAST_ACTIVITY, jabber_iq_last_parse);
jabber_iq_register_handler("query", NS_OOB_IQ_DATA, jabber_oob_parse);
jabber_iq_register_handler("query", "jabber:iq:register",
jabber_register_parse);
jabber_iq_register_handler("query", "jabber:iq:roster",
jabber_roster_parse);
jabber_iq_register_handler("query", "jabber:iq:version",
jabber_iq_version_parse);
jabber_iq_register_handler("block", NS_SIMPLE_BLOCKING, jabber_blocklist_parse_push);
jabber_iq_register_handler("unblock", NS_SIMPLE_BLOCKING, jabber_blocklist_parse_push);
jabber_iq_register_handler("time", NS_ENTITY_TIME, jabber_time_parse);
}
void jabber_iq_uninit(void)
{
g_hash_table_destroy(iq_handlers);
g_hash_table_destroy(signal_iq_handlers);
iq_handlers = signal_iq_handlers = NULL;
}