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