Permalink
Browse files

jabber: Implement carbons (XEP-0280)

"Message carbons" (XEP-0280) is a server feature to get copies of
outgoing messages sent from other clients connected to the same account.
It's not widely supported by most public XMPP servers (easier if you
host your own), but this will probably change in the next few years.

This is enabled by default if the server supports it. It can also be
disabled with the "carbons" account setting.

Loosely based on a patch by kormat from trac ticket 1021. (Thanks!)
I moved stuff around, simplified things, fixed a few bugs, and used the
new self-messages feature.
  • Loading branch information...
dequis committed Nov 21, 2015
1 parent 8fdeaa5 commit fa8f57b7f51a44ee0870d1bf29646007e087f24e
Showing with 106 additions and 8 deletions.
  1. +16 −0 doc/user-guide/commands.xml
  2. +37 −2 protocols/jabber/iq.c
  3. +3 −0 protocols/jabber/jabber.c
  4. +2 −0 protocols/jabber/jabber.h
  5. +48 −6 protocols/jabber/message.c
@@ -788,6 +788,22 @@
</description>
</bitlbee-setting>

<bitlbee-setting name="carbons" type="boolean" scope="account">
<default>true</default>

<description>
<para>
Jabber specific. "Message carbons" (XEP-0280) is a server feature to get copies of outgoing messages sent from other clients connected to the same account. It's not widely supported by most public XMPP servers (easier if you host your own), but this will probably change in the next few years.
</para>
<para>
This defaults to true, which will enable it if the server supports it, or fail silently if it's not. This setting only exists to allow disabling the feature if anyone considers it undesirable.
</para>
<para>
See also the <emphasis>self_messages</emphasis> setting.
</para>
</description>
</bitlbee-setting>

<bitlbee-setting name="charset" type="string" scope="global">
<default>utf-8</default>
<possible-values>you can get a list of all possible values by doing 'iconv -l' in a shell</possible-values>
@@ -27,6 +27,7 @@
static xt_status jabber_parse_roster(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
static xt_status jabber_iq_display_vcard(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
static xt_status jabber_gmail_handle_new(struct im_connection *ic, struct xt_node *node);
static xt_status jabber_iq_carbons_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);

xt_status jabber_pkt_iq(struct xt_node *node, gpointer data)
{
@@ -117,6 +118,7 @@ xt_status jabber_pkt_iq(struct xt_node *node, gpointer data)
XMLNS_SI,
XMLNS_BYTESTREAMS,
XMLNS_FILETRANSFER,
XMLNS_CARBONS,
NULL };
const char **f;

@@ -1003,9 +1005,26 @@ static xt_status jabber_iq_disco_server_response(struct im_connection *ic,
struct xt_node *node, struct xt_node *orig)
{
struct jabber_data *jd = ic->proto_data;
struct xt_node *id;
struct xt_node *query, *id;

if ((id = xt_find_path(node, "query/identity"))) {
if (!(query = xt_find_node(node->children, "query"))) {
return XT_HANDLED;
}

if (xt_find_node_by_attr(query->children, "feature", "var", XMLNS_CARBONS) &&
set_getbool(&ic->acc->set, "carbons")) {

struct xt_node *enable, *iq;

enable = xt_new_node("enable", NULL, NULL);
xt_add_attr(enable, "xmlns", XMLNS_CARBONS);
iq = jabber_make_packet("iq", "set", NULL, enable);

jabber_cache_add(ic, iq, jabber_iq_carbons_response);
jabber_write_packet(ic, iq);
}

if ((id = xt_find_node(query->children, "identity"))) {
char *cat, *type, *name;

if (!(cat = xt_find_attr(id, "category")) ||
@@ -1022,3 +1041,19 @@ static xt_status jabber_iq_disco_server_response(struct im_connection *ic,

return XT_HANDLED;
}

static xt_status jabber_iq_carbons_response(struct im_connection *ic,
struct xt_node *node, struct xt_node *orig)
{
struct jabber_error *err;

if ((err = jabber_error_parse(xt_find_node(node->children, "error"), XMLNS_STANZA_ERROR))) {
imcb_error(ic, "Error enabling carbons: %s%s%s",
err->code, err->text ? ": " : "", err->text ? err->text : "");
jabber_error_free(err);
} else {
imcb_log(ic, "Carbons enabled");
}

return XT_HANDLED;
}
@@ -113,6 +113,9 @@ static void jabber_init(account_t *acc)
s = set_add(&acc->set, "mail_notifications_handle", NULL, NULL, acc);
s->flags |= ACC_SET_OFFLINE_ONLY | SET_NULL_OK;

s = set_add(&acc->set, "carbons", "true", set_eval_bool, acc);
s->flags |= ACC_SET_OFFLINE_ONLY;

acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE |
ACC_FLAG_HANDLE_DOMAINS;
}
@@ -227,6 +227,8 @@ struct jabber_transfer {
#define XMLNS_DELAY "urn:xmpp:delay" /* XEP-0203 */
#define XMLNS_XDATA "jabber:x:data" /* XEP-0004 */
#define XMLNS_GMAILNOTIFY "google:mail:notify" /* Not a XEP */
#define XMLNS_CARBONS "urn:xmpp:carbons:2" /* XEP-0280 */
#define XMLNS_FORWARDING "urn:xmpp:forward:0" /* XEP-0297 */
#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* XEP-0085 */
#define XMLNS_DISCO_INFO "http://jabber.org/protocol/disco#info" /* XEP-0030 */
#define XMLNS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" /* XEP-0030 */
@@ -23,10 +23,10 @@

#include "jabber.h"

xt_status jabber_pkt_message(struct xt_node *node, gpointer data)
static xt_status jabber_pkt_message_normal(struct xt_node *node, gpointer data, gboolean carbons_sent)
{
struct im_connection *ic = data;
char *from = xt_find_attr(node, "from");
char *from = xt_find_attr(node, carbons_sent ? "to" : "from");
char *type = xt_find_attr(node, "type");
char *id = xt_find_attr(node, "id");
struct xt_node *body = xt_find_node(node->children, "body"), *c;
@@ -38,7 +38,7 @@ xt_status jabber_pkt_message(struct xt_node *node, gpointer data)
return XT_HANDLED; /* Consider this packet corrupted. */
}

if (request && id && g_strcmp0(type, "groupchat") != 0) {
if (request && id && g_strcmp0(type, "groupchat") != 0 && !carbons_sent) {
/* Send a message receipt (XEP-0184), looking like this:
* <message from='...' id='...' to='...'>
* <received xmlns='urn:xmpp:receipts' id='richard2-4.1.247'/>
@@ -127,7 +127,7 @@ xt_status jabber_pkt_message(struct xt_node *node, gpointer data)

if (fullmsg->len > 0) {
imcb_buddy_msg(ic, from, fullmsg->str,
0, jabber_get_timestamp(node));
carbons_sent ? OPT_SELFMESSAGE : 0, jabber_get_timestamp(node));
}
if (room) {
imcb_chat_invite(ic, room, from, reason);
@@ -136,8 +136,9 @@ xt_status jabber_pkt_message(struct xt_node *node, gpointer data)
g_string_free(fullmsg, TRUE);

/* Handling of incoming typing notifications. */
if (bud == NULL) {
/* Can't handle these for unknown buddies. */
if (bud == NULL || carbons_sent) {
/* Can't handle these for unknown buddies.
And ignore them if it's just carbons */
} else if (xt_find_node(node->children, "composing")) {
bud->flags |= JBFLAG_DOES_XEP85;
imcb_buddy_typing(ic, from, OPT_TYPING);
@@ -161,3 +162,44 @@ xt_status jabber_pkt_message(struct xt_node *node, gpointer data)

return XT_HANDLED;
}

static xt_status jabber_carbons_message(struct xt_node *node, gpointer data)
{
struct im_connection *ic = data;
struct xt_node *wrap, *fwd, *msg;
gboolean carbons_sent;

if ((wrap = xt_find_node(node->children, "received"))) {
carbons_sent = FALSE;
} else if ((wrap = xt_find_node(node->children, "sent"))) {
carbons_sent = TRUE;
}

if (wrap == NULL || g_strcmp0(xt_find_attr(wrap, "xmlns"), XMLNS_CARBONS) != 0) {
return XT_NEXT;
}

if (!(fwd = xt_find_node(wrap->children, "forwarded")) ||
(g_strcmp0(xt_find_attr(fwd, "xmlns"), XMLNS_FORWARDING) != 0) ||
!(msg = xt_find_node(fwd->children, "message"))) {
imcb_log(ic, "Error: Invalid carbons message received");
return XT_ABORT;
}

return jabber_pkt_message_normal(msg, data, carbons_sent);
}

xt_status jabber_pkt_message(struct xt_node *node, gpointer data)
{
struct im_connection *ic = data;
struct jabber_data *jd = ic->proto_data;
char *from = xt_find_attr(node, "from");

if (jabber_compare_jid(jd->me, from)) { /* Probably a Carbons message */
xt_status st = jabber_carbons_message(node, data);
if (st == XT_HANDLED || st == XT_ABORT) {
return st;
}
}
return jabber_pkt_message_normal(node, data, FALSE);
}

0 comments on commit fa8f57b

Please sign in to comment.