1 /* X-Chat
2  * Copyright (C) 1998 Peter Zelezny.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include <time.h>
26 
27 #ifdef WIN32
28 #include <io.h>
29 #else
30 #include <unistd.h>
31 #endif
32 
33 #include "hexchat.h"
34 #include "notify.h"
35 #include "cfgfiles.h"
36 #include "fe.h"
37 #include "server.h"
38 #include "text.h"
39 #include "util.h"
40 #include "hexchatc.h"
41 
42 
43 GSList *notify_list = 0;
44 int notify_tag = 0;
45 
46 
47 static char *
despacify_dup(char * str)48 despacify_dup (char *str)
49 {
50 	char *p, *res = g_malloc (strlen (str) + 1);
51 
52 	p = res;
53 	while (1)
54 	{
55 		if (*str != ' ')
56 		{
57 			*p = *str;
58 			if (*p == 0)
59 				return res;
60 			p++;
61 		}
62 		str++;
63 	}
64 }
65 
66 static int
notify_netcmp(char * str,void * serv)67 notify_netcmp (char *str, void *serv)
68 {
69 	char *net = despacify_dup (server_get_network (serv, TRUE));
70 
71 	if (rfc_casecmp (str, net) == 0)
72 	{
73 		g_free (net);
74 		return 0;	/* finish & return FALSE from token_foreach() */
75 	}
76 
77 	g_free (net);
78 	return 1;	/* keep going... */
79 }
80 
81 /* monitor this nick on this particular network? */
82 
83 static gboolean
notify_do_network(struct notify * notify,server * serv)84 notify_do_network (struct notify *notify, server *serv)
85 {
86 	if (!notify->networks)	/* ALL networks for this nick */
87 		return TRUE;
88 
89 	if (token_foreach (notify->networks, ',', notify_netcmp, serv))
90 		return FALSE;	/* network list doesn't contain this one */
91 
92 	return TRUE;
93 }
94 
95 struct notify_per_server *
notify_find_server_entry(struct notify * notify,struct server * serv)96 notify_find_server_entry (struct notify *notify, struct server *serv)
97 {
98 	GSList *list = notify->server_list;
99 	struct notify_per_server *servnot;
100 
101 	while (list)
102 	{
103 		servnot = (struct notify_per_server *) list->data;
104 		if (servnot->server == serv)
105 			return servnot;
106 		list = list->next;
107 	}
108 
109 	/* not found, should we add it, or is this not a network where
110       we're monitoring this nick? */
111 	if (!notify_do_network (notify, serv))
112 		return NULL;
113 
114 	servnot = g_new0 (struct notify_per_server, 1);
115 	servnot->server = serv;
116 	servnot->notify = notify;
117 	notify->server_list = g_slist_prepend (notify->server_list, servnot);
118 	return servnot;
119 }
120 
121 void
notify_save(void)122 notify_save (void)
123 {
124 	int fh;
125 	struct notify *notify;
126 	GSList *list = notify_list;
127 
128 	fh = hexchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
129 	if (fh != -1)
130 	{
131 		while (list)
132 		{
133 			notify = (struct notify *) list->data;
134 			write (fh, notify->name, strlen (notify->name));
135 			if (notify->networks)
136 			{
137 				write (fh, " ", 1);
138 				write (fh, notify->networks, strlen (notify->networks));
139 			}
140 			write (fh, "\n", 1);
141 			list = list->next;
142 		}
143 		close (fh);
144 	}
145 }
146 
147 void
notify_load(void)148 notify_load (void)
149 {
150 	int fh;
151 	char buf[256];
152 	char *sep;
153 
154 	fh = hexchat_open_file ("notify.conf", O_RDONLY, 0, 0);
155 	if (fh != -1)
156 	{
157 		while (waitline (fh, buf, sizeof buf, FALSE) != -1)
158 		{
159 			if (buf[0] != '#' && buf[0] != 0)
160 			{
161 				sep = strchr (buf, ' ');
162 				if (sep)
163 				{
164 					sep[0] = 0;
165 					notify_adduser (buf, sep + 1);
166 				}
167 				else
168 					notify_adduser (buf, NULL);
169 			}
170 		}
171 		close (fh);
172 	}
173 }
174 
175 static struct notify_per_server *
notify_find(server * serv,char * nick)176 notify_find (server *serv, char *nick)
177 {
178 	GSList *list = notify_list;
179 	struct notify_per_server *servnot;
180 	struct notify *notify;
181 
182 	while (list)
183 	{
184 		notify = (struct notify *) list->data;
185 
186 		servnot = notify_find_server_entry (notify, serv);
187 		if (!servnot)
188 		{
189 			list = list->next;
190 			continue;
191 		}
192 
193 		if (!serv->p_cmp (notify->name, nick))
194 			return servnot;
195 
196 		list = list->next;
197 	}
198 
199 	return NULL;
200 }
201 
202 static void
notify_announce_offline(server * serv,struct notify_per_server * servnot,char * nick,int quiet,const message_tags_data * tags_data)203 notify_announce_offline (server * serv, struct notify_per_server *servnot,
204 								 char *nick, int quiet,
205 								 const message_tags_data *tags_data)
206 {
207 	session *sess;
208 
209 	sess = serv->front_session;
210 
211 	servnot->ison = FALSE;
212 	servnot->lastoff = time (0);
213 	if (!quiet)
214 		EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYOFFLINE, sess, nick, serv->servername,
215 									  server_get_network (serv, TRUE), NULL, 0,
216 									  tags_data->timestamp);
217 	fe_notify_update (nick);
218 	fe_notify_update (0);
219 }
220 
221 static void
notify_announce_online(server * serv,struct notify_per_server * servnot,char * nick,const message_tags_data * tags_data)222 notify_announce_online (server * serv, struct notify_per_server *servnot,
223 								char *nick, const message_tags_data *tags_data)
224 {
225 	session *sess;
226 
227 	sess = serv->front_session;
228 
229 	servnot->lastseen = time (0);
230 	if (servnot->ison)
231 		return;
232 
233 	servnot->ison = TRUE;
234 	servnot->laston = time (0);
235 	EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYONLINE, sess, nick, serv->servername,
236 					 server_get_network (serv, TRUE), NULL, 0,
237 					 tags_data->timestamp);
238 	fe_notify_update (nick);
239 	fe_notify_update (0);
240 
241 	if (prefs.hex_notify_whois_online)
242 	{
243 
244 	    /* Let's do whois with idle time (like in /quote WHOIS %s %s) */
245 
246 	    char *wii_str = g_strdup_printf ("%s %s", nick, nick);
247 	    serv->p_whois (serv, wii_str);
248 	    g_free (wii_str);
249 	}
250 }
251 
252 /* handles numeric 601 */
253 
254 void
notify_set_offline(server * serv,char * nick,int quiet,const message_tags_data * tags_data)255 notify_set_offline (server * serv, char *nick, int quiet,
256 						  const message_tags_data *tags_data)
257 {
258 	struct notify_per_server *servnot;
259 
260 	servnot = notify_find (serv, nick);
261 	if (!servnot)
262 		return;
263 
264 	notify_announce_offline (serv, servnot, nick, quiet, tags_data);
265 }
266 
267 /* handles numeric 604 and 600 */
268 
269 void
notify_set_online(server * serv,char * nick,const message_tags_data * tags_data)270 notify_set_online (server * serv, char *nick,
271 						 const message_tags_data *tags_data)
272 {
273 	struct notify_per_server *servnot;
274 
275 	servnot = notify_find (serv, nick);
276 	if (!servnot)
277 		return;
278 
279 	notify_announce_online (serv, servnot, nick, tags_data);
280 }
281 
282 /* monitor can send lists for numeric 730/731 */
283 
284 void
notify_set_offline_list(server * serv,char * users,int quiet,const message_tags_data * tags_data)285 notify_set_offline_list (server * serv, char *users, int quiet,
286 						  const message_tags_data *tags_data)
287 {
288 	struct notify_per_server *servnot;
289 	char nick[NICKLEN];
290 	char *token, *chr;
291 
292 	token = strtok (users, ",");
293 	while (token != NULL)
294 	{
295 		chr = strchr (token, '!');
296 		if (chr != NULL)
297 			*chr = '\0';
298 
299 		g_strlcpy (nick, token, sizeof(nick));
300 
301 		servnot = notify_find (serv, nick);
302 		if (servnot)
303 			notify_announce_offline (serv, servnot, nick, quiet, tags_data);
304 
305 		token = strtok (NULL, ",");
306 	}
307 }
308 
309 void
notify_set_online_list(server * serv,char * users,const message_tags_data * tags_data)310 notify_set_online_list (server * serv, char *users,
311 						 const message_tags_data *tags_data)
312 {
313 	struct notify_per_server *servnot;
314 	char nick[NICKLEN];
315 	char *token, *chr;
316 
317 	token = strtok (users, ",");
318 	while (token != NULL)
319 	{
320 		chr = strchr (token, '!');
321 		if (chr != NULL)
322 			*chr = '\0';
323 
324 		g_strlcpy (nick, token, sizeof(nick));
325 
326 		servnot = notify_find (serv, nick);
327 		if (servnot)
328 			notify_announce_online (serv, servnot, nick, tags_data);
329 
330 		token = strtok (NULL, ",");
331 	}
332 }
333 
334 static void
notify_watch(server * serv,char * nick,int add)335 notify_watch (server * serv, char *nick, int add)
336 {
337 	char tbuf[256];
338 	char addchar = '+';
339 
340 	if (!add)
341 		addchar = '-';
342 
343 	if (serv->supports_monitor)
344 		g_snprintf (tbuf, sizeof (tbuf), "MONITOR %c %s", addchar, nick);
345 	else if (serv->supports_watch)
346 		g_snprintf (tbuf, sizeof (tbuf), "WATCH %c%s", addchar, nick);
347 	else
348 		return;
349 
350 	serv->p_raw (serv, tbuf);
351 }
352 
353 static void
notify_watch_all(struct notify * notify,int add)354 notify_watch_all (struct notify *notify, int add)
355 {
356 	server *serv;
357 	GSList *list = serv_list;
358 	while (list)
359 	{
360 		serv = list->data;
361 		if (serv->connected && serv->end_of_motd && notify_do_network (notify, serv))
362 			notify_watch (serv, notify->name, add);
363 		list = list->next;
364 	}
365 }
366 
367 static void
notify_flush_watches(server * serv,GSList * from,GSList * end)368 notify_flush_watches (server * serv, GSList *from, GSList *end)
369 {
370 	char tbuf[512];
371 	GSList *list;
372 	struct notify *notify;
373 
374 	serv->supports_monitor ? strcpy (tbuf, "MONITOR + ") : strcpy (tbuf, "WATCH");
375 
376 	list = from;
377 	while (list != end)
378 	{
379 		notify = list->data;
380 		if (serv->supports_monitor)
381 			g_strlcat (tbuf, ",", sizeof(tbuf));
382 		else
383 			g_strlcat (tbuf, " +", sizeof(tbuf));
384 		g_strlcat (tbuf, notify->name, sizeof(tbuf));
385 		list = list->next;
386 	}
387 	serv->p_raw (serv, tbuf);
388 }
389 
390 /* called when logging in. e.g. when End of motd. */
391 
392 void
notify_send_watches(server * serv)393 notify_send_watches (server * serv)
394 {
395 	struct notify *notify;
396 	const int format_len = serv->supports_monitor ? 1 : 2; /* just , for monitor or + and space for watch */
397 	GSList *list;
398 	GSList *point;
399 	GSList *send_list = NULL;
400 	int len = 0;
401 
402 	/* Only get the list for this network */
403 	list = notify_list;
404 	while (list)
405 	{
406 		notify = list->data;
407 
408 		if (notify_do_network (notify, serv))
409 		{
410 			send_list = g_slist_append (send_list, notify);
411 		}
412 
413 		list = list->next;
414 	}
415 
416 	/* Now send that list in batches */
417 	point = list = send_list;
418 	while (list)
419 	{
420 		notify = list->data;
421 
422 		len += strlen (notify->name) + format_len;
423 		if (len > 500)
424 		{
425 			/* Too long send existing list */
426 			notify_flush_watches (serv, point, list);
427 			len = strlen (notify->name) + format_len;
428 			point = list; /* We left off here */
429 		}
430 
431 		list = g_slist_next (list);
432 	}
433 
434 	if (len) /* We had leftovers under 500, send them all */
435 	{
436 		notify_flush_watches (serv, point, NULL);
437 	}
438 
439 	g_slist_free (send_list);
440 }
441 
442 /* called when receiving a ISON 303 - should this func go? */
443 
444 void
notify_markonline(server * serv,char * word[],const message_tags_data * tags_data)445 notify_markonline (server *serv, char *word[], const message_tags_data *tags_data)
446 {
447 	struct notify *notify;
448 	struct notify_per_server *servnot;
449 	GSList *list = notify_list;
450 	int i, seen;
451 
452 	while (list)
453 	{
454 		notify = (struct notify *) list->data;
455 		servnot = notify_find_server_entry (notify, serv);
456 		if (!servnot)
457 		{
458 			list = list->next;
459 			continue;
460 		}
461 		i = 4;
462 		seen = FALSE;
463 		while (*word[i])
464 		{
465 			if (!serv->p_cmp (notify->name, word[i]))
466 			{
467 				seen = TRUE;
468 				notify_announce_online (serv, servnot, notify->name, tags_data);
469 				break;
470 			}
471 			i++;
472 			/* FIXME: word[] is only a 32 element array, limits notify list to
473 			   about 27 people */
474 			if (i > PDIWORDS - 5)
475 			{
476 				/*fprintf (stderr, _("*** HEXCHAT WARNING: notify list too large.\n"));*/
477 				break;
478 			}
479 		}
480 		if (!seen && servnot->ison)
481 		{
482 			notify_announce_offline (serv, servnot, notify->name, FALSE, tags_data);
483 		}
484 		list = list->next;
485 	}
486 	fe_notify_update (0);
487 }
488 
489 /* yuck! Old routine for ISON notify */
490 
491 static void
notify_checklist_for_server(server * serv)492 notify_checklist_for_server (server *serv)
493 {
494 	char outbuf[512];
495 	struct notify *notify;
496 	GSList *list = notify_list;
497 	int i = 0;
498 
499 	strcpy (outbuf, "ISON ");
500 	while (list)
501 	{
502 		notify = list->data;
503 		if (notify_do_network (notify, serv))
504 		{
505 			i++;
506 			strcat (outbuf, notify->name);
507 			strcat (outbuf, " ");
508 			if (strlen (outbuf) > 460)
509 			{
510 				/* LAME: we can't send more than 512 bytes to the server, but     *
511 				 * if we split it in two packets, our offline detection wouldn't  *
512 				 work                                                           */
513 				/*fprintf (stderr, _("*** HEXCHAT WARNING: notify list too large.\n"));*/
514 				break;
515 			}
516 		}
517 		list = list->next;
518 	}
519 
520 	if (i)
521 		serv->p_raw (serv, outbuf);
522 }
523 
524 int
notify_checklist(void)525 notify_checklist (void)	/* check ISON list */
526 {
527 	struct server *serv;
528 	GSList *list = serv_list;
529 
530 	while (list)
531 	{
532 		serv = list->data;
533 		if (serv->connected && serv->end_of_motd && !serv->supports_watch && !serv->supports_monitor)
534 		{
535 			notify_checklist_for_server (serv);
536 		}
537 		list = list->next;
538 	}
539 	return 1;
540 }
541 
542 void
notify_showlist(struct session * sess,const message_tags_data * tags_data)543 notify_showlist (struct session *sess, const message_tags_data *tags_data)
544 {
545 	char outbuf[256];
546 	struct notify *notify;
547 	GSList *list = notify_list;
548 	struct notify_per_server *servnot;
549 	int i = 0;
550 
551 	EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYHEAD, sess, NULL, NULL, NULL, NULL, 0,
552 								  tags_data->timestamp);
553 	while (list)
554 	{
555 		i++;
556 		notify = (struct notify *) list->data;
557 		servnot = notify_find_server_entry (notify, sess->server);
558 		if (servnot && servnot->ison)
559 			g_snprintf (outbuf, sizeof (outbuf), _("  %-20s online\n"), notify->name);
560 		else
561 			g_snprintf (outbuf, sizeof (outbuf), _("  %-20s offline\n"), notify->name);
562 		PrintTextTimeStamp (sess, outbuf, tags_data->timestamp);
563 		list = list->next;
564 	}
565 	if (i)
566 	{
567 		sprintf (outbuf, "%d", i);
568 		EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYNUMBER, sess, outbuf, NULL, NULL, NULL,
569 									  0, tags_data->timestamp);
570 	} else
571 		EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYEMPTY, sess, NULL, NULL, NULL, NULL, 0,
572 									  tags_data->timestamp);
573 }
574 
575 int
notify_deluser(char * name)576 notify_deluser (char *name)
577 {
578 	struct notify *notify;
579 	struct notify_per_server *servnot;
580 	GSList *list = notify_list;
581 
582 	while (list)
583 	{
584 		notify = (struct notify *) list->data;
585 		if (!rfc_casecmp (notify->name, name))
586 		{
587 			fe_notify_update (notify->name);
588 			/* Remove the records for each server */
589 			while (notify->server_list)
590 			{
591 				servnot = (struct notify_per_server *) notify->server_list->data;
592 				notify->server_list =
593 					g_slist_remove (notify->server_list, servnot);
594 				g_free (servnot);
595 			}
596 			notify_list = g_slist_remove (notify_list, notify);
597 			notify_watch_all (notify, FALSE);
598 			g_free (notify->networks);
599 			g_free (notify->name);
600 			g_free (notify);
601 			fe_notify_update (0);
602 			return 1;
603 		}
604 		list = list->next;
605 	}
606 	return 0;
607 }
608 
609 void
notify_adduser(char * name,char * networks)610 notify_adduser (char *name, char *networks)
611 {
612 	struct notify *notify = g_new0 (struct notify, 1);
613 
614 	notify->name = g_strndup (name, NICKLEN - 1);
615 
616 	if (networks != NULL)
617 		notify->networks = despacify_dup (networks);
618 	notify->server_list = 0;
619 	notify_list = g_slist_prepend (notify_list, notify);
620 	notify_checklist ();
621 	fe_notify_update (notify->name);
622 	fe_notify_update (0);
623 	notify_watch_all (notify, TRUE);
624 }
625 
626 gboolean
notify_is_in_list(server * serv,char * name)627 notify_is_in_list (server *serv, char *name)
628 {
629 	struct notify *notify;
630 	GSList *list = notify_list;
631 
632 	while (list)
633 	{
634 		notify = (struct notify *) list->data;
635 		if (!serv->p_cmp (notify->name, name))
636 			return TRUE;
637 		list = list->next;
638 	}
639 
640 	return FALSE;
641 }
642 
643 int
notify_isnotify(struct session * sess,char * name)644 notify_isnotify (struct session *sess, char *name)
645 {
646 	struct notify *notify;
647 	struct notify_per_server *servnot;
648 	GSList *list = notify_list;
649 
650 	while (list)
651 	{
652 		notify = (struct notify *) list->data;
653 		if (!sess->server->p_cmp (notify->name, name))
654 		{
655 			servnot = notify_find_server_entry (notify, sess->server);
656 			if (servnot && servnot->ison)
657 				return TRUE;
658 		}
659 		list = list->next;
660 	}
661 
662 	return FALSE;
663 }
664 
665 void
notify_cleanup()666 notify_cleanup ()
667 {
668 	GSList *list = notify_list;
669 	GSList *nslist, *srvlist;
670 	struct notify *notify;
671 	struct notify_per_server *servnot;
672 	struct server *serv;
673 	int valid;
674 
675 	while (list)
676 	{
677 		/* Traverse the list of notify structures */
678 		notify = (struct notify *) list->data;
679 		nslist = notify->server_list;
680 		while (nslist)
681 		{
682 			/* Look at each per-server structure */
683 			servnot = (struct notify_per_server *) nslist->data;
684 
685 			/* Check the server is valid */
686 			valid = FALSE;
687 			srvlist = serv_list;
688 			while (srvlist)
689 			{
690 				serv = (struct server *) srvlist->data;
691 				if (servnot->server == serv)
692 				{
693 					valid = serv->connected;	/* Only valid if server is too */
694 					break;
695 				}
696 				srvlist = srvlist->next;
697 			}
698 			if (!valid)
699 			{
700 				notify->server_list =
701 					g_slist_remove (notify->server_list, servnot);
702 				g_free (servnot);
703 				nslist = notify->server_list;
704 			} else
705 			{
706 				nslist = nslist->next;
707 			}
708 		}
709 		list = list->next;
710 	}
711 	fe_notify_update (0);
712 }
713