Mercurial > hg
view mcabber/src/xmpp_muc.c @ 1617:9ca672ee884f
Fix previous commit :)
author | Mikael Berthe <mikael@lilotux.net> |
---|---|
date | Sun, 11 Oct 2009 22:47:12 +0200 |
parents | 351427ef0b4b |
children | 2f6bdfa0cb01 |
line wrap: on
line source
/* * xmpp_muc.c -- Jabber MUC protocol handling * * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de> * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #include <string.h> #include <stdlib.h> #include "xmpp_helper.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 void decline_invitation(event_muc_invitation *invitation, 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", "http://jabber.org/protocol/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); } static int evscallback_invitation(eviqs *evp, guint evcontext) { event_muc_invitation *invitation = evp->data; // Sanity check if (!invitation) { // Shouldn't happen. scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback."); return 0; } if (evcontext == EVS_CONTEXT_TIMEOUT) { scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.", evp->id); goto evscallback_invitation_free; } if (evcontext == EVS_CONTEXT_CANCEL) { scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id); goto evscallback_invitation_free; } if (!(evcontext & EVS_CONTEXT_USER)) goto evscallback_invitation_free; // Ok, let's work now. // evcontext: 0, 1 == reject, accept if (evcontext & ~EVS_CONTEXT_USER) { 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); decline_invitation(invitation, NULL); } evscallback_invitation_free: g_free(invitation->to); g_free(invitation->from); g_free(invitation->passwd); g_free(invitation->reason); g_free(invitation); evp->data = NULL; return 0; } // 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 (!lm_connection_is_authenticated(lconnection) || !room) return; if (!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(roomid, LM_MESSAGE_TYPE_PRESENCE); x = lm_message_new_presence(mystatus, roomid, mystatusmsg); y = lm_message_node_add_child(x->node, "x", NULL); lm_message_node_set_attribute(y, "xmlns", "http://jabber.org/protocol/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 (!lm_connection_is_authenticated(lconnection) || !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", "http://jabber.org/protocol/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; LmMessageNode *query, *x; if (!lm_connection_is_authenticated(lconnection) || !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", "http://jabber.org/protocol/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); lm_connection_send(lconnection, iq, NULL); lm_message_unref(iq); return 0; } // Unlock a MUC room // room syntax: "room@server" void xmpp_room_unlock(const char *room) { LmMessageNode *y, *z; LmMessage *iq; if (!lm_connection_is_authenticated(lconnection) || !room) return; iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_SET); lm_message_node_set_attribute(iq->node, "xmlns", "http://jabber.org/protocol/muc#owner"); y = lm_message_node_add_child(iq->node, "query", NULL); z = lm_message_node_add_child(y, "x", NULL); lm_message_node_set_attribute(z, "xmlns", "jabber:x:data"); lm_message_node_set_attribute(z, "type", "submit"); lm_connection_send(lconnection, iq, NULL); 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; LmMessageNode *query, *x; if (!lm_connection_is_authenticated(lconnection) || !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", "http://jabber.org/protocol/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); lm_connection_send(lconnection, iq, NULL); lm_message_unref(iq); } // muc_get_item_info(...) // Get room member's information from xmlndata. // The variables must be initialized before calling this function, // because they are not touched if the relevant information is missing. static void muc_get_item_info(const char *from, LmMessageNode *xmldata, enum imrole *mbrole, enum imaffiliation *mbaffil, const char **mbjid, const char **mbnick, const char **actorjid, const char **reason) { LmMessageNode *y, *z; const char *p; y = lm_message_node_find_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 *reason = lm_message_node_get_child_value(y, "reason"); z = lm_message_node_find_child(y, "actor"); if (z) *actorjid = lm_message_node_get_attribute(z, "jid"); } // 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) { bool new_member = FALSE; // True if somebody else joins the room (not us) gchar *mbuf; 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", rname); else mbuf = NULL; new_member = TRUE; } } else { mbuf = NULL; if (strcmp(ournick, rname)) { if (printstatus != status_none) mbuf = g_strdup_printf("%s has joined", rname); new_member = TRUE; } } if (mbuf) { guint msgflags = HBB_PREFIX_INFO; if (!settings_opt_get_int("muc_flag_joins")) 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) { LmMessageNode *y; const char *p; char *mbuf; const char *ournick; enum imrole mbrole = role_none; enum imaffiliation mbaffil = affil_none; enum room_printstatus printstatus; enum room_autowhois autowhois; const char *mbjid = NULL, *mbnick = NULL; const char *actorjid = NULL, *reason = NULL; bool new_member = FALSE; // True if somebody else joins the room (not us) 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); } // Get room member's information muc_get_item_info(from, xmldata, &mbrole, &mbaffil, &mbjid, &mbnick, &actorjid, &reason); // Get our room nickname ournick = buddy_getnickname(room_elt->data); if (!ournick) { // It shouldn't happen, probably a server issue mbuf = g_strdup_printf("Unexpected groupchat packet!"); scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); scr_WriteIncomingMessage(roomjid, mbuf, 0, HBB_PREFIX_INFO, 0); g_free(mbuf); // Send back an unavailable packet xmpp_setstatus(offline, roomjid, "", TRUE); scr_DrawRoster(); return; } // Get the status code // 201: a room has been created // 301: the user has been banned from the room // 303: new room nickname // 307: the user has been kicked from the room // 321,322,332: the user has been removed from the room y = lm_message_node_find_child(xmldata, "status"); if (y) { p = lm_message_node_get_attribute(y, "code"); if (p) statuscode = atoi(p); } // 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 (ournick && !strcmp(rname, ournick)) buddy_setnickname(room_elt->data, mbnick); nickchange = TRUE; } // Check for departure/arrival if (!mbnick && ust == offline) { // Somebody is leaving enum { leave=0, kick, ban } how = leave; bool we_left = FALSE; if (statuscode == 307) how = kick; else if (statuscode == 301) how = ban; // If this is a leave, check if it is ourself if (ournick && !strcmp(rname, ournick)) { we_left = TRUE; // _We_ have left! (kicked, banned, etc.) 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_UpdateChatStatus(FALSE); update_roster = TRUE; } // The message depends on _who_ left, and _how_ if (how) { gchar *mbuf_end; // Forced leave if (actorjid) { mbuf_end = g_strdup_printf("%s from %s by <%s>.\nReason: %s", (how == ban ? "banned" : "kicked"), roomjid, actorjid, reason); } else { mbuf_end = g_strdup_printf("%s from %s.", (how == ban ? "banned" : "kicked"), roomjid); } if (we_left) mbuf = g_strdup_printf("You have been %s", mbuf_end); else mbuf = g_strdup_printf("%s has been %s", rname, mbuf_end); g_free(mbuf_end); } else { // Natural leave if (we_left) { LmMessageNode *destroynode = lm_message_node_find_child(xmldata, "destroy"); if (destroynode) { if ((reason = lm_message_node_get_child_value(destroynode, "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); } } } // Display the mbuf message if we're concerned // or if the print_status isn't set to none. if (we_left || printstatus != status_none) { msgflags = HBB_PREFIX_INFO; if (!we_left && settings_opt_get_int("muc_flag_joins") != 2) msgflags |= HBB_PREFIX_NOFLAG; scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0); } if (log_muc_conf) hlog_write_message(roomjid, 0, -1, mbuf); if (we_left) { scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf); g_free(mbuf); return; } g_free(mbuf); } else if (buddy_getstatus(room_elt->data, rname) == offline && ust != offline) { // Somebody is joining new_member = muc_handle_join(room_elt, rname, roomjid, ournick, printstatus, usttime, log_muc_conf); } else { // This is a simple member status change if (printstatus == status_all && !nickchange) { mbuf = g_strdup_printf("Member status has changed: %s [%c] %s", rname, imstatus2char[ust], ((ustmsg) ? ustmsg : "")); scr_WriteIncomingMessage(roomjid, mbuf, usttime, HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0); g_free(mbuf); } } // Sanity check, shouldn't happen... if (!rname) return; // Update room member status roster_setstatus(roomjid, rname, bpprio, ust, ustmsg, usttime, mbrole, mbaffil, mbjid); autowhois = buddy_getautowhois(room_elt->data); if (autowhois == autowhois_default) autowhois = (settings_opt_get_int("muc_auto_whois") ? autowhois_on : autowhois_off); if (new_member && autowhois == autowhois_on) { // FIXME: This will fail for some UTF-8 nicknames. gchar *joiner_nick = from_utf8(rname); cmd_room_whois(room_elt->data, joiner_nick, FALSE); g_free(joiner_nick); } scr_DrawRoster(); } 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) // 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. static void got_invite(const char* from, const char *to, const char* reason, const char* passwd) { eviqs *evn; event_muc_invitation *invitation; GString *sbuf; char *barejid; GSList *room_elt; sbuf = g_string_new(""); if (reason) { 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); evn = evs_new(EVS_TYPE_INVITATION, EVS_MAX_TIMEOUT); if (evn) { evn->callback = &evscallback_invitation; invitation = g_new(event_muc_invitation, 1); invitation->to = g_strdup(to); invitation->from = g_strdup(from); invitation->passwd = g_strdup(passwd); invitation->reason = g_strdup(reason); evn->data = invitation; evn->desc = g_strdup_printf("<%s> invites you to %s ", from, to); g_string_printf(sbuf, "Please use /event %s accept|reject", evn->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) { LmMessageNode *invite = lm_message_node_get_child(x, "invite"); if (invite) { const char *invite_from; const char *reason = NULL; const char *password = NULL; invite_from = lm_message_node_get_attribute(invite, "from"); reason = lm_message_node_get_child_value(invite, "reason"); password = lm_message_node_get_child_value(invite, "password"); if (invite_from) got_invite(invite_from, from, reason, password); } // TODO // handle status code = 100 ( not anonymous ) // handle status code = 170 ( changement de config ) // 10.2.1 Notification of Configuration Changes // declined invitation } /* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */