Mercurial > ~mikael > mcabber > hg
view mcabber/src/xmpp_muc.c @ 1610:6db9f403f707
Replace 'username' with 'jid' in the configuration file
The previous behaviour doesn't make much sense anymore.
MCabber does DNS SRV lookups so providing the server name
is usually not needed.
author | Mikael Berthe <mikael@lilotux.net> |
---|---|
date | Sun, 11 Oct 2009 20:06:47 +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... */