2 * xmpp_muc.c -- Jabber MUC protocol handling
4 * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
5 * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or (at
10 * your option) any later version.
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
26 #include "xmpp_helper.h"
37 extern enum imstatus mystatus
;
38 extern gchar
*mystatusmsg
;
40 static void decline_invitation(event_muc_invitation
*invitation
, char *reason
)
42 // cut and paste from xmpp_room_invite
46 if (!invitation
) return;
47 if (!invitation
->to
|| !invitation
->from
) return;
49 m
= lm_message_new(invitation
->to
, LM_MESSAGE_TYPE_MESSAGE
);
51 x
= lm_message_node_add_child(m
->node
, "x", NULL
);
52 lm_message_node_set_attribute(x
, "xmlns",
53 "http://jabber.org/protocol/muc#user");
55 y
= lm_message_node_add_child(x
, "decline", NULL
);
56 lm_message_node_set_attribute(y
, "to", invitation
->from
);
59 lm_message_node_add_child(y
, "reason", reason
);
61 lm_connection_send(lconnection
, m
, NULL
);
65 static int evscallback_invitation(eviqs
*evp
, guint evcontext
)
67 event_muc_invitation
*invitation
= evp
->data
;
72 scr_LogPrint(LPRINT_LOGNORM
, "Error in evs callback.");
76 if (evcontext
== EVS_CONTEXT_TIMEOUT
) {
77 scr_LogPrint(LPRINT_LOGNORM
, "Event %s timed out, cancelled.", evp
->id
);
78 goto evscallback_invitation_free
;
80 if (evcontext
== EVS_CONTEXT_CANCEL
) {
81 scr_LogPrint(LPRINT_LOGNORM
, "Event %s cancelled.", evp
->id
);
82 goto evscallback_invitation_free
;
84 if (!(evcontext
& EVS_CONTEXT_USER
))
85 goto evscallback_invitation_free
;
86 // Ok, let's work now.
87 // evcontext: 0, 1 == reject, accept
89 if (evcontext
& ~EVS_CONTEXT_USER
) {
90 char *nickname
= default_muc_nickname(invitation
->to
);
91 xmpp_room_join(invitation
->to
, nickname
, invitation
->passwd
);
94 scr_LogPrint(LPRINT_LOGNORM
, "Invitation to %s refused.", invitation
->to
);
95 decline_invitation(invitation
, NULL
);
98 evscallback_invitation_free
:
99 g_free(invitation
->to
);
100 g_free(invitation
->from
);
101 g_free(invitation
->passwd
);
102 g_free(invitation
->reason
);
109 void xmpp_room_join(const char *room
, const char *nickname
, const char *passwd
)
116 if (!lm_connection_is_authenticated(lconnection
) || !room
) return;
117 if (!nickname
) return;
119 roomid
= g_strdup_printf("%s/%s", room
, nickname
);
120 if (check_jid_syntax(roomid
)) {
121 scr_LogPrint(LPRINT_NORMAL
, "<%s/%s> is not a valid Jabber room", room
,
127 room_elt
= roster_find(room
, jidsearch
, ROSTER_TYPE_USER
|ROSTER_TYPE_ROOM
);
128 // Add room if it doesn't already exist
130 room_elt
= roster_add_user(room
, NULL
, NULL
, ROSTER_TYPE_ROOM
,
133 // Make sure this is a room (it can be a conversion user->room)
134 buddy_settype(room_elt
->data
, ROSTER_TYPE_ROOM
);
136 // If insideroom is TRUE, this is a nickname change and we don't care here
137 if (!buddy_getinsideroom(room_elt
->data
)) {
138 // We're trying to enter a room
139 buddy_setnickname(room_elt
->data
, nickname
);
142 // Send the XML request
143 x
= lm_message_new(roomid
, LM_MESSAGE_TYPE_PRESENCE
);
145 x
= lm_message_new_presence(mystatus
, roomid
, mystatusmsg
);
146 y
= lm_message_node_add_child(x
->node
, "x", NULL
);
147 lm_message_node_set_attribute(y
, "xmlns", "http://jabber.org/protocol/muc");
149 lm_message_node_add_child(y
, "password", passwd
);
151 lm_connection_send(lconnection
, x
, NULL
);
156 // Invite a user to a MUC room
157 // room syntax: "room@server"
158 // reason can be null.
159 void xmpp_room_invite(const char *room
, const char *fjid
, const char *reason
)
162 LmMessageNode
*x
, *y
;
164 if (!lm_connection_is_authenticated(lconnection
) || !room
|| !fjid
) return;
166 msg
= lm_message_new(room
, LM_MESSAGE_TYPE_MESSAGE
);
168 x
= lm_message_node_add_child(msg
->node
, "x", NULL
);
169 lm_message_node_set_attribute(x
, "xmlns",
170 "http://jabber.org/protocol/muc#user");
172 y
= lm_message_node_add_child(x
, "invite", NULL
);
173 lm_message_node_set_attribute(y
, "to", fjid
);
176 lm_message_node_add_child(y
, "reason", reason
);
178 lm_connection_send(lconnection
, msg
, NULL
);
179 lm_message_unref(msg
);
182 int xmpp_room_setattrib(const char *roomid
, const char *fjid
,
183 const char *nick
, struct role_affil ra
,
187 LmMessageNode
*query
, *x
;
189 if (!lm_connection_is_authenticated(lconnection
) || !roomid
) return 1;
190 if (!fjid
&& !nick
) return 1;
192 if (check_jid_syntax((char*)roomid
)) {
193 scr_LogPrint(LPRINT_NORMAL
, "<%s> is not a valid Jabber id", roomid
);
196 if (fjid
&& check_jid_syntax((char*)fjid
)) {
197 scr_LogPrint(LPRINT_NORMAL
, "<%s> is not a valid Jabber id", fjid
);
201 if (ra
.type
== type_affil
&& ra
.val
.affil
== affil_outcast
&& !fjid
)
202 return 1; // Shouldn't happen (jid mandatory when banning)
204 iq
= lm_message_new_with_sub_type(roomid
, LM_MESSAGE_TYPE_IQ
,
205 LM_MESSAGE_SUB_TYPE_SET
);
206 query
= lm_message_node_add_child(iq
->node
, "query", NULL
);
207 lm_message_node_set_attribute(query
, "xmlns",
208 "http://jabber.org/protocol/muc#admin");
209 x
= lm_message_node_add_child(query
, "item", NULL
);
212 lm_message_node_set_attribute(x
, "jid", fjid
);
214 lm_message_node_set_attribute(x
, "nick", nick
);
217 if (ra
.type
== type_affil
)
218 lm_message_node_set_attribute(x
, "affiliation", straffil
[ra
.val
.affil
]);
219 else if (ra
.type
== type_role
)
220 lm_message_node_set_attribute(x
, "role", strrole
[ra
.val
.role
]);
223 lm_message_node_add_child(x
, "reason", reason
);
225 lm_connection_send(lconnection
, iq
, NULL
);
226 lm_message_unref(iq
);
232 // room syntax: "room@server"
233 void xmpp_room_unlock(const char *room
)
235 LmMessageNode
*y
, *z
;
238 if (!lm_connection_is_authenticated(lconnection
) || !room
) return;
240 iq
= lm_message_new_with_sub_type(room
, LM_MESSAGE_TYPE_IQ
,
241 LM_MESSAGE_SUB_TYPE_SET
);
242 lm_message_node_set_attribute(iq
->node
, "xmlns",
243 "http://jabber.org/protocol/muc#owner");
246 y
= lm_message_node_add_child(iq
->node
, "query", NULL
);
247 z
= lm_message_node_add_child(y
, "x", NULL
);
248 lm_message_node_set_attribute(z
, "xmlns", "jabber:x:data");
249 lm_message_node_set_attribute(z
, "type", "submit");
251 lm_connection_send(lconnection
, iq
, NULL
);
252 lm_message_unref(iq
);
255 // Destroy a MUC room
256 // room syntax: "room@server"
257 void xmpp_room_destroy(const char *room
, const char *venue
, const char *reason
)
260 LmMessageNode
*query
, *x
;
262 if (!lm_connection_is_authenticated(lconnection
) || !room
) return;
264 iq
= lm_message_new_with_sub_type(room
, LM_MESSAGE_TYPE_IQ
,
265 LM_MESSAGE_SUB_TYPE_SET
);
266 query
= lm_message_node_add_child(iq
->node
, "query", NULL
);
267 lm_message_node_set_attribute(query
, "xmlns",
268 "http://jabber.org/protocol/muc#owner");
269 x
= lm_message_node_add_child(query
, "destroy", NULL
);
272 lm_message_node_set_attribute(x
, "jid", venue
);
275 lm_message_node_add_child(x
, "reason", reason
);
277 lm_connection_send(lconnection
, iq
, NULL
);
278 lm_message_unref(iq
);
281 // muc_get_item_info(...)
282 // Get room member's information from xmlndata.
283 // The variables must be initialized before calling this function,
284 // because they are not touched if the relevant information is missing.
285 static void muc_get_item_info(const char *from
, LmMessageNode
*xmldata
,
286 enum imrole
*mbrole
, enum imaffiliation
*mbaffil
,
287 const char **mbjid
, const char **mbnick
,
288 const char **actorjid
, const char **reason
)
290 LmMessageNode
*y
, *z
;
293 y
= lm_message_node_find_child(xmldata
, "item");
297 p
= lm_message_node_get_attribute(y
, "affiliation");
299 if (!strcmp(p
, "owner")) *mbaffil
= affil_owner
;
300 else if (!strcmp(p
, "admin")) *mbaffil
= affil_admin
;
301 else if (!strcmp(p
, "member")) *mbaffil
= affil_member
;
302 else if (!strcmp(p
, "outcast")) *mbaffil
= affil_outcast
;
303 else if (!strcmp(p
, "none")) *mbaffil
= affil_none
;
304 else scr_LogPrint(LPRINT_LOGNORM
, "<%s>: Unknown affiliation \"%s\"",
307 p
= lm_message_node_get_attribute(y
, "role");
309 if (!strcmp(p
, "moderator")) *mbrole
= role_moderator
;
310 else if (!strcmp(p
, "participant")) *mbrole
= role_participant
;
311 else if (!strcmp(p
, "visitor")) *mbrole
= role_visitor
;
312 else if (!strcmp(p
, "none")) *mbrole
= role_none
;
313 else scr_LogPrint(LPRINT_LOGNORM
, "<%s>: Unknown role \"%s\"",
316 *mbjid
= lm_message_node_get_attribute(y
, "jid");
317 *mbnick
= lm_message_node_get_attribute(y
, "nick");
318 // For kick/ban, there can be actor and reason tags
319 *reason
= lm_message_node_get_child_value(y
, "reason");
320 z
= lm_message_node_find_child(y
, "actor");
322 *actorjid
= lm_message_node_get_attribute(z
, "jid");
325 // muc_handle_join(...)
326 // Handle a join event in a MUC room.
327 // This function will return the new_member value TRUE if somebody else joins
328 // the room (and FALSE if _we_ are joining the room).
329 static bool muc_handle_join(const GSList
*room_elt
, const char *rname
,
330 const char *roomjid
, const char *ournick
,
331 enum room_printstatus printstatus
,
332 time_t usttime
, int log_muc_conf
)
334 bool new_member
= FALSE
; // True if somebody else joins the room (not us)
337 if (!buddy_getinsideroom(room_elt
->data
)) {
338 // We weren't inside the room yet. Now we are.
339 // However, this could be a presence packet from another room member
341 buddy_setinsideroom(room_elt
->data
, TRUE
);
342 // Set the message flag unless we're already in the room buffer window
343 scr_setmsgflag_if_needed(roomjid
, FALSE
);
344 // Add a message to the tracelog file
345 mbuf
= g_strdup_printf("You have joined %s as \"%s\"", roomjid
, ournick
);
346 scr_LogPrint(LPRINT_LOGNORM
, "%s", mbuf
);
348 mbuf
= g_strdup_printf("You have joined as \"%s\"", ournick
);
350 // The 1st presence message could be for another room member
351 if (strcmp(ournick
, rname
)) {
352 // Display current mbuf and create a new message for the member
353 // Note: the usttime timestamp is related to the other member,
355 scr_WriteIncomingMessage(roomjid
, mbuf
, 0,
356 HBB_PREFIX_INFO
|HBB_PREFIX_NOFLAG
, 0);
358 hlog_write_message(roomjid
, 0, -1, mbuf
);
360 if (printstatus
!= status_none
)
361 mbuf
= g_strdup_printf("%s has joined", rname
);
368 if (strcmp(ournick
, rname
)) {
369 if (printstatus
!= status_none
)
370 mbuf
= g_strdup_printf("%s has joined", rname
);
376 guint msgflags
= HBB_PREFIX_INFO
;
377 if (!settings_opt_get_int("muc_flag_joins"))
378 msgflags
|= HBB_PREFIX_NOFLAG
;
379 scr_WriteIncomingMessage(roomjid
, mbuf
, usttime
, msgflags
, 0);
381 hlog_write_message(roomjid
, 0, -1, mbuf
);
388 void handle_muc_presence(const char *from
, LmMessageNode
*xmldata
,
389 const char *roomjid
, const char *rname
,
390 enum imstatus ust
, const char *ustmsg
,
391 time_t usttime
, char bpprio
)
397 enum imrole mbrole
= role_none
;
398 enum imaffiliation mbaffil
= affil_none
;
399 enum room_printstatus printstatus
;
400 enum room_autowhois autowhois
;
401 const char *mbjid
= NULL
, *mbnick
= NULL
;
402 const char *actorjid
= NULL
, *reason
= NULL
;
403 bool new_member
= FALSE
; // True if somebody else joins the room (not us)
404 guint statuscode
= 0;
405 guint nickchange
= 0;
410 log_muc_conf
= settings_opt_get_int("log_muc_conf");
412 room_elt
= roster_find(roomjid
, jidsearch
, 0);
414 // Add room if it doesn't already exist
415 // It shouldn't happen, there is probably something wrong (server or
417 room_elt
= roster_add_user(roomjid
, NULL
, NULL
, ROSTER_TYPE_ROOM
,
419 scr_LogPrint(LPRINT_LOGNORM
, "Strange MUC presence message");
421 // Make sure this is a room (it can be a conversion user->room)
422 buddy_settype(room_elt
->data
, ROSTER_TYPE_ROOM
);
425 // Get room member's information
426 muc_get_item_info(from
, xmldata
, &mbrole
, &mbaffil
, &mbjid
, &mbnick
,
429 // Get our room nickname
430 ournick
= buddy_getnickname(room_elt
->data
);
433 // It shouldn't happen, probably a server issue
434 mbuf
= g_strdup_printf("Unexpected groupchat packet!");
436 scr_LogPrint(LPRINT_LOGNORM
, "%s", mbuf
);
437 scr_WriteIncomingMessage(roomjid
, mbuf
, 0, HBB_PREFIX_INFO
, 0);
439 // Send back an unavailable packet
440 xmpp_setstatus(offline
, roomjid
, "", TRUE
);
445 // Get the status code
446 // 201: a room has been created
447 // 301: the user has been banned from the room
448 // 303: new room nickname
449 // 307: the user has been kicked from the room
450 // 321,322,332: the user has been removed from the room
451 y
= lm_message_node_find_child(xmldata
, "status");
453 p
= lm_message_node_get_attribute(y
, "code");
455 statuscode
= atoi(p
);
458 // Get the room's "print_status" settings
459 printstatus
= buddy_getprintstatus(room_elt
->data
);
460 if (printstatus
== status_default
) {
461 printstatus
= (guint
) settings_opt_get_int("muc_print_status");
463 printstatus
= status_default
;
466 // A new room has been created; accept MUC default config
467 if (statuscode
== 201)
468 xmpp_room_unlock(roomjid
);
470 // Check for nickname change
471 if (statuscode
== 303 && mbnick
) {
472 mbuf
= g_strdup_printf("%s is now known as %s", rname
, mbnick
);
473 scr_WriteIncomingMessage(roomjid
, mbuf
, usttime
,
474 HBB_PREFIX_INFO
|HBB_PREFIX_NOFLAG
, 0);
476 hlog_write_message(roomjid
, 0, -1, mbuf
);
478 buddy_resource_setname(room_elt
->data
, rname
, mbnick
);
479 // Maybe it's _our_ nickname...
480 if (ournick
&& !strcmp(rname
, ournick
))
481 buddy_setnickname(room_elt
->data
, mbnick
);
485 // Check for departure/arrival
486 if (!mbnick
&& ust
== offline
) {
487 // Somebody is leaving
488 enum { leave
=0, kick
, ban
} how
= leave
;
489 bool we_left
= FALSE
;
491 if (statuscode
== 307)
493 else if (statuscode
== 301)
496 // If this is a leave, check if it is ourself
497 if (ournick
&& !strcmp(rname
, ournick
)) {
498 we_left
= TRUE
; // _We_ have left! (kicked, banned, etc.)
499 buddy_setinsideroom(room_elt
->data
, FALSE
);
500 buddy_setnickname(room_elt
->data
, NULL
);
501 buddy_del_all_resources(room_elt
->data
);
502 buddy_settopic(room_elt
->data
, NULL
);
503 scr_UpdateChatStatus(FALSE
);
504 update_roster
= TRUE
;
507 // The message depends on _who_ left, and _how_
512 mbuf_end
= g_strdup_printf("%s from %s by <%s>.\nReason: %s",
513 (how
== ban
? "banned" : "kicked"),
514 roomjid
, actorjid
, reason
);
516 mbuf_end
= g_strdup_printf("%s from %s.",
517 (how
== ban
? "banned" : "kicked"),
521 mbuf
= g_strdup_printf("You have been %s", mbuf_end
);
523 mbuf
= g_strdup_printf("%s has been %s", rname
, mbuf_end
);
529 LmMessageNode
*destroynode
= lm_message_node_find_child(xmldata
,
532 if ((reason
= lm_message_node_get_child_value(destroynode
,
534 mbuf
= g_strdup_printf("You have left %s, "
535 "the room has been destroyed: %s",
538 mbuf
= g_strdup_printf("You have left %s, "
539 "the room has been destroyed", roomjid
);
542 mbuf
= g_strdup_printf("You have left %s", roomjid
);
545 if (ust
!= offline
) {
546 // This can happen when a network failure occurs,
547 // this isn't an official leave but the user isn't there anymore.
548 mbuf
= g_strdup_printf("%s has disappeared!", rname
);
552 mbuf
= g_strdup_printf("%s has left: %s", rname
, ustmsg
);
554 mbuf
= g_strdup_printf("%s has left", rname
);
559 // Display the mbuf message if we're concerned
560 // or if the print_status isn't set to none.
561 if (we_left
|| printstatus
!= status_none
) {
562 msgflags
= HBB_PREFIX_INFO
;
563 if (!we_left
&& settings_opt_get_int("muc_flag_joins") != 2)
564 msgflags
|= HBB_PREFIX_NOFLAG
;
565 scr_WriteIncomingMessage(roomjid
, mbuf
, usttime
, msgflags
, 0);
569 hlog_write_message(roomjid
, 0, -1, mbuf
);
572 scr_LogPrint(LPRINT_LOGNORM
, "%s", mbuf
);
577 } else if (buddy_getstatus(room_elt
->data
, rname
) == offline
&&
579 // Somebody is joining
580 new_member
= muc_handle_join(room_elt
, rname
, roomjid
, ournick
,
581 printstatus
, usttime
, log_muc_conf
);
583 // This is a simple member status change
585 if (printstatus
== status_all
&& !nickchange
) {
586 mbuf
= g_strdup_printf("Member status has changed: %s [%c] %s", rname
,
587 imstatus2char
[ust
], ((ustmsg
) ? ustmsg
: ""));
588 scr_WriteIncomingMessage(roomjid
, mbuf
, usttime
,
589 HBB_PREFIX_INFO
|HBB_PREFIX_NOFLAG
, 0);
594 // Sanity check, shouldn't happen...
598 // Update room member status
599 roster_setstatus(roomjid
, rname
, bpprio
, ust
, ustmsg
, usttime
,
600 mbrole
, mbaffil
, mbjid
);
602 autowhois
= buddy_getautowhois(room_elt
->data
);
603 if (autowhois
== autowhois_default
)
604 autowhois
= (settings_opt_get_int("muc_auto_whois") ?
605 autowhois_on
: autowhois_off
);
607 if (new_member
&& autowhois
== autowhois_on
) {
608 // FIXME: This will fail for some UTF-8 nicknames.
609 gchar
*joiner_nick
= from_utf8(rname
);
610 cmd_room_whois(room_elt
->data
, joiner_nick
, FALSE
);
617 void roompresence(gpointer room
, void *presencedata
)
620 const char *nickname
;
622 struct T_presence
*pres
= presencedata
;
624 if (!buddy_getinsideroom(room
))
627 bjid
= buddy_getjid(room
);
629 nickname
= buddy_getnickname(room
);
630 if (!nickname
) return;
632 to
= g_strdup_printf("%s/%s", bjid
, nickname
);
633 xmpp_setstatus(pres
->st
, to
, pres
->msg
, TRUE
);
637 // got_invite(from, to, reason, passwd)
638 // This function should be called when receiving an invitation from user
639 // "from", to enter the room "to". Optional reason and room password can
641 static void got_invite(const char* from
, const char *to
, const char* reason
,
645 event_muc_invitation
*invitation
;
650 sbuf
= g_string_new("");
652 g_string_printf(sbuf
,
653 "Received an invitation to <%s>, from <%s>, reason: %s",
656 g_string_printf(sbuf
, "Received an invitation to <%s>, from <%s>",
660 barejid
= jidtodisp(from
);
661 scr_WriteIncomingMessage(barejid
, sbuf
->str
, 0, HBB_PREFIX_INFO
, 0);
662 scr_LogPrint(LPRINT_LOGNORM
, "%s", sbuf
->str
);
664 evn
= evs_new(EVS_TYPE_INVITATION
, EVS_MAX_TIMEOUT
);
666 evn
->callback
= &evscallback_invitation
;
667 invitation
= g_new(event_muc_invitation
, 1);
668 invitation
->to
= g_strdup(to
);
669 invitation
->from
= g_strdup(from
);
670 invitation
->passwd
= g_strdup(passwd
);
671 invitation
->reason
= g_strdup(reason
);
672 evn
->data
= invitation
;
673 evn
->desc
= g_strdup_printf("<%s> invites you to %s ", from
, to
);
674 g_string_printf(sbuf
, "Please use /event %s accept|reject", evn
->id
);
676 g_string_printf(sbuf
, "Unable to create a new event!");
678 scr_WriteIncomingMessage(barejid
, sbuf
->str
, 0, HBB_PREFIX_INFO
, 0);
679 scr_LogPrint(LPRINT_LOGNORM
, "%s", sbuf
->str
);
680 g_string_free(sbuf
, TRUE
);
683 // Make sure the MUC room barejid is a room in the roster
684 barejid
= jidtodisp(to
);
685 room_elt
= roster_find(barejid
, jidsearch
, 0);
687 buddy_settype(room_elt
->data
, ROSTER_TYPE_ROOM
);
693 // Specific MUC message handling (for example invitation processing)
694 void got_muc_message(const char *from
, LmMessageNode
*x
)
696 LmMessageNode
*invite
= lm_message_node_get_child(x
, "invite");
699 const char *invite_from
;
700 const char *reason
= NULL
;
701 const char *password
= NULL
;
703 invite_from
= lm_message_node_get_attribute(invite
, "from");
704 reason
= lm_message_node_get_child_value(invite
, "reason");
705 password
= lm_message_node_get_child_value(invite
, "password");
707 got_invite(invite_from
, from
, reason
, password
);
710 // handle status code = 100 ( not anonymous )
711 // handle status code = 170 ( changement de config )
712 // 10.2.1 Notification of Configuration Changes
713 // declined invitation
716 /* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */