Mercurial > ~mikael > mcabber > hg
view mcabber/mcabber/xmpp_muc.c @ 1731:4fbfae993c24
Improve login process
The roster and private storage elements are requested before broadcasting
our presence, because we want the roster before we receive our contacts
presence notifications.
This is more efficients, and also solves an issue with entity capabilities
(as we do not store caps for unknown items).
author | Mikael Berthe <mikael@lilotux.net> |
---|---|
date | Sun, 28 Feb 2010 18:50:30 +0100 |
parents | e6e89b1d7831 |
children | e4378fbab5d7 |
line wrap: on
line source
/* * xmpp_muc.c -- Jabber MUC protocol handling * * Copyright (C) 2008-2010 Frank Zschockelt <mcabber@freakysoft.de> * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net> * Copyrigth (C) 2010 Myhailo Danylenko <isbear@ukrposte.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 "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 void decline_invitation(event_muc_invitation *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", "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); } void destroy_event_muc_invitation(event_muc_invitation *invitation) { g_free(invitation->to); g_free(invitation->from); g_free(invitation->passwd); g_free(invitation->reason); 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 *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); 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) 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_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 (!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", "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 (!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", "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 *node; 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", "http://jabber.org/protocol/muc#owner"); node = lm_message_node_add_child(node, "x", NULL); lm_message_node_set_attributes(node, "xmlns", "jabber:x:data", "type", "submit", NULL); 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 (!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", "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. // TODO: check for duplicate invites (need an existing invitation registry // for that). static void got_invite(const char* from, const char *to, const char* reason, const char* passwd) { 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); { const char *id; char *desc = g_strdup_printf("<%s> invites you to %s", from, to); event_muc_invitation *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); id = evs_new(desc, NULL, 0, evscallback_invitation, invitation, (GDestroyNotify)destroy_event_muc_invitation); g_free(desc); if (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) { 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... */