1 /*
2  * muc.c
3  * vim: expandtab:ts=4:sts=4:sw=4
4  *
5  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
6  *
7  * This file is part of Profanity.
8  *
9  * Profanity is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Profanity is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
21  *
22  * In addition, as a special exception, the copyright holders give permission to
23  * link the code of portions of this program with the OpenSSL library under
24  * certain conditions as described in each individual source file, and
25  * distribute linked combinations including the two.
26  *
27  * You must obey the GNU General Public License in all respects for all of the
28  * code used other than OpenSSL. If you modify file(s) with this exception, you
29  * may extend this exception to your version of the file(s), but you are not
30  * obligated to do so. If you do not wish to do so, delete this exception
31  * statement from your version. If you delete this exception statement from all
32  * source files in the program, then also delete it here.
33  *
34  */
35 
36 #include "config.h"
37 
38 #include <stdlib.h>
39 #include <string.h>
40 #include <assert.h>
41 
42 #include <glib.h>
43 
44 #include "common.h"
45 #include "tools/autocomplete.h"
46 #include "ui/ui.h"
47 #include "ui/window_list.h"
48 #include "xmpp/jid.h"
49 #include "xmpp/muc.h"
50 #include "xmpp/contact.h"
51 
52 #ifdef HAVE_OMEMO
53 #include "omemo/omemo.h"
54 #endif
55 
56 typedef struct _muc_room_t
57 {
58     char* room; // e.g. test@conference.server
59     char* nick; // e.g. Some User
60     muc_role_t role;
61     muc_affiliation_t affiliation;
62     char* password;
63     char* subject;
64     char* autocomplete_prefix;
65     gboolean pending_config;
66     GList* pending_broadcasts;
67     gboolean autojoin;
68     gboolean pending_nick_change;
69     GHashTable* roster;
70     GHashTable* members;
71     Autocomplete nick_ac;
72     Autocomplete jid_ac;
73     GHashTable* nick_changes;
74     gboolean roster_received;
75     muc_member_type_t member_type;
76     muc_anonymity_type_t anonymity_type;
77 } ChatRoom;
78 
79 GHashTable* rooms = NULL;
80 GHashTable* invite_passwords = NULL;
81 Autocomplete invite_ac = NULL;
82 Autocomplete confservers_ac = NULL;
83 
84 static void _free_room(ChatRoom* room);
85 static gint _compare_occupants(Occupant* a, Occupant* b);
86 static muc_role_t _role_from_string(const char* const role);
87 static muc_affiliation_t _affiliation_from_string(const char* const affiliation);
88 static char* _role_to_string(muc_role_t role);
89 static char* _affiliation_to_string(muc_affiliation_t affiliation);
90 static Occupant* _muc_occupant_new(const char* const nick, const char* const jid, muc_role_t role,
91                                    muc_affiliation_t affiliation, resource_presence_t presence, const char* const status);
92 static void _occupant_free(Occupant* occupant);
93 
94 void
muc_init(void)95 muc_init(void)
96 {
97     invite_ac = autocomplete_new();
98     confservers_ac = autocomplete_new();
99     rooms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)_free_room);
100     invite_passwords = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
101 }
102 
103 void
muc_close(void)104 muc_close(void)
105 {
106     autocomplete_free(invite_ac);
107     autocomplete_free(confservers_ac);
108     g_hash_table_destroy(rooms);
109     g_hash_table_destroy(invite_passwords);
110     rooms = NULL;
111     invite_passwords = NULL;
112     invite_ac = NULL;
113     confservers_ac = NULL;
114 }
115 
116 void
muc_confserver_add(const char * const server)117 muc_confserver_add(const char* const server)
118 {
119     autocomplete_add(confservers_ac, server);
120 }
121 
122 void
muc_invites_add(const char * const room,const char * const password)123 muc_invites_add(const char* const room, const char* const password)
124 {
125     autocomplete_add(invite_ac, room);
126     if (password) {
127         g_hash_table_replace(invite_passwords, strdup(room), strdup(password));
128     }
129 }
130 
131 void
muc_invites_remove(const char * const room)132 muc_invites_remove(const char* const room)
133 {
134     autocomplete_remove(invite_ac, room);
135     g_hash_table_remove(invite_passwords, room);
136 }
137 
138 gint
muc_invites_count(void)139 muc_invites_count(void)
140 {
141     return autocomplete_length(invite_ac);
142 }
143 
144 GList*
muc_invites(void)145 muc_invites(void)
146 {
147     return autocomplete_create_list(invite_ac);
148 }
149 
150 char*
muc_invite_password(const char * const room)151 muc_invite_password(const char* const room)
152 {
153     return g_hash_table_lookup(invite_passwords, room);
154 }
155 
156 gboolean
muc_invites_contain(const char * const room)157 muc_invites_contain(const char* const room)
158 {
159     GList* invites = autocomplete_create_list(invite_ac);
160     GList* curr = invites;
161     while (curr) {
162         if (strcmp(curr->data, room) == 0) {
163             g_list_free_full(invites, g_free);
164             return TRUE;
165         } else {
166             curr = g_list_next(curr);
167         }
168     }
169     g_list_free_full(invites, g_free);
170 
171     return FALSE;
172 }
173 
174 void
muc_invites_reset_ac(void)175 muc_invites_reset_ac(void)
176 {
177     autocomplete_reset(invite_ac);
178 }
179 
180 void
muc_confserver_reset_ac(void)181 muc_confserver_reset_ac(void)
182 {
183     autocomplete_reset(confservers_ac);
184 }
185 
186 char*
muc_invites_find(const char * const search_str,gboolean previous,void * context)187 muc_invites_find(const char* const search_str, gboolean previous, void* context)
188 {
189     return autocomplete_complete(invite_ac, search_str, TRUE, previous);
190 }
191 
192 char*
muc_confserver_find(const char * const search_str,gboolean previous,void * context)193 muc_confserver_find(const char* const search_str, gboolean previous, void* context)
194 {
195     return autocomplete_complete(confservers_ac, search_str, TRUE, previous);
196 }
197 
198 void
muc_invites_clear(void)199 muc_invites_clear(void)
200 {
201     autocomplete_clear(invite_ac);
202     if (invite_passwords) {
203         g_hash_table_remove_all(invite_passwords);
204     }
205 }
206 
207 void
muc_confserver_clear(void)208 muc_confserver_clear(void)
209 {
210     autocomplete_clear(confservers_ac);
211 }
212 
213 void
muc_join(const char * const room,const char * const nick,const char * const password,gboolean autojoin)214 muc_join(const char* const room, const char* const nick, const char* const password, gboolean autojoin)
215 {
216     ChatRoom* new_room = malloc(sizeof(ChatRoom));
217     new_room->room = strdup(room);
218     new_room->nick = strdup(nick);
219     new_room->role = MUC_ROLE_NONE;
220     new_room->affiliation = MUC_AFFILIATION_NONE;
221     new_room->autocomplete_prefix = NULL;
222     if (password) {
223         new_room->password = strdup(password);
224     } else {
225         new_room->password = NULL;
226     }
227     new_room->subject = NULL;
228     new_room->pending_broadcasts = NULL;
229     new_room->pending_config = FALSE;
230     new_room->roster = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)_occupant_free);
231     new_room->members = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
232     new_room->nick_ac = autocomplete_new();
233     new_room->jid_ac = autocomplete_new();
234     new_room->nick_changes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
235     new_room->roster_received = FALSE;
236     new_room->pending_nick_change = FALSE;
237     new_room->autojoin = autojoin;
238     new_room->member_type = MUC_MEMBER_TYPE_UNKNOWN;
239     new_room->anonymity_type = MUC_ANONYMITY_TYPE_UNKNOWN;
240 
241     g_hash_table_insert(rooms, strdup(room), new_room);
242 }
243 
244 void
muc_leave(const char * const room)245 muc_leave(const char* const room)
246 {
247     g_hash_table_remove(rooms, room);
248 }
249 
250 gboolean
muc_requires_config(const char * const room)251 muc_requires_config(const char* const room)
252 {
253     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
254     if (chat_room) {
255         return chat_room->pending_config;
256     } else {
257         return FALSE;
258     }
259 }
260 
261 void
muc_set_requires_config(const char * const room,gboolean val)262 muc_set_requires_config(const char* const room, gboolean val)
263 {
264     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
265     if (chat_room) {
266         chat_room->pending_config = val;
267     }
268 }
269 
270 void
muc_set_features(const char * const room,GSList * features)271 muc_set_features(const char* const room, GSList* features)
272 {
273     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
274     if (chat_room && features) {
275         if (g_slist_find_custom(features, "muc_membersonly", (GCompareFunc)g_strcmp0)) {
276             chat_room->member_type = MUC_MEMBER_TYPE_MEMBERS_ONLY;
277         } else {
278             chat_room->member_type = MUC_MEMBER_TYPE_PUBLIC;
279         }
280         if (g_slist_find_custom(features, "muc_nonanonymous", (GCompareFunc)g_strcmp0)) {
281             chat_room->anonymity_type = MUC_ANONYMITY_TYPE_NONANONYMOUS;
282         } else if (g_slist_find_custom(features, "muc_semianonymous", (GCompareFunc)g_strcmp0)) {
283             chat_room->anonymity_type = MUC_ANONYMITY_TYPE_SEMIANONYMOUS;
284         } else {
285             chat_room->anonymity_type = MUC_ANONYMITY_TYPE_UNKNOWN;
286         }
287     }
288 }
289 
290 /*
291  * Returns TRUE if the user is currently in the room
292  */
293 gboolean
muc_active(const char * const room)294 muc_active(const char* const room)
295 {
296     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
297     return (chat_room != NULL);
298 }
299 
300 gboolean
muc_autojoin(const char * const room)301 muc_autojoin(const char* const room)
302 {
303     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
304     if (chat_room) {
305         return chat_room->autojoin;
306     } else {
307         return FALSE;
308     }
309 }
310 
311 void
muc_set_subject(const char * const room,const char * const subject)312 muc_set_subject(const char* const room, const char* const subject)
313 {
314     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
315     if (chat_room) {
316         free(chat_room->subject);
317         if (subject) {
318             chat_room->subject = strdup(subject);
319         } else {
320             chat_room->subject = NULL;
321         }
322     }
323 }
324 
325 char*
muc_subject(const char * const room)326 muc_subject(const char* const room)
327 {
328     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
329     if (chat_room) {
330         return chat_room->subject;
331     } else {
332         return NULL;
333     }
334 }
335 
336 void
muc_pending_broadcasts_add(const char * const room,const char * const message)337 muc_pending_broadcasts_add(const char* const room, const char* const message)
338 {
339     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
340     if (chat_room) {
341         chat_room->pending_broadcasts = g_list_append(chat_room->pending_broadcasts, strdup(message));
342     }
343 }
344 
345 GList*
muc_pending_broadcasts(const char * const room)346 muc_pending_broadcasts(const char* const room)
347 {
348     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
349     if (chat_room) {
350         return chat_room->pending_broadcasts;
351     } else {
352         return NULL;
353     }
354 }
355 
356 char*
muc_old_nick(const char * const room,const char * const new_nick)357 muc_old_nick(const char* const room, const char* const new_nick)
358 {
359     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
360     if (chat_room && chat_room->pending_nick_change) {
361         return g_hash_table_lookup(chat_room->nick_changes, new_nick);
362     } else {
363         return NULL;
364     }
365 }
366 
367 /*
368  * Flag that the user has sent a nick change to the service
369  * and is awaiting the response
370  */
371 void
muc_nick_change_start(const char * const room,const char * const new_nick)372 muc_nick_change_start(const char* const room, const char* const new_nick)
373 {
374     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
375     if (chat_room) {
376         chat_room->pending_nick_change = TRUE;
377         g_hash_table_insert(chat_room->nick_changes, strdup(new_nick), strdup(chat_room->nick));
378     }
379 }
380 
381 /*
382  * Returns TRUE if the room is awaiting the result of a
383  * nick change
384  */
385 gboolean
muc_nick_change_pending(const char * const room)386 muc_nick_change_pending(const char* const room)
387 {
388     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
389     if (chat_room) {
390         return chat_room->pending_nick_change;
391     } else {
392         return FALSE;
393     }
394 }
395 
396 /*
397  * Change the current nick name for the room, call once
398  * the service has responded
399  */
400 void
muc_nick_change_complete(const char * const room,const char * const nick)401 muc_nick_change_complete(const char* const room, const char* const nick)
402 {
403     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
404     if (chat_room) {
405         g_hash_table_remove(chat_room->roster, chat_room->nick);
406         autocomplete_remove(chat_room->nick_ac, chat_room->nick);
407         free(chat_room->nick);
408         chat_room->nick = strdup(nick);
409         chat_room->pending_nick_change = FALSE;
410         g_hash_table_remove(chat_room->nick_changes, nick);
411     }
412 }
413 
414 /*
415  * Return a list of room names
416  * The contents of the list are owned by the chat room and should not be
417  * modified or freed.
418  */
419 GList*
muc_rooms(void)420 muc_rooms(void)
421 {
422     return g_hash_table_get_keys(rooms);
423 }
424 
425 /*
426  * Return current users nickname for the specified room
427  * The nickname is owned by the chat room and should not be modified or freed
428  */
429 char*
muc_nick(const char * const room)430 muc_nick(const char* const room)
431 {
432     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
433     if (chat_room) {
434         return chat_room->nick;
435     } else {
436         return NULL;
437     }
438 }
439 
440 /*
441  * Return password for the specified room
442  * The password is owned by the chat room and should not be modified or freed
443  */
444 char*
muc_password(const char * const room)445 muc_password(const char* const room)
446 {
447     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
448     if (chat_room) {
449         return chat_room->password;
450     } else {
451         return NULL;
452     }
453 }
454 
455 /*
456  * Returns TRUE if the specified nick exists in the room's roster
457  */
458 gboolean
muc_roster_contains_nick(const char * const room,const char * const nick)459 muc_roster_contains_nick(const char* const room, const char* const nick)
460 {
461     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
462     if (chat_room) {
463         Occupant* occupant = g_hash_table_lookup(chat_room->roster, nick);
464         return (occupant != NULL);
465     } else {
466         return FALSE;
467     }
468 }
469 
470 /*
471  * Add a new chat room member to the room's roster
472  */
473 gboolean
muc_roster_add(const char * const room,const char * const nick,const char * const jid,const char * const role,const char * const affiliation,const char * const show,const char * const status)474 muc_roster_add(const char* const room, const char* const nick, const char* const jid, const char* const role,
475                const char* const affiliation, const char* const show, const char* const status)
476 {
477     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
478     gboolean updated = FALSE;
479     resource_presence_t new_presence = resource_presence_from_string(show);
480 
481     if (chat_room) {
482         Occupant* old = g_hash_table_lookup(chat_room->roster, nick);
483 
484         if (!old) {
485             updated = TRUE;
486             autocomplete_add(chat_room->nick_ac, nick);
487         } else if (old->presence != new_presence || (g_strcmp0(old->status, status) != 0)) {
488             updated = TRUE;
489         }
490 
491         resource_presence_t presence = resource_presence_from_string(show);
492         muc_role_t role_t = _role_from_string(role);
493         muc_affiliation_t affiliation_t = _affiliation_from_string(affiliation);
494         Occupant* occupant = _muc_occupant_new(nick, jid, role_t, affiliation_t, presence, status);
495         g_hash_table_replace(chat_room->roster, strdup(nick), occupant);
496 
497         if (jid) {
498             Jid* jidp = jid_create(jid);
499             if (jidp->barejid) {
500                 autocomplete_add(chat_room->jid_ac, jidp->barejid);
501             }
502             jid_destroy(jidp);
503         }
504     }
505 
506     return updated;
507 }
508 
509 /*
510  * Remove a room member from the room's roster
511  */
512 void
muc_roster_remove(const char * const room,const char * const nick)513 muc_roster_remove(const char* const room, const char* const nick)
514 {
515     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
516     if (chat_room) {
517         g_hash_table_remove(chat_room->roster, nick);
518         autocomplete_remove(chat_room->nick_ac, nick);
519     }
520 }
521 
522 Occupant*
muc_roster_item(const char * const room,const char * const nick)523 muc_roster_item(const char* const room, const char* const nick)
524 {
525     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
526     if (chat_room) {
527         Occupant* occupant = g_hash_table_lookup(chat_room->roster, nick);
528         return occupant;
529     } else {
530         return NULL;
531     }
532 }
533 
534 /*
535  * Return a list of PContacts representing the room members in the room's roster
536  */
537 GList*
muc_roster(const char * const room)538 muc_roster(const char* const room)
539 {
540     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
541     if (chat_room) {
542         GList* result = NULL;
543         GList* occupants = g_hash_table_get_values(chat_room->roster);
544 
545         GList* curr = occupants;
546         while (curr) {
547             result = g_list_insert_sorted(result, curr->data, (GCompareFunc)_compare_occupants);
548             curr = g_list_next(curr);
549         }
550 
551         g_list_free(occupants);
552 
553         return result;
554     } else {
555         return NULL;
556     }
557 }
558 
559 /*
560  * Return a Autocomplete representing the room member's in the roster
561  */
562 Autocomplete
muc_roster_ac(const char * const room)563 muc_roster_ac(const char* const room)
564 {
565     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
566     if (chat_room) {
567         return chat_room->nick_ac;
568     } else {
569         return NULL;
570     }
571 }
572 
573 Autocomplete
muc_roster_jid_ac(const char * const room)574 muc_roster_jid_ac(const char* const room)
575 {
576     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
577     if (chat_room) {
578         return chat_room->jid_ac;
579     } else {
580         return NULL;
581     }
582 }
583 
584 /*
585  * Set to TRUE when the rooms roster has been fully received
586  */
587 void
muc_roster_set_complete(const char * const room)588 muc_roster_set_complete(const char* const room)
589 {
590     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
591     if (chat_room) {
592         chat_room->roster_received = TRUE;
593     }
594 }
595 
596 /*
597  * Returns TRUE id the rooms roster has been fully received
598  */
599 gboolean
muc_roster_complete(const char * const room)600 muc_roster_complete(const char* const room)
601 {
602     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
603     if (chat_room) {
604         return chat_room->roster_received;
605     } else {
606         return FALSE;
607     }
608 }
609 
610 gboolean
muc_occupant_available(Occupant * occupant)611 muc_occupant_available(Occupant* occupant)
612 {
613     return (occupant->presence == RESOURCE_ONLINE || occupant->presence == RESOURCE_CHAT);
614 }
615 
616 const char*
muc_occupant_affiliation_str(Occupant * occupant)617 muc_occupant_affiliation_str(Occupant* occupant)
618 {
619     return _affiliation_to_string(occupant->affiliation);
620 }
621 
622 const char*
muc_occupant_role_str(Occupant * occupant)623 muc_occupant_role_str(Occupant* occupant)
624 {
625     return _role_to_string(occupant->role);
626 }
627 
628 GSList*
muc_occupants_by_role(const char * const room,muc_role_t role)629 muc_occupants_by_role(const char* const room, muc_role_t role)
630 {
631     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
632     if (chat_room) {
633         GSList* result = NULL;
634         GHashTableIter iter;
635         gpointer key;
636         gpointer value;
637 
638         g_hash_table_iter_init(&iter, chat_room->roster);
639         while (g_hash_table_iter_next(&iter, &key, &value)) {
640             Occupant* occupant = (Occupant*)value;
641             if (occupant->role == role) {
642                 result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_occupants);
643             }
644         }
645         return result;
646     } else {
647         return NULL;
648     }
649 }
650 
651 GSList*
muc_occupants_by_affiliation(const char * const room,muc_affiliation_t affiliation)652 muc_occupants_by_affiliation(const char* const room, muc_affiliation_t affiliation)
653 {
654     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
655     if (chat_room) {
656         GSList* result = NULL;
657         GHashTableIter iter;
658         gpointer key;
659         gpointer value;
660 
661         g_hash_table_iter_init(&iter, chat_room->roster);
662         while (g_hash_table_iter_next(&iter, &key, &value)) {
663             Occupant* occupant = (Occupant*)value;
664             if (occupant->affiliation == affiliation) {
665                 result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_occupants);
666             }
667         }
668         return result;
669     } else {
670         return NULL;
671     }
672 }
673 
674 /*
675  * Remove the old_nick from the roster, and flag that a pending nickname change
676  * is in progress
677  */
678 void
muc_occupant_nick_change_start(const char * const room,const char * const new_nick,const char * const old_nick)679 muc_occupant_nick_change_start(const char* const room, const char* const new_nick, const char* const old_nick)
680 {
681     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
682     if (chat_room) {
683         g_hash_table_insert(chat_room->nick_changes, strdup(new_nick), strdup(old_nick));
684         muc_roster_remove(room, old_nick);
685     }
686 }
687 
688 /*
689  * Complete the pending nick name change for a contact in the room's roster
690  * The new nick name will be added to the roster
691  * The old nick name will be returned in a new string which must be freed by
692  * the caller
693  */
694 char*
muc_roster_nick_change_complete(const char * const room,const char * const nick)695 muc_roster_nick_change_complete(const char* const room, const char* const nick)
696 {
697     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
698     if (chat_room) {
699         char* old_nick = g_hash_table_lookup(chat_room->nick_changes, nick);
700         if (old_nick) {
701             char* old_nick_cpy = strdup(old_nick);
702             g_hash_table_remove(chat_room->nick_changes, nick);
703 
704             return old_nick_cpy;
705         }
706     }
707 
708     return NULL;
709 }
710 
711 char*
muc_autocomplete(ProfWin * window,const char * const input,gboolean previous)712 muc_autocomplete(ProfWin* window, const char* const input, gboolean previous)
713 {
714     if (window->type != WIN_MUC) {
715         return NULL;
716     }
717 
718     ProfMucWin* mucwin = (ProfMucWin*)window;
719     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
720     ChatRoom* chat_room = g_hash_table_lookup(rooms, mucwin->roomjid);
721     if (chat_room == NULL || chat_room->nick_ac == NULL) {
722         return NULL;
723     }
724 
725     const char* search_str = NULL;
726 
727     gchar* last_space = g_strrstr(input, " ");
728     if (!last_space) {
729         search_str = input;
730         if (!chat_room->autocomplete_prefix) {
731             chat_room->autocomplete_prefix = strdup("");
732         }
733     } else {
734         search_str = last_space + 1;
735         if (!chat_room->autocomplete_prefix) {
736             chat_room->autocomplete_prefix = g_strndup(input, search_str - input);
737         }
738     }
739 
740     char* result = autocomplete_complete(chat_room->nick_ac, search_str, FALSE, previous);
741     if (result == NULL) {
742         return NULL;
743     }
744 
745     GString* replace_with = g_string_new(chat_room->autocomplete_prefix);
746     g_string_append(replace_with, result);
747 
748     if (strlen(chat_room->autocomplete_prefix) == 0) {
749         g_string_append(replace_with, ": ");
750     }
751     g_free(result);
752     result = replace_with->str;
753     g_string_free(replace_with, FALSE);
754     return result;
755 }
756 
757 void
muc_jid_autocomplete_reset(const char * const room)758 muc_jid_autocomplete_reset(const char* const room)
759 {
760     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
761     if (chat_room) {
762         if (chat_room->jid_ac) {
763             autocomplete_reset(chat_room->jid_ac);
764         }
765     }
766 }
767 
768 void
muc_jid_autocomplete_add_all(const char * const room,GSList * jids)769 muc_jid_autocomplete_add_all(const char* const room, GSList* jids)
770 {
771     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
772     if (chat_room) {
773         if (chat_room->jid_ac) {
774             GSList* curr_jid = jids;
775             while (curr_jid) {
776                 const char* jid = curr_jid->data;
777                 Jid* jidp = jid_create(jid);
778                 if (jidp) {
779                     if (jidp->barejid) {
780                         autocomplete_add(chat_room->jid_ac, jidp->barejid);
781                     }
782                 }
783                 jid_destroy(jidp);
784                 curr_jid = g_slist_next(curr_jid);
785             }
786         }
787     }
788 }
789 
790 void
muc_autocomplete_reset(const char * const room)791 muc_autocomplete_reset(const char* const room)
792 {
793     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
794     if (chat_room) {
795         if (chat_room->nick_ac) {
796             autocomplete_reset(chat_room->nick_ac);
797         }
798 
799         if (chat_room->autocomplete_prefix) {
800             free(chat_room->autocomplete_prefix);
801             chat_room->autocomplete_prefix = NULL;
802         }
803     }
804 }
805 
806 char*
muc_role_str(const char * const room)807 muc_role_str(const char* const room)
808 {
809     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
810     if (chat_room) {
811         return _role_to_string(chat_room->role);
812     } else {
813         return "none";
814     }
815 }
816 
817 void
muc_set_role(const char * const room,const char * const role)818 muc_set_role(const char* const room, const char* const role)
819 {
820     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
821     if (chat_room) {
822         chat_room->role = _role_from_string(role);
823     }
824 }
825 
826 char*
muc_affiliation_str(const char * const room)827 muc_affiliation_str(const char* const room)
828 {
829     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
830     if (chat_room) {
831         return _affiliation_to_string(chat_room->affiliation);
832     } else {
833         return "none";
834     }
835 }
836 
837 void
muc_set_affiliation(const char * const room,const char * const affiliation)838 muc_set_affiliation(const char* const room, const char* const affiliation)
839 {
840     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
841     if (chat_room) {
842         chat_room->affiliation = _affiliation_from_string(affiliation);
843     }
844 }
845 
846 muc_member_type_t
muc_member_type(const char * const room)847 muc_member_type(const char* const room)
848 {
849     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
850     if (chat_room) {
851         return chat_room->member_type;
852     } else {
853         return MUC_MEMBER_TYPE_UNKNOWN;
854     }
855 }
856 
857 muc_anonymity_type_t
muc_anonymity_type(const char * const room)858 muc_anonymity_type(const char* const room)
859 {
860     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
861     if (chat_room) {
862         return chat_room->anonymity_type;
863     } else {
864         return MUC_ANONYMITY_TYPE_UNKNOWN;
865     }
866 }
867 
868 /*
869  * Return the list of jid affiliated as member in the room
870  */
871 GList*
muc_members(const char * const room)872 muc_members(const char* const room)
873 {
874     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
875     if (chat_room) {
876         return g_hash_table_get_keys(chat_room->members);
877     } else {
878         return NULL;
879     }
880 }
881 
882 void
muc_members_add(const char * const room,const char * const jid)883 muc_members_add(const char* const room, const char* const jid)
884 {
885     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
886     if (chat_room) {
887         if (g_hash_table_insert(chat_room->members, strdup(jid), NULL)) {
888 #ifdef HAVE_OMEMO
889             if (chat_room->anonymity_type == MUC_ANONYMITY_TYPE_NONANONYMOUS) {
890                 char* our_barejid = connection_get_barejid();
891                 if (strcmp(jid, our_barejid) != 0) {
892                     omemo_start_session(jid);
893                 }
894                 free(our_barejid);
895             }
896 #endif
897         }
898     }
899 }
900 
901 void
muc_members_remove(const char * const room,const char * const jid)902 muc_members_remove(const char* const room, const char* const jid)
903 {
904     ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
905     if (chat_room) {
906         g_hash_table_remove(chat_room->members, jid);
907     }
908 }
909 
910 void
muc_members_update(const char * const room,const char * const jid,const char * const affiliation)911 muc_members_update(const char* const room, const char* const jid, const char* const affiliation)
912 {
913     if (strcmp(affiliation, "outcast") == 0 || strcmp(affiliation, "none") == 0) {
914         muc_members_remove(room, jid);
915     } else if (strcmp(affiliation, "member") == 0 || strcmp(affiliation, "admin") == 0 || strcmp(affiliation, "owner") == 0) {
916         muc_members_add(room, jid);
917     }
918 }
919 
920 static void
_free_room(ChatRoom * room)921 _free_room(ChatRoom* room)
922 {
923     if (room) {
924         free(room->room);
925         free(room->nick);
926         free(room->subject);
927         free(room->password);
928         free(room->autocomplete_prefix);
929         if (room->roster) {
930             g_hash_table_destroy(room->roster);
931         }
932         if (room->members) {
933             g_hash_table_destroy(room->members);
934         }
935         autocomplete_free(room->nick_ac);
936         autocomplete_free(room->jid_ac);
937         if (room->nick_changes) {
938             g_hash_table_destroy(room->nick_changes);
939         }
940         if (room->pending_broadcasts) {
941             g_list_free_full(room->pending_broadcasts, free);
942         }
943         free(room);
944     }
945 }
946 
947 static gint
_compare_occupants(Occupant * a,Occupant * b)948 _compare_occupants(Occupant* a, Occupant* b)
949 {
950     const char* utf8_str_a = a->nick_collate_key;
951     const char* utf8_str_b = b->nick_collate_key;
952 
953     gint result = g_strcmp0(utf8_str_a, utf8_str_b);
954 
955     return result;
956 }
957 
958 static muc_role_t
_role_from_string(const char * const role)959 _role_from_string(const char* const role)
960 {
961     if (role) {
962         if (g_strcmp0(role, "visitor") == 0) {
963             return MUC_ROLE_VISITOR;
964         } else if (g_strcmp0(role, "participant") == 0) {
965             return MUC_ROLE_PARTICIPANT;
966         } else if (g_strcmp0(role, "moderator") == 0) {
967             return MUC_ROLE_MODERATOR;
968         } else {
969             return MUC_ROLE_NONE;
970         }
971     } else {
972         return MUC_ROLE_NONE;
973     }
974 }
975 
976 static char*
_role_to_string(muc_role_t role)977 _role_to_string(muc_role_t role)
978 {
979     char* result = NULL;
980 
981     switch (role) {
982     case MUC_ROLE_NONE:
983         result = "none";
984         break;
985     case MUC_ROLE_VISITOR:
986         result = "visitor";
987         break;
988     case MUC_ROLE_PARTICIPANT:
989         result = "participant";
990         break;
991     case MUC_ROLE_MODERATOR:
992         result = "moderator";
993         break;
994     default:
995         result = "none";
996         break;
997     }
998 
999     return result;
1000 }
1001 
1002 static muc_affiliation_t
_affiliation_from_string(const char * const affiliation)1003 _affiliation_from_string(const char* const affiliation)
1004 {
1005     if (affiliation) {
1006         if (g_strcmp0(affiliation, "outcast") == 0) {
1007             return MUC_AFFILIATION_OUTCAST;
1008         } else if (g_strcmp0(affiliation, "member") == 0) {
1009             return MUC_AFFILIATION_MEMBER;
1010         } else if (g_strcmp0(affiliation, "admin") == 0) {
1011             return MUC_AFFILIATION_ADMIN;
1012         } else if (g_strcmp0(affiliation, "owner") == 0) {
1013             return MUC_AFFILIATION_OWNER;
1014         } else {
1015             return MUC_AFFILIATION_NONE;
1016         }
1017     } else {
1018         return MUC_AFFILIATION_NONE;
1019     }
1020 }
1021 
1022 static char*
_affiliation_to_string(muc_affiliation_t affiliation)1023 _affiliation_to_string(muc_affiliation_t affiliation)
1024 {
1025     char* result = NULL;
1026 
1027     switch (affiliation) {
1028     case MUC_AFFILIATION_NONE:
1029         result = "none";
1030         break;
1031     case MUC_AFFILIATION_OUTCAST:
1032         result = "outcast";
1033         break;
1034     case MUC_AFFILIATION_MEMBER:
1035         result = "member";
1036         break;
1037     case MUC_AFFILIATION_ADMIN:
1038         result = "admin";
1039         break;
1040     case MUC_AFFILIATION_OWNER:
1041         result = "owner";
1042         break;
1043     default:
1044         result = "none";
1045         break;
1046     }
1047 
1048     return result;
1049 }
1050 
1051 static Occupant*
_muc_occupant_new(const char * const nick,const char * const jid,muc_role_t role,muc_affiliation_t affiliation,resource_presence_t presence,const char * const status)1052 _muc_occupant_new(const char* const nick, const char* const jid, muc_role_t role, muc_affiliation_t affiliation,
1053                   resource_presence_t presence, const char* const status)
1054 {
1055     Occupant* occupant = malloc(sizeof(Occupant));
1056 
1057     if (nick) {
1058         occupant->nick = strdup(nick);
1059         occupant->nick_collate_key = g_utf8_collate_key(occupant->nick, -1);
1060     } else {
1061         occupant->nick = NULL;
1062         occupant->nick_collate_key = NULL;
1063     }
1064 
1065     if (jid) {
1066         occupant->jid = strdup(jid);
1067     } else {
1068         occupant->jid = NULL;
1069     }
1070 
1071     occupant->presence = presence;
1072 
1073     if (status) {
1074         occupant->status = strdup(status);
1075     } else {
1076         occupant->status = NULL;
1077     }
1078 
1079     occupant->role = role;
1080     occupant->affiliation = affiliation;
1081 
1082     return occupant;
1083 }
1084 
1085 static void
_occupant_free(Occupant * occupant)1086 _occupant_free(Occupant* occupant)
1087 {
1088     if (occupant) {
1089         free(occupant->nick);
1090         free(occupant->nick_collate_key);
1091         free(occupant->jid);
1092         free(occupant->status);
1093         free(occupant);
1094     }
1095 }
1096