You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
947 lines
31 KiB
947 lines
31 KiB
/* |
|
* xmpp_muc.c -- Jabber MUC protocol handling |
|
* |
|
* Copyright (C) 2008-2010 Frank Zschockelt <mcabber@freakysoft.de> |
|
* Copyright (C) 2005-2014 Mikael Berthe <mikael@lilotux.net> |
|
* Copyright (C) 2010 Myhailo Danylenko <isbear@ukrpost.net> |
|
* |
|
* 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, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
#include <string.h> |
|
#include <stdlib.h> |
|
|
|
#include "xmpp_helper.h" |
|
#include "xmpp_iq.h" |
|
#include "xmpp_muc.h" |
|
#include "events.h" |
|
#include "hooks.h" |
|
#include "screen.h" |
|
#include "hbuf.h" |
|
#include "roster.h" |
|
#include "commands.h" |
|
#include "settings.h" |
|
#include "utils.h" |
|
#include "histolog.h" |
|
|
|
extern enum imstatus mystatus; |
|
extern gchar *mystatusmsg; |
|
|
|
static GSList *invitations = NULL; |
|
|
|
static void decline_invitation(event_muc_invitation_t *invitation, const char *reason) |
|
{ |
|
// cut and paste from xmpp_room_invite |
|
LmMessage *m; |
|
LmMessageNode *x, *y; |
|
|
|
if (!invitation) return; |
|
if (!invitation->to || !invitation->from) return; |
|
|
|
m = lm_message_new(invitation->to, LM_MESSAGE_TYPE_MESSAGE); |
|
|
|
x = lm_message_node_add_child(m->node, "x", NULL); |
|
lm_message_node_set_attribute(x, "xmlns", NS_MUC_USER); |
|
|
|
y = lm_message_node_add_child(x, "decline", NULL); |
|
lm_message_node_set_attribute(y, "to", invitation->from); |
|
|
|
if (reason) |
|
lm_message_node_add_child(y, "reason", reason); |
|
|
|
lm_connection_send(lconnection, m, NULL); |
|
lm_message_unref(m); |
|
} |
|
|
|
void destroy_event_muc_invitation(event_muc_invitation_t *invitation) |
|
{ |
|
invitations = g_slist_remove(invitations, invitation); |
|
g_free(invitation->to); |
|
g_free(invitation->from); |
|
g_free(invitation->passwd); |
|
g_free(invitation->reason); |
|
g_free(invitation->evid); |
|
g_free(invitation); |
|
} |
|
|
|
// invitation event handler |
|
// TODO: if event is accepted, check if other events to the same room exist and |
|
// destroy them? (need invitation registry list for that) |
|
static gboolean evscallback_invitation(guint evcontext, const char *arg, gpointer userdata) |
|
{ |
|
event_muc_invitation_t *invitation = userdata; |
|
|
|
// Sanity check |
|
if (G_UNLIKELY(!invitation)) { |
|
// Shouldn't happen. |
|
scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback."); |
|
return FALSE; |
|
} |
|
|
|
if (evcontext == EVS_CONTEXT_TIMEOUT) { |
|
scr_LogPrint(LPRINT_LOGNORM, "Invitation event %s timed out, cancelled.", invitation->to); |
|
return FALSE; |
|
} |
|
if (evcontext == EVS_CONTEXT_CANCEL) { |
|
scr_LogPrint(LPRINT_LOGNORM, "Invitation event %s cancelled.", invitation->to); |
|
return FALSE; |
|
} |
|
if (!(evcontext == EVS_CONTEXT_ACCEPT || evcontext == EVS_CONTEXT_REJECT)) |
|
return FALSE; |
|
|
|
// Ok, let's work now |
|
if (evcontext == EVS_CONTEXT_ACCEPT) { |
|
char *nickname = default_muc_nickname(invitation->to); |
|
xmpp_room_join(invitation->to, nickname, invitation->passwd); |
|
g_free(nickname); |
|
} else { |
|
scr_LogPrint(LPRINT_LOGNORM, "Invitation to %s refused.", invitation->to); |
|
if (invitation->reply) |
|
decline_invitation(invitation, arg); |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
// Join a MUC room |
|
void xmpp_room_join(const char *room, const char *nickname, const char *passwd) |
|
{ |
|
LmMessage *x; |
|
LmMessageNode *y; |
|
gchar *roomid; |
|
GSList *room_elt; |
|
|
|
if (!xmpp_is_online() || !room || !nickname) |
|
return; |
|
|
|
roomid = g_strdup_printf("%s/%s", room, nickname); |
|
if (check_jid_syntax(roomid)) { |
|
scr_LogPrint(LPRINT_NORMAL, "<%s/%s> is not a valid Jabber room", room, |
|
nickname); |
|
g_free(roomid); |
|
return; |
|
} |
|
|
|
room_elt = roster_find(room, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM); |
|
// Add room if it doesn't already exist |
|
if (!room_elt) { |
|
room_elt = roster_add_user(room, NULL, NULL, ROSTER_TYPE_ROOM, |
|
sub_none, -1); |
|
} else { |
|
// Make sure this is a room (it can be a conversion user->room) |
|
buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); |
|
} |
|
// If insideroom is TRUE, this is a nickname change and we don't care here |
|
if (!buddy_getinsideroom(room_elt->data)) { |
|
// We're trying to enter a room |
|
buddy_setnickname(room_elt->data, nickname); |
|
} |
|
|
|
// Send the XML request |
|
x = lm_message_new_presence(mystatus, roomid, mystatusmsg); |
|
xmpp_insert_entity_capabilities(x->node, mystatus); // Entity Caps (XEP-0115) |
|
y = lm_message_node_add_child(x->node, "x", NULL); |
|
lm_message_node_set_attribute(y, "xmlns", NS_MUC); |
|
if (passwd) |
|
lm_message_node_add_child(y, "password", passwd); |
|
|
|
lm_connection_send(lconnection, x, NULL); |
|
lm_message_unref(x); |
|
g_free(roomid); |
|
} |
|
|
|
// Invite a user to a MUC room |
|
// room syntax: "room@server" |
|
// reason can be null. |
|
void xmpp_room_invite(const char *room, const char *fjid, const char *reason) |
|
{ |
|
LmMessage *msg; |
|
LmMessageNode *x, *y; |
|
|
|
if (!xmpp_is_online() || !room || !fjid) |
|
return; |
|
|
|
msg = lm_message_new(room, LM_MESSAGE_TYPE_MESSAGE); |
|
|
|
x = lm_message_node_add_child(msg->node, "x", NULL); |
|
lm_message_node_set_attribute(x, "xmlns", NS_MUC_USER); |
|
|
|
y = lm_message_node_add_child(x, "invite", NULL); |
|
lm_message_node_set_attribute(y, "to", fjid); |
|
|
|
if (reason) |
|
lm_message_node_add_child(y, "reason", reason); |
|
|
|
lm_connection_send(lconnection, msg, NULL); |
|
lm_message_unref(msg); |
|
} |
|
|
|
int xmpp_room_setattrib(const char *roomid, const char *fjid, |
|
const char *nick, struct role_affil ra, |
|
const char *reason) |
|
{ |
|
LmMessage *iq; |
|
LmMessageHandler *handler; |
|
LmMessageNode *query, *x; |
|
|
|
if (!xmpp_is_online() || !roomid) |
|
return 1; |
|
if (!fjid && !nick) return 1; |
|
|
|
if (check_jid_syntax((char*)roomid)) { |
|
scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", roomid); |
|
return 1; |
|
} |
|
if (fjid && check_jid_syntax((char*)fjid)) { |
|
scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", fjid); |
|
return 1; |
|
} |
|
|
|
if (ra.type == type_affil && ra.val.affil == affil_outcast && !fjid) |
|
return 1; // Shouldn't happen (jid mandatory when banning) |
|
|
|
iq = lm_message_new_with_sub_type(roomid, LM_MESSAGE_TYPE_IQ, |
|
LM_MESSAGE_SUB_TYPE_SET); |
|
query = lm_message_node_add_child(iq->node, "query", NULL); |
|
lm_message_node_set_attribute(query, "xmlns", NS_MUC_ADMIN); |
|
x = lm_message_node_add_child(query, "item", NULL); |
|
|
|
if (fjid) { |
|
lm_message_node_set_attribute(x, "jid", fjid); |
|
} else { // nickname |
|
lm_message_node_set_attribute(x, "nick", nick); |
|
} |
|
|
|
if (ra.type == type_affil) |
|
lm_message_node_set_attribute(x, "affiliation", straffil[ra.val.affil]); |
|
else if (ra.type == type_role) |
|
lm_message_node_set_attribute(x, "role", strrole[ra.val.role]); |
|
|
|
if (reason) |
|
lm_message_node_add_child(x, "reason", reason); |
|
|
|
handler = lm_message_handler_new(handle_iq_dummy, NULL, FALSE); |
|
lm_connection_send_with_reply(lconnection, iq, handler, NULL); |
|
lm_message_handler_unref(handler); |
|
lm_message_unref(iq); |
|
|
|
return 0; |
|
} |
|
|
|
// Unlock a MUC room |
|
// room syntax: "room@server" |
|
void xmpp_room_unlock(const char *room) |
|
{ |
|
LmMessageNode *node; |
|
LmMessageHandler *handler; |
|
LmMessage *iq; |
|
|
|
if (!xmpp_is_online() || !room) |
|
return; |
|
|
|
iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ, |
|
LM_MESSAGE_SUB_TYPE_SET); |
|
|
|
node = lm_message_node_add_child(iq->node, "query", NULL); |
|
lm_message_node_set_attribute(node, "xmlns", NS_MUC_OWNER); |
|
node = lm_message_node_add_child(node, "x", NULL); |
|
lm_message_node_set_attributes(node, "xmlns", "jabber:x:data", |
|
"type", "submit", NULL); |
|
|
|
handler = lm_message_handler_new(handle_iq_dummy, NULL, FALSE); |
|
lm_connection_send_with_reply(lconnection, iq, handler, NULL); |
|
lm_message_handler_unref(handler); |
|
lm_message_unref(iq); |
|
} |
|
|
|
// Destroy a MUC room |
|
// room syntax: "room@server" |
|
void xmpp_room_destroy(const char *room, const char *venue, const char *reason) |
|
{ |
|
LmMessage *iq; |
|
LmMessageHandler *handler; |
|
LmMessageNode *query, *x; |
|
|
|
if (!xmpp_is_online() || !room) |
|
return; |
|
|
|
iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ, |
|
LM_MESSAGE_SUB_TYPE_SET); |
|
query = lm_message_node_add_child(iq->node, "query", NULL); |
|
lm_message_node_set_attribute(query, "xmlns", NS_MUC_OWNER); |
|
x = lm_message_node_add_child(query, "destroy", NULL); |
|
|
|
if (venue && *venue) |
|
lm_message_node_set_attribute(x, "jid", venue); |
|
|
|
if (reason) |
|
lm_message_node_add_child(x, "reason", reason); |
|
|
|
handler = lm_message_handler_new(handle_iq_dummy, NULL, FALSE); |
|
lm_connection_send_with_reply(lconnection, iq, handler, NULL); |
|
lm_message_handler_unref(handler); |
|
lm_message_unref(iq); |
|
} |
|
|
|
// muc_get_item_info(...) |
|
// Get room member's information from xmldata. |
|
// The variables must be initialized before calling this function, |
|
// because they are not touched if the relevant information is missing. |
|
// Note that *actor should be freed by the caller. |
|
static void muc_get_item_info(const char *from, LmMessageNode *xmldata, |
|
enum imrole *mbrole, enum imaffiliation *mbaffil, |
|
const char **mbjid, const char **mbnick, |
|
char **actor, const char **reason) |
|
{ |
|
LmMessageNode *y, *z; |
|
const char *p, *actorjid, *actornick; |
|
|
|
y = lm_message_node_get_child(xmldata, "item"); |
|
if (!y) |
|
return; |
|
|
|
p = lm_message_node_get_attribute(y, "affiliation"); |
|
if (p) { |
|
if (!strcmp(p, "owner")) *mbaffil = affil_owner; |
|
else if (!strcmp(p, "admin")) *mbaffil = affil_admin; |
|
else if (!strcmp(p, "member")) *mbaffil = affil_member; |
|
else if (!strcmp(p, "outcast")) *mbaffil = affil_outcast; |
|
else if (!strcmp(p, "none")) *mbaffil = affil_none; |
|
else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown affiliation \"%s\"", |
|
from, p); |
|
} |
|
p = lm_message_node_get_attribute(y, "role"); |
|
if (p) { |
|
if (!strcmp(p, "moderator")) *mbrole = role_moderator; |
|
else if (!strcmp(p, "participant")) *mbrole = role_participant; |
|
else if (!strcmp(p, "visitor")) *mbrole = role_visitor; |
|
else if (!strcmp(p, "none")) *mbrole = role_none; |
|
else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown role \"%s\"", |
|
from, p); |
|
} |
|
*mbjid = lm_message_node_get_attribute(y, "jid"); |
|
*mbnick = lm_message_node_get_attribute(y, "nick"); |
|
// For kick/ban, there can be actor and reason tags |
|
z = lm_message_node_get_child(y, "actor"); |
|
if (z) { |
|
actornick = lm_message_node_get_attribute(z, "nick"); |
|
actorjid = lm_message_node_get_attribute(z, "jid"); |
|
if (actorjid) { |
|
if (actornick) { |
|
// We have both the actor's jid and nick |
|
*actor = g_strdup_printf("%s <%s>", actornick, actorjid); |
|
} else { |
|
*actor = g_strdup(actorjid); // jid only |
|
} |
|
} else if (!actorjid && actornick) { |
|
// We only have the nickname |
|
*actor = g_strdup(actornick); |
|
} |
|
} |
|
|
|
*reason = lm_message_node_get_child_value(y, "reason"); |
|
if (*reason && !**reason) |
|
*reason = NULL; |
|
} |
|
|
|
// muc_handle_join(...) |
|
// Handle a join event in a MUC room. |
|
// This function will return the new_member value TRUE if somebody else joins |
|
// the room (and FALSE if _we_ are joining the room). |
|
static bool muc_handle_join(const GSList *room_elt, const char *rname, |
|
const char *roomjid, const char *ournick, |
|
enum room_printstatus printstatus, |
|
time_t usttime, int log_muc_conf, |
|
enum room_autowhois autowhois, const char *mbjid) |
|
{ |
|
bool new_member = FALSE; // True if somebody else joins the room (not us) |
|
gchar *nickjid; |
|
gchar *mbuf; |
|
enum room_flagjoins flagjoins; |
|
char *tmp = NULL; |
|
int printjid; |
|
|
|
printjid = settings_opt_get_int("muc_print_jid"); |
|
if (mbjid && autowhois == autowhois_off && printjid) { |
|
if (printjid == 1) // print nick + barejid |
|
tmp = jidtodisp(mbjid); |
|
if (printjid == 2) // print nick + full jid |
|
tmp = g_strdup(mbjid); |
|
nickjid = g_strdup_printf("%s <%s>", rname, tmp); |
|
g_free(tmp); |
|
} else { |
|
nickjid = g_strdup(rname); |
|
} |
|
|
|
if (!buddy_getinsideroom(room_elt->data)) { |
|
// We weren't inside the room yet. Now we are. |
|
// However, this could be a presence packet from another room member |
|
|
|
buddy_setinsideroom(room_elt->data, TRUE); |
|
// Set the message flag unless we're already in the room buffer window |
|
scr_setmsgflag_if_needed(roomjid, FALSE); |
|
// Add a message to the tracelog file |
|
mbuf = g_strdup_printf("You have joined %s as \"%s\"", roomjid, ournick); |
|
scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); |
|
g_free(mbuf); |
|
mbuf = g_strdup_printf("You have joined as \"%s\"", ournick); |
|
|
|
// The 1st presence message could be for another room member |
|
if (strcmp(ournick, rname)) { |
|
// Display current mbuf and create a new message for the member |
|
// Note: the usttime timestamp is related to the other member, |
|
// so we use 0 here. |
|
scr_WriteIncomingMessage(roomjid, mbuf, 0, |
|
HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); |
|
if (log_muc_conf) |
|
hlog_write_message(roomjid, 0, -1, mbuf); |
|
g_free(mbuf); |
|
if (printstatus != status_none) |
|
mbuf = g_strdup_printf("%s has joined", nickjid); |
|
else |
|
mbuf = NULL; |
|
new_member = TRUE; |
|
} |
|
} else { |
|
mbuf = NULL; |
|
if (strcmp(ournick, rname)) { |
|
if (printstatus != status_none) |
|
mbuf = g_strdup_printf("%s has joined", nickjid); |
|
new_member = TRUE; |
|
} |
|
} |
|
|
|
g_free(nickjid); |
|
|
|
if (mbuf) { |
|
guint msgflags = HBB_PREFIX_INFO; |
|
flagjoins = buddy_getflagjoins(room_elt->data); |
|
if (flagjoins == flagjoins_default && |
|
!settings_opt_get_int("muc_flag_joins")) |
|
flagjoins = flagjoins_none; |
|
if (flagjoins == flagjoins_none) |
|
msgflags |= HBB_PREFIX_NOFLAG; |
|
scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0); |
|
if (log_muc_conf) |
|
hlog_write_message(roomjid, 0, -1, mbuf); |
|
g_free(mbuf); |
|
} |
|
|
|
return new_member; |
|
} |
|
|
|
void handle_muc_presence(const char *from, LmMessageNode *xmldata, |
|
const char *roomjid, const char *rname, |
|
enum imstatus ust, const char *ustmsg, |
|
time_t usttime, char bpprio) |
|
{ |
|
char *mbuf; |
|
const char *ournick; |
|
enum imrole mbrole = role_none; |
|
enum imaffiliation mbaffil = affil_none; |
|
enum room_printstatus printstatus; |
|
enum room_autowhois autowhois; |
|
enum room_flagjoins flagjoins; |
|
const char *mbjid = NULL, *mbnick = NULL; |
|
const char *reason = NULL; |
|
char *actor = NULL; |
|
bool new_member = FALSE; // True if somebody else joins the room (not us) |
|
bool our_presence = FALSE; // True if this presence is from us (i.e. bears |
|
// code 110) |
|
guint statuscode = 0; |
|
guint nickchange = 0; |
|
GSList *room_elt; |
|
int log_muc_conf; |
|
guint msgflags; |
|
|
|
log_muc_conf = settings_opt_get_int("log_muc_conf"); |
|
|
|
room_elt = roster_find(roomjid, jidsearch, 0); |
|
if (!room_elt) { |
|
// Add room if it doesn't already exist |
|
// It shouldn't happen, there is probably something wrong (server or |
|
// network issue?) |
|
room_elt = roster_add_user(roomjid, NULL, NULL, ROSTER_TYPE_ROOM, |
|
sub_none, -1); |
|
scr_LogPrint(LPRINT_LOGNORM, "Strange MUC presence message"); |
|
} else { |
|
// Make sure this is a room (it can be a conversion user->room) |
|
buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); |
|
} |
|
|
|
// We're not interested in presence from the bare room JID (used for MUC |
|
// avatars, for example) |
|
if (!rname || rname[0] == '\0') |
|
return; |
|
|
|
// Get room member's information |
|
muc_get_item_info(from, xmldata, &mbrole, &mbaffil, &mbjid, &mbnick, |
|
&actor, &reason); |
|
|
|
// Get our room nickname |
|
ournick = buddy_getnickname(room_elt->data); |
|
|
|
if (!ournick) { |
|
// It shouldn't happen, probably a server issue |
|
const gchar msg[] = "Unexpected groupchat packet!"; |
|
scr_LogPrint(LPRINT_LOGNORM, msg); |
|
scr_WriteIncomingMessage(roomjid, msg, 0, HBB_PREFIX_INFO, 0); |
|
// Send back an unavailable packet |
|
xmpp_setstatus(offline, roomjid, "", TRUE); |
|
scr_update_roster(); |
|
return; |
|
} |
|
|
|
#define SETSTATUSCODE(VALUE) \ |
|
{ \ |
|
if (G_UNLIKELY(statuscode)) \ |
|
scr_LogPrint(LPRINT_DEBUG, "handle_muc_presence: WARNING: " \ |
|
"replacing status code %u with %u.", statuscode, VALUE); \ |
|
statuscode = VALUE; \ |
|
} |
|
|
|
{ // Get the status code |
|
LmMessageNode *node; |
|
for (node = xmldata -> children; node; node = node -> next) { |
|
if (!g_strcmp0(node -> name, "status")) { |
|
const char *codestr = lm_message_node_get_attribute(node, "code"); |
|
if (codestr) { |
|
const char *mesg = NULL; |
|
switch (atoi(codestr)) { |
|
// initial |
|
case 100: |
|
mesg = "The room is not anonymous."; |
|
break; |
|
case 110: // It is our presence |
|
our_presence = TRUE; |
|
break; |
|
// initial |
|
case 170: |
|
mesg = "The room is logged."; |
|
break; |
|
// initial |
|
case 201: // Room created |
|
SETSTATUSCODE(201); |
|
break; |
|
// initial |
|
case 210: // Your nick change (on join) |
|
// FIXME: print nick |
|
mesg = "The room has changed your nick!"; |
|
buddy_setnickname(room_elt->data, rname); |
|
ournick = rname; |
|
break; |
|
case 301: // User banned |
|
SETSTATUSCODE(301); |
|
break; |
|
case 303: // Nick change |
|
SETSTATUSCODE(303); |
|
break; |
|
case 307: // User kicked |
|
SETSTATUSCODE(307); |
|
break; |
|
// XXX (next three) |
|
case 321: |
|
mesg = "User leaves room due to affilation change."; |
|
break; |
|
case 322: |
|
mesg = "User leaves room, as room is only for members now."; |
|
break; |
|
case 332: |
|
mesg = "User leaves room due to system shutdown."; |
|
break; |
|
default: |
|
scr_LogPrint(LPRINT_DEBUG, |
|
"handle_muc_presence: Unknown MUC status code: %s.", |
|
codestr); |
|
break; |
|
} |
|
if (mesg) { |
|
scr_WriteIncomingMessage(roomjid, mesg, usttime, |
|
HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); |
|
if (log_muc_conf) |
|
hlog_write_message(roomjid, 0, -1, mesg); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
#undef SETSTATUSCODE |
|
|
|
if (!our_presence) |
|
if (ournick && !strcmp(ournick, rname)) |
|
our_presence = TRUE; |
|
|
|
// Get the room's "print_status" settings |
|
printstatus = buddy_getprintstatus(room_elt->data); |
|
if (printstatus == status_default) { |
|
printstatus = (guint) settings_opt_get_int("muc_print_status"); |
|
if (printstatus > 3) |
|
printstatus = status_default; |
|
} |
|
|
|
// A new room has been created; accept MUC default config |
|
if (statuscode == 201) |
|
xmpp_room_unlock(roomjid); |
|
|
|
// Check for nickname change |
|
if (statuscode == 303 && mbnick) { |
|
mbuf = g_strdup_printf("%s is now known as %s", rname, mbnick); |
|
scr_WriteIncomingMessage(roomjid, mbuf, usttime, |
|
HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); |
|
if (log_muc_conf) |
|
hlog_write_message(roomjid, 0, -1, mbuf); |
|
g_free(mbuf); |
|
buddy_resource_setname(room_elt->data, rname, mbnick); |
|
// Maybe it's _our_ nickname... |
|
if (our_presence) |
|
buddy_setnickname(room_elt->data, mbnick); |
|
nickchange = TRUE; |
|
} |
|
|
|
autowhois = buddy_getautowhois(room_elt->data); |
|
if (autowhois == autowhois_default) |
|
autowhois = (settings_opt_get_int("muc_auto_whois") ? |
|
autowhois_on : autowhois_off); |
|
|
|
// Check for departure/arrival |
|
if (statuscode != 303 && ust == offline) { |
|
// Somebody is leaving |
|
enum { leave=0, kick, ban } how = leave; |
|
|
|
if (statuscode == 307) |
|
how = kick; |
|
else if (statuscode == 301) |
|
how = ban; |
|
|
|
// If this is a leave, check if it is ourself |
|
if (our_presence) { |
|
buddy_setinsideroom(room_elt->data, FALSE); |
|
buddy_setnickname(room_elt->data, NULL); |
|
buddy_del_all_resources(room_elt->data); |
|
buddy_settopic(room_elt->data, NULL); |
|
scr_update_chat_status(FALSE); |
|
scr_update_roster(); |
|
} |
|
|
|
// The message depends on _who_ left, and _how_ |
|
if (how) { |
|
gchar *mbuf_end; |
|
gchar *reason_msg = NULL; |
|
// Forced leave |
|
if (actor) { |
|
mbuf_end = g_strdup_printf("%s from %s by %s", |
|
(how == ban ? "banned" : "kicked"), |
|
roomjid, actor); |
|
} else { |
|
mbuf_end = g_strdup_printf("%s from %s", |
|
(how == ban ? "banned" : "kicked"), |
|
roomjid); |
|
} |
|
if (reason) |
|
reason_msg = g_strdup_printf("\nReason: %s", reason); |
|
if (our_presence) |
|
mbuf = g_strdup_printf("You have been %s%s", mbuf_end, |
|
reason_msg ? reason_msg : ""); |
|
else |
|
mbuf = g_strdup_printf("%s has been %s%s", rname, mbuf_end, |
|
reason_msg ? reason_msg : ""); |
|
|
|
g_free(reason_msg); |
|
g_free(mbuf_end); |
|
} else { |
|
// Natural leave |
|
if (our_presence) { |
|
LmMessageNode *destroynode = lm_message_node_get_child(xmldata, |
|
"destroy"); |
|
if (destroynode) { |
|
reason = lm_message_node_get_child_value(destroynode, "reason"); |
|
if (reason && *reason) { |
|
mbuf = g_strdup_printf("You have left %s, " |
|
"the room has been destroyed: %s", |
|
roomjid, reason); |
|
} else { |
|
mbuf = g_strdup_printf("You have left %s, " |
|
"the room has been destroyed", roomjid); |
|
} |
|
} else { |
|
mbuf = g_strdup_printf("You have left %s", roomjid); |
|
} |
|
} else { |
|
if (ust != offline) { |
|
// This can happen when a network failure occurs, |
|
// this isn't an official leave but the user isn't there anymore. |
|
mbuf = g_strdup_printf("%s has disappeared!", rname); |
|
ust = offline; |
|
} else { |
|
if (ustmsg) |
|
mbuf = g_strdup_printf("%s has left: %s", rname, ustmsg); |
|
else |
|
mbuf = g_strdup_printf("%s has left", rname); |
|
} |
|
} |
|
} |
|
|
|
g_free(actor); |
|
|
|
// Display the mbuf message if we're concerned |
|
// or if the print_status isn't set to none. |
|
if (our_presence || printstatus != status_none) { |
|
msgflags = HBB_PREFIX_INFO; |
|
flagjoins = buddy_getflagjoins(room_elt->data); |
|
if (flagjoins == flagjoins_default && |
|
settings_opt_get_int("muc_flag_joins") == 2) |
|
flagjoins = flagjoins_all; |
|
if (!our_presence && flagjoins != flagjoins_all) |
|
msgflags |= HBB_PREFIX_NOFLAG; |
|
//silent message if someone else joins, and we care about noone |
|
scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0); |
|
} |
|
|
|
if (log_muc_conf) |
|
hlog_write_message(roomjid, 0, -1, mbuf); |
|
|
|
if (our_presence) { |
|
scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); |
|
g_free(mbuf); |
|
return; |
|
} |
|
g_free(mbuf); |
|
} else { |
|
enum imstatus old_ust = buddy_getstatus(room_elt->data, rname); |
|
if (old_ust == offline && ust != offline) { |
|
// Somebody is joining |
|
new_member = muc_handle_join(room_elt, rname, roomjid, ournick, |
|
printstatus, usttime, log_muc_conf, |
|
autowhois, mbjid); |
|
} else { |
|
// This is a simple member status change |
|
|
|
if (printstatus == status_all && !nickchange) { |
|
const char *old_ustmsg = buddy_getstatusmsg(room_elt->data, rname); |
|
if (old_ust != ust || g_strcmp0(old_ustmsg, ustmsg)) { |
|
mbuf = g_strdup_printf("%s [%c>%c] %s", rname, imstatus2char[old_ust], |
|
imstatus2char[ust], ((ustmsg) ? ustmsg : "")); |
|
scr_WriteIncomingMessage(roomjid, mbuf, usttime, |
|
HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); |
|
g_free(mbuf); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Update room member status |
|
roster_setstatus(roomjid, rname, bpprio, ust, ustmsg, usttime, |
|
mbrole, mbaffil, mbjid); |
|
|
|
if (new_member && autowhois == autowhois_on) { |
|
cmd_room_whois(room_elt->data, rname, FALSE); |
|
} |
|
|
|
scr_update_roster(); |
|
} |
|
|
|
void roompresence(gpointer room, void *presencedata) |
|
{ |
|
const char *bjid; |
|
const char *nickname; |
|
char *to; |
|
struct T_presence *pres = presencedata; |
|
|
|
if (!buddy_getinsideroom(room)) |
|
return; |
|
|
|
bjid = buddy_getjid(room); |
|
if (!bjid) return; |
|
nickname = buddy_getnickname(room); |
|
if (!nickname) return; |
|
|
|
to = g_strdup_printf("%s/%s", bjid, nickname); |
|
xmpp_setstatus(pres->st, to, pres->msg, TRUE); |
|
g_free(to); |
|
} |
|
|
|
// got_invite(from, to, reason, passwd, reply) |
|
// This function should be called when receiving an invitation from user |
|
// "from", to enter the room "to". Optional reason and room password can |
|
// be provided. |
|
void got_invite(const char* from, const char *to, const char* reason, |
|
const char* passwd, gboolean reply) |
|
{ |
|
GString *sbuf; |
|
char *barejid; |
|
GSList *room_elt; |
|
|
|
sbuf = g_string_new(""); |
|
if (reason && reason[0]) { |
|
g_string_printf(sbuf, |
|
"Received an invitation to <%s>, from <%s>, reason: %s", |
|
to, from, reason); |
|
} else { |
|
g_string_printf(sbuf, "Received an invitation to <%s>, from <%s>", |
|
to, from); |
|
} |
|
|
|
barejid = jidtodisp(from); |
|
scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0); |
|
scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str); |
|
|
|
{ // remove any equal older invites |
|
GSList *iel = invitations; |
|
while (iel) { |
|
event_muc_invitation_t *invitation = iel->data; |
|
iel = iel -> next; |
|
if (!g_strcmp0(to, invitation->to) && |
|
!g_strcmp0(passwd, invitation->passwd)) { |
|
// found a previous invitation |
|
// We keep the old one, unless the current one is better and allows us |
|
// to send a reply. |
|
if (!reply || invitation->reply) { |
|
g_free(barejid); |
|
return; |
|
} |
|
scr_LogPrint(LPRINT_DEBUG, "Destroying previous invitation event %s.", |
|
invitation->evid); |
|
evs_del(invitation->evid); |
|
} |
|
} |
|
} |
|
|
|
{ // create event |
|
const char *id; |
|
char *desc = g_strdup_printf("<%s> invites you to %s", from, to); |
|
event_muc_invitation_t *invitation; |
|
|
|
invitation = g_new(event_muc_invitation_t, 1); |
|
invitation->to = g_strdup(to); |
|
invitation->from = g_strdup(from); |
|
invitation->passwd = g_strdup(passwd); |
|
invitation->reason = g_strdup(reason); |
|
invitation->reply = reply; |
|
invitation->evid = NULL; |
|
|
|
invitations = g_slist_append(invitations, invitation); |
|
|
|
id = evs_new(desc, NULL, 0, evscallback_invitation, invitation, |
|
(GDestroyNotify)destroy_event_muc_invitation); |
|
g_free(desc); |
|
if (id) { |
|
invitation->evid = g_strdup(id); |
|
g_string_printf(sbuf, "Please use /event %s accept|reject", id); |
|
} else |
|
g_string_printf(sbuf, "Unable to create a new event!"); |
|
} |
|
scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0); |
|
scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str); |
|
g_string_free(sbuf, TRUE); |
|
g_free(barejid); |
|
|
|
// Make sure the MUC room barejid is a room in the roster |
|
barejid = jidtodisp(to); |
|
room_elt = roster_find(barejid, jidsearch, 0); |
|
if (room_elt) |
|
buddy_settype(room_elt->data, ROSTER_TYPE_ROOM); |
|
|
|
g_free(barejid); |
|
} |
|
|
|
|
|
// Specific MUC message handling (for example invitation processing) |
|
void got_muc_message(const char *from, LmMessageNode *x, time_t timestamp) |
|
{ |
|
LmMessageNode *node; |
|
// invitation |
|
node = lm_message_node_get_child(x, "invite"); |
|
if (node) { |
|
const char *invite_from; |
|
const char *reason = NULL; |
|
const char *password = NULL; |
|
|
|
invite_from = lm_message_node_get_attribute(node, "from"); |
|
reason = lm_message_node_get_child_value(node, "reason"); |
|
password = lm_message_node_get_child_value(node, "password"); |
|
if (invite_from) |
|
got_invite(invite_from, from, reason, password, TRUE); |
|
} |
|
|
|
// declined invitation |
|
node = lm_message_node_get_child(x, "decline"); |
|
if (node) { |
|
const char *decline_from = lm_message_node_get_attribute(node, "from"); |
|
const char *reason = lm_message_node_get_child_value(node, "reason"); |
|
if (decline_from) { |
|
if (reason && reason[0]) |
|
scr_LogPrint(LPRINT_LOGNORM, "<%s> declined your invitation: %s.", |
|
from, reason); |
|
else |
|
scr_LogPrint(LPRINT_LOGNORM, "<%s> declined your invitation.", from); |
|
} |
|
} |
|
|
|
// status codes |
|
for (node = x -> children; node; node = node -> next) { |
|
if (!g_strcmp0(node -> name, "status")) { |
|
const char *codestr = lm_message_node_get_attribute(node, "code"); |
|
if (codestr) { |
|
const char *mesg = NULL; |
|
switch (atoi(codestr)) { |
|
// initial |
|
case 100: |
|
mesg = "The room is not anonymous."; |
|
break; |
|
case 101: |
|
mesg = "Your affilation has changed while absent."; |
|
break; |
|
case 102: |
|
mesg = "The room shows unavailable members."; |
|
break; |
|
case 103: |
|
mesg = "The room does not show unavailable members."; |
|
break; |
|
case 104: |
|
mesg = "The room configuration has changed."; |
|
break; |
|
case 170: |
|
mesg = "The room is logged."; |
|
break; |
|
case 171: |
|
mesg = "The room is not logged."; |
|
break; |
|
case 172: |
|
mesg = "The room is not anonymous."; |
|
break; |
|
case 173: |
|
mesg = "The room is semi-anonymous."; |
|
break; |
|
case 174: |
|
mesg = "The room is anonymous."; |
|
break; |
|
default: |
|
scr_LogPrint(LPRINT_DEBUG, |
|
"got_muc_message: Unknown MUC status code: %s.", |
|
codestr); |
|
break; |
|
} |
|
if (mesg) { |
|
scr_WriteIncomingMessage(from, mesg, timestamp, |
|
HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); |
|
if (settings_opt_get_int("log_muc_conf")) |
|
hlog_write_message(from, 0, -1, mesg); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2: For Vim users... */
|
|
|