New silcconfig library and server parser. Merged silc-newconfig-final.patch.
[runtime.git] / apps / irssi / src / core / nicklist.c
1 /*
2  nicklist.c : irssi
3
4     Copyright (C) 1999-2000 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "misc.h"
24
25 #include "servers.h"
26 #include "channels.h"
27 #include "nicklist.h"
28 #include "masks.h"
29
30 #define isalnumhigh(a) \
31         (i_isalnum(a) || (unsigned char) (a) >= 128)
32
33 static void nick_hash_add(CHANNEL_REC *channel, NICK_REC *nick)
34 {
35         NICK_REC *list;
36
37         nick->next = NULL;
38
39         list = g_hash_table_lookup(channel->nicks, nick->nick);
40         if (list == NULL)
41                 g_hash_table_insert(channel->nicks, nick->nick, nick);
42         else {
43                 /* multiple nicks with same name */
44                 while (list->next != NULL)
45                         list = list->next;
46                 list->next = nick;
47         }
48
49         if (nick == channel->ownnick) {
50                 /* move our own nick to beginning of the nick list.. */
51                 nicklist_set_own(channel, nick);
52         }
53 }
54
55 static void nick_hash_remove(CHANNEL_REC *channel, NICK_REC *nick)
56 {
57         NICK_REC *list;
58
59         list = g_hash_table_lookup(channel->nicks, nick->nick);
60         if (list == NULL)
61                 return;
62
63         if (list == nick || list->next == NULL) {
64                 g_hash_table_remove(channel->nicks, nick->nick);
65                 if (list->next != NULL) {
66                         g_hash_table_insert(channel->nicks, nick->next->nick,
67                                             nick->next);
68                 }
69         } else {
70                 while (list->next != nick)
71                         list = list->next;
72                 list->next = nick->next;
73         }
74 }
75
76 /* Add new nick to list */
77 void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick)
78 {
79         /*MODULE_DATA_INIT(nick);*/
80
81         nick->type = module_get_uniq_id("NICK", 0);
82         nick->chat_type = channel->chat_type;
83
84         nick_hash_add(channel, nick);
85         signal_emit("nicklist new", 2, channel, nick);
86 }
87
88 /* Set host address for nick */
89 void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host)
90 {
91         g_return_if_fail(channel != NULL);
92         g_return_if_fail(nick != NULL);
93         g_return_if_fail(host != NULL);
94
95         g_free_not_null(nick->host);
96         nick->host = g_strdup(host);
97
98         signal_emit("nicklist host changed", 2, channel, nick);
99 }
100
101 static void nicklist_destroy(CHANNEL_REC *channel, NICK_REC *nick)
102 {
103         signal_emit("nicklist remove", 2, channel, nick);
104
105         /*MODULE_DATA_DEINIT(nick);*/
106         g_free(nick->nick);
107         g_free_not_null(nick->realname);
108         g_free_not_null(nick->host);
109         g_free(nick);
110 }
111
112 /* Remove nick from list */
113 void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
114 {
115         g_return_if_fail(IS_CHANNEL(channel));
116         g_return_if_fail(nick != NULL);
117
118         nick_hash_remove(channel, nick);
119         nicklist_destroy(channel, nick);
120 }
121
122 static void nicklist_rename_list(SERVER_REC *server, void *new_nick_id,
123                                  const char *old_nick, const char *new_nick,
124                                  GSList *nicks)
125 {
126         CHANNEL_REC *channel;
127         NICK_REC *nickrec;
128         GSList *tmp;
129
130         for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
131                 channel = tmp->data;
132                 nickrec = tmp->next->data;
133
134                 /* remove old nick from hash table */
135                 nick_hash_remove(channel, nickrec);
136
137                 if (new_nick_id != NULL)
138                         nickrec->unique_id = new_nick_id;
139
140                 g_free(nickrec->nick);
141                 nickrec->nick = g_strdup(new_nick);
142
143                 /* add new nick to hash table */
144                 nick_hash_add(channel, nickrec);
145
146                 signal_emit("nicklist changed", 3, channel, nickrec, old_nick);
147         }
148         g_slist_free(nicks);
149 }
150
151 void nicklist_rename(SERVER_REC *server, const char *old_nick,
152                      const char *new_nick)
153 {
154         nicklist_rename_list(server, NULL, old_nick, new_nick,
155                              nicklist_get_same(server, old_nick));
156 }
157
158 void nicklist_rename_unique(SERVER_REC *server,
159                             void *old_nick_id, const char *old_nick,
160                             void *new_nick_id, const char *new_nick)
161 {
162         nicklist_rename_list(server, new_nick_id, old_nick, new_nick,
163                              nicklist_get_same_unique(server, old_nick_id));
164 }
165
166 static NICK_REC *nicklist_find_wildcards(CHANNEL_REC *channel,
167                                          const char *mask)
168 {
169         GSList *nicks, *tmp;
170         NICK_REC *nick;
171
172         nicks = nicklist_getnicks(channel);
173         nick = NULL;
174         for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
175                 nick = tmp->data;
176
177                 if (mask_match_address(channel->server, mask,
178                                        nick->nick, nick->host))
179                         break;
180         }
181         g_slist_free(nicks);
182         return tmp == NULL ? NULL : nick;
183 }
184
185 GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask)
186 {
187         GSList *nicks, *tmp, *next;
188
189         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
190         g_return_val_if_fail(mask != NULL, NULL);
191
192         nicks = nicklist_getnicks(channel);
193         for (tmp = nicks; tmp != NULL; tmp = next) {
194                 NICK_REC *nick = tmp->data;
195
196                 next = tmp->next;
197                 if (!mask_match_address(channel->server, mask,
198                                         nick->nick, nick->host))
199                         nicks = g_slist_remove(nicks, tmp->data);
200         }
201
202         return nicks;
203 }
204
205 /* Find nick */
206 NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick)
207 {
208         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
209         g_return_val_if_fail(nick != NULL, NULL);
210
211         return g_hash_table_lookup(channel->nicks, nick);
212 }
213
214 NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
215                                void *id)
216 {
217         NICK_REC *rec;
218
219         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
220         g_return_val_if_fail(nick != NULL, NULL);
221
222         rec = g_hash_table_lookup(channel->nicks, nick);
223         while (rec != NULL && rec->unique_id != id)
224                 rec = rec->next;
225
226         return rec;
227 }
228
229 /* Find nick mask, wildcards allowed */
230 NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask)
231 {
232         NICK_REC *nickrec;
233         char *nick, *host;
234
235         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
236         g_return_val_if_fail(mask != NULL, NULL);
237
238         nick = g_strdup(mask);
239         host = strchr(nick, '!');
240         if (host != NULL) *host++ = '\0';
241
242         if (strchr(nick, '*') || strchr(nick, '?')) {
243                 g_free(nick);
244                 return nicklist_find_wildcards(channel, mask);
245         }
246
247         nickrec = g_hash_table_lookup(channel->nicks, nick);
248
249         if (host != NULL) {
250                 while (nickrec != NULL) {
251                         if (nickrec->host != NULL &&
252                             match_wildcards(host, nickrec->host))
253                                 break; /* match */
254                         nickrec = nickrec->next;
255                 }
256         }
257         g_free(nick);
258         return nickrec;
259 }
260
261 static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list)
262 {
263         while (rec != NULL) {
264                 *list = g_slist_append(*list, rec);
265                 rec = rec->next;
266         }
267 }
268
269 /* Get list of nicks */
270 GSList *nicklist_getnicks(CHANNEL_REC *channel)
271 {
272         GSList *list;
273
274         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
275
276         list = NULL;
277         g_hash_table_foreach(channel->nicks, (GHFunc) get_nicks_hash, &list);
278         return list;
279 }
280
281 typedef struct {
282         CHANNEL_REC *channel;
283         const char *nick;
284         GSList *list;
285 } NICKLIST_GET_SAME_REC;
286
287 static void get_nicks_same_hash(gpointer key, NICK_REC *nick,
288                                 NICKLIST_GET_SAME_REC *rec)
289 {
290         while (nick != NULL) {
291                 if (g_strcasecmp(nick->nick, rec->nick) == 0) {
292                         rec->list = g_slist_append(rec->list, rec->channel);
293                         rec->list = g_slist_append(rec->list, nick);
294                 }
295
296                 nick = nick->next;
297         }
298 }
299
300 GSList *nicklist_get_same(SERVER_REC *server, const char *nick)
301 {
302         NICKLIST_GET_SAME_REC rec;
303         GSList *tmp;
304
305         g_return_val_if_fail(IS_SERVER(server), NULL);
306
307         rec.nick = nick;
308         rec.list = NULL;
309         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
310                 rec.channel = tmp->data;
311                 g_hash_table_foreach(rec.channel->nicks,
312                                      (GHFunc) get_nicks_same_hash, &rec);
313         }
314         return rec.list;
315 }
316
317 typedef struct {
318         CHANNEL_REC *channel;
319         void *id;
320         GSList *list;
321 } NICKLIST_GET_SAME_UNIQUE_REC;
322
323 static void get_nicks_same_hash_unique(gpointer key, NICK_REC *nick,
324                                        NICKLIST_GET_SAME_UNIQUE_REC *rec)
325 {
326         while (nick != NULL) {
327                 if (nick->unique_id == rec->id) {
328                         rec->list = g_slist_append(rec->list, rec->channel);
329                         rec->list = g_slist_append(rec->list, nick);
330                         break;
331                 }
332
333                 nick = nick->next;
334         }
335 }
336
337 GSList *nicklist_get_same_unique(SERVER_REC *server, void *id)
338 {
339         NICKLIST_GET_SAME_UNIQUE_REC rec;
340         GSList *tmp;
341
342         g_return_val_if_fail(IS_SERVER(server), NULL);
343         g_return_val_if_fail(id != NULL, NULL);
344
345         rec.id = id;
346         rec.list = NULL;
347         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
348                 rec.channel = tmp->data;
349                 g_hash_table_foreach(rec.channel->nicks,
350                                      (GHFunc) get_nicks_same_hash_unique,
351                                      &rec);
352         }
353         return rec.list;
354 }
355
356 /* nick record comparision for sort functions */
357 int nicklist_compare(NICK_REC *p1, NICK_REC *p2)
358 {
359         int status1, status2;
360         
361         if (p1 == NULL) return -1;
362         if (p2 == NULL) return 1;
363
364         /* we assign each status (op, halfop, voice, normal) a number
365          * and compare them. this is easier than 100,000 if's and
366          * returns :-)
367          * -- yath */
368
369         if (p1->op)
370                 status1 = 4;
371         else if (p1->halfop)
372                 status1 = 3;
373         else if (p1->voice)
374                 status1 = 2;
375         else
376                 status1 = 1;
377
378         if (p2->op)
379                 status2 = 4;
380         else if (p2->halfop)
381                 status2 = 3;
382         else if (p2->voice)
383                 status2 = 2;
384         else
385                 status2 = 1;
386         
387         if (status1 < status2)
388                 return 1;
389         else if (status1 > status2)
390                 return -1;
391         
392         return g_strcasecmp(p1->nick, p2->nick);
393 }
394
395 static void nicklist_update_flags_list(SERVER_REC *server, int gone,
396                                        int serverop, GSList *nicks)
397 {
398         GSList *tmp;
399         CHANNEL_REC *channel;
400         NICK_REC *rec;
401
402         g_return_if_fail(IS_SERVER(server));
403
404         for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
405                 channel = tmp->data;
406                 rec = tmp->next->data;
407
408                 rec->last_check = time(NULL);
409
410                 if (gone != -1 && (int)rec->gone != gone) {
411                         rec->gone = gone;
412                         signal_emit("nicklist gone changed", 2, channel, rec);
413                 }
414
415                 if (serverop != -1 && (int)rec->serverop != serverop) {
416                         rec->serverop = serverop;
417                         signal_emit("nicklist serverop changed", 2, channel, rec);
418                 }
419         }
420         g_slist_free(nicks);
421 }
422
423 void nicklist_update_flags(SERVER_REC *server, const char *nick,
424                            int gone, int serverop)
425 {
426         nicklist_update_flags_list(server, gone, serverop,
427                                    nicklist_get_same(server, nick));
428 }
429
430 void nicklist_update_flags_unique(SERVER_REC *server, void *id,
431                                   int gone, int serverop)
432 {
433         nicklist_update_flags_list(server, gone, serverop,
434                                    nicklist_get_same_unique(server, id));
435 }
436
437 /* Specify which nick in channel is ours */
438 void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick)
439 {
440         NICK_REC *first, *next;
441
442         channel->ownnick = nick;
443
444         /* move our nick in the list to first, makes some things easier
445            (like handling multiple identical nicks in fe-messages.c) */
446         first = g_hash_table_lookup(channel->nicks, nick->nick);
447         if (first->next == NULL)
448                 return;
449
450         next = nick->next;
451         nick->next = first;
452
453         while (first->next != nick)
454                 first = first->next;
455         first->next = next;
456
457         g_hash_table_insert(channel->nicks, nick->nick, nick);
458 }
459
460 static void sig_channel_created(CHANNEL_REC *channel)
461 {
462         g_return_if_fail(IS_CHANNEL(channel));
463
464         channel->nicks = g_hash_table_new((GHashFunc) g_istr_hash,
465                                           (GCompareFunc) g_istr_equal);
466 }
467
468 static void nicklist_remove_hash(gpointer key, NICK_REC *nick,
469                                  CHANNEL_REC *channel)
470 {
471         NICK_REC *next;
472
473         while (nick != NULL) {
474                 next = nick->next;
475                 nicklist_destroy(channel, nick);
476                 nick = next;
477         }
478 }
479
480 static void sig_channel_destroyed(CHANNEL_REC *channel)
481 {
482         g_return_if_fail(IS_CHANNEL(channel));
483
484         g_hash_table_foreach(channel->nicks,
485                              (GHFunc) nicklist_remove_hash, channel);
486         g_hash_table_destroy(channel->nicks);
487 }
488
489 static NICK_REC *nick_nfind(CHANNEL_REC *channel, const char *nick, int len)
490 {
491         NICK_REC *rec;
492         char *tmpnick;
493
494         tmpnick = g_strndup(nick, len);
495         rec = g_hash_table_lookup(channel->nicks, tmpnick);
496
497         if (rec != NULL) {
498                 /* if there's multiple, get the one with identical case */
499                 while (rec->next != NULL) {
500                         if (strcmp(rec->nick, tmpnick) == 0)
501                                 break;
502                         rec = rec->next;
503                 }
504         }
505
506         g_free(tmpnick);
507         return rec;
508 }
509
510 /* Check is `msg' is meant for `nick'. */
511 int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick)
512 {
513         const char *msgstart, *orignick;
514         int len, fullmatch;
515
516         g_return_val_if_fail(nick != NULL, FALSE);
517         g_return_val_if_fail(msg != NULL, FALSE);
518
519         if (channel != NULL && channel->server->nick_match_msg != NULL)
520                 return channel->server->nick_match_msg(msg, nick);
521
522         /* first check for identical match */
523         len = strlen(nick);
524         if (g_strncasecmp(msg, nick, len) == 0 && !isalnumhigh((int) msg[len]))
525                 return TRUE;
526
527         orignick = nick;
528         for (;;) {
529                 nick = orignick;
530                 msgstart = msg;
531                 fullmatch = TRUE;
532
533                 /* check if it matches for alphanumeric parts of nick */
534                 while (*nick != '\0' && *msg != '\0') {
535                         if (i_toupper(*nick) == i_toupper(*msg)) {
536                                 /* total match */
537                                 msg++;
538                         } else if (i_isalnum(*msg) && !i_isalnum(*nick)) {
539                                 /* some strange char in your nick, pass it */
540                                 fullmatch = FALSE;
541                         } else
542                                 break;
543
544                         nick++;
545                 }
546
547                 if (msg != msgstart && !isalnumhigh(*msg)) {
548                         /* at least some of the chars in line matched the
549                            nick, and msg continue with non-alphanum character,
550                            this might be for us.. */
551                         if (*nick != '\0') {
552                                 /* remove the rest of the non-alphanum chars
553                                    from nick and check if it then matches. */
554                                 fullmatch = FALSE;
555                                 while (*nick != '\0' && !i_isalnum(*nick))
556                                         nick++;
557                         }
558
559                         if (*nick == '\0') {
560                                 /* yes, match! */
561                                 break;
562                         }
563                 }
564
565                 /* no match. check if this is a message to multiple people
566                    (like nick1,nick2: text) */
567                 while (*msg != '\0' && *msg != ' ' && *msg != ',') msg++;
568
569                 if (*msg != ',') {
570                         nick = orignick;
571                         break;
572                 }
573
574                 msg++;
575         }
576
577         if (*nick != '\0')
578                 return FALSE; /* didn't match */
579
580         if (fullmatch)
581                 return TRUE; /* matched without fuzzyness */
582
583         /* matched with some fuzzyness .. check if there's an exact match
584            for some other nick in the same channel. */
585         return nick_nfind(channel, msgstart, (int) (msg-msgstart)) == NULL;
586 }
587
588 void nicklist_init(void)
589 {
590         signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
591         signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
592 }
593
594 void nicklist_deinit(void)
595 {
596         signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
597         signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
598
599         module_uniq_destroy("NICK");
600 }