Mercurial > hg
diff mcabber/src/xmpp_muc.c @ 1598:a087125d8fc8
Replace libjabber with loudmouth
author | franky |
---|---|
date | Sun, 11 Oct 2009 15:38:32 +0200 |
parents | mcabber/src/jabglue.c@1802b926e3fa |
children | dcd5d4c75199 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mcabber/src/xmpp_muc.c Sun Oct 11 15:38:32 2009 +0200 @@ -0,0 +1,678 @@ +/* See xmpp.c file for copyright and license details. */ + +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; +} + +static 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(); +} + +static 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) +static 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 +} +