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