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 <string.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 
23 #include "hexchat.h"
24 #include "hexchatc.h"
25 #include "modes.h"
26 #include "server.h"
27 #include "text.h"
28 #include "fe.h"
29 #include "util.h"
30 #include "inbound.h"
31 #ifdef HAVE_STRINGS_H
32 #include <strings.h>
33 #endif
34 
35 #include <glib/gprintf.h>
36 
37 typedef struct
38 {
39 	server *serv;
40 	char *op;
41 	char *deop;
42 	char *voice;
43 	char *devoice;
44 } mode_run;
45 
46 static int is_prefix_char (server * serv, char c);
47 static void record_chan_mode (session *sess, char sign, char mode, char *arg);
48 static char *mode_cat (char *str, char *addition);
49 static void handle_single_mode (mode_run *mr, char sign, char mode, char *nick,
50 										  char *chan, char *arg, int quiet, int is_324,
51 										  const message_tags_data *tags_data);
52 static int mode_has_arg (server *serv, char sign, char mode);
53 static void mode_print_grouped (session *sess, char *nick, mode_run *mr,
54 										  const message_tags_data *tags_data);
55 static int mode_chanmode_type (server * serv, char mode);
56 
57 
58 /* word[] - list of nicks.
59    wpos   - index into word[]. Where nicks really start.
60    end    - index into word[]. Last entry plus one.
61    sign   - a char, e.g. '+' or '-'
62    mode   - a mode, e.g. 'o' or 'v'	*/
63 void
send_channel_modes(session * sess,char * tbuf,char * word[],int wpos,int end,char sign,char mode,int modes_per_line)64 send_channel_modes (session *sess, char *tbuf, char *word[], int wpos,
65 						  int end, char sign, char mode, int modes_per_line)
66 {
67 	int usable_modes, orig_len, len, wlen, i, max;
68 	server *serv = sess->server;
69 
70 	/* sanity check. IRC RFC says three per line but some servers may support less. */
71 	if (serv->modes_per_line < 1)
72 		serv->modes_per_line = 3;
73 	if (modes_per_line < 1)
74 		modes_per_line = serv->modes_per_line;
75 
76 	/* RFC max, minus length of "MODE %s " and "\r\n" and 1 +/- sign */
77 	/* 512 - 6 - 2 - 1 - strlen(chan) */
78 	max = 503 - strlen (sess->channel);
79 
80 	while (wpos < end)
81 	{
82 		tbuf[0] = '\0';
83 		orig_len = len = 0;
84 
85 		/* we'll need this many modechars too */
86 		len += modes_per_line;
87 
88 		/* how many can we fit? */
89 		for (i = 0; i < modes_per_line; i++)
90 		{
91 			/* no more nicks left? */
92 			if (wpos + i >= end)
93 				break;
94 			wlen = strlen (word[wpos + i]) + 1;
95 			if (wlen + len > max)
96 				break;
97 			len += wlen; /* length of our whole string so far */
98 		}
99 		if (i < 1)
100 			return;
101 		usable_modes = i;	/* this is how many we'll send on this line */
102 
103 		/* add the +/-modemodemodemode */
104 		len = orig_len;
105 		tbuf[len] = sign;
106 		len++;
107 		for (i = 0; i < usable_modes; i++)
108 		{
109 			tbuf[len] = mode;
110 			len++;
111 		}
112 		tbuf[len] = 0;	/* null terminate for the strcat() to work */
113 
114 		/* add all the nicknames */
115 		for (i = 0; i < usable_modes; i++)
116 		{
117 			strcat (tbuf, " ");
118 			strcat (tbuf, word[wpos + i]);
119 		}
120 		serv->p_mode (serv, sess->channel, tbuf);
121 
122 		wpos += usable_modes;
123 	}
124 }
125 
126 /* does 'chan' have a valid prefix? e.g. # or & */
127 
128 int
is_channel(server * serv,char * chan)129 is_channel (server * serv, char *chan)
130 {
131 	if (strchr (serv->chantypes, chan[0]))
132 		return 1;
133 	return 0;
134 }
135 
136 /* is the given char a valid nick mode char? e.g. @ or + */
137 
138 static int
is_prefix_char(server * serv,char c)139 is_prefix_char (server * serv, char c)
140 {
141 	int pos = 0;
142 	char *np = serv->nick_prefixes;
143 
144 	while (np[0])
145 	{
146 		if (np[0] == c)
147 			return pos;
148 		pos++;
149 		np++;
150 	}
151 
152 	if (serv->bad_prefix)
153 	{
154 		if (strchr (serv->bad_nick_prefixes, c))
155 		/* valid prefix char, but mode unknown */
156 			return -2;
157 	}
158 
159 	return -1;
160 }
161 
162 /* returns '@' for ops etc... */
163 
164 char
get_nick_prefix(server * serv,unsigned int access)165 get_nick_prefix (server * serv, unsigned int access)
166 {
167 	int pos;
168 	char c;
169 
170 	for (pos = 0; pos < USERACCESS_SIZE; pos++)
171 	{
172 		c = serv->nick_prefixes[pos];
173 		if (c == 0)
174 			break;
175 		if (access & (1 << pos))
176 			return c;
177 	}
178 
179 	return 0;
180 }
181 
182 /* returns the access bitfield for a nickname. E.g.
183 	@nick would return 000010 in binary
184 	%nick would return 000100 in binary
185 	+nick would return 001000 in binary */
186 
187 unsigned int
nick_access(server * serv,char * nick,int * modechars)188 nick_access (server * serv, char *nick, int *modechars)
189 {
190 	int i;
191 	unsigned int access = 0;
192 	char *orig = nick;
193 
194 	while (*nick)
195 	{
196 		i = is_prefix_char (serv, *nick);
197 		if (i == -1)
198 			break;
199 
200 		/* -2 == valid prefix char, but mode unknown */
201 		if (i != -2)
202 			access |= (1 << i);
203 
204 		nick++;
205 	}
206 
207 	*modechars = nick - orig;
208 
209 	return access;
210 }
211 
212 /* returns the access number for a particular mode. e.g.
213 	mode 'a' returns 0
214 	mode 'o' returns 1
215 	mode 'h' returns 2
216 	mode 'v' returns 3
217 	Also puts the nick-prefix-char in 'prefix' */
218 
219 int
mode_access(server * serv,char mode,char * prefix)220 mode_access (server * serv, char mode, char *prefix)
221 {
222 	int pos = 0;
223 
224 	while (serv->nick_modes[pos])
225 	{
226 		if (serv->nick_modes[pos] == mode)
227 		{
228 			*prefix = serv->nick_prefixes[pos];
229 			return pos;
230 		}
231 		pos++;
232 	}
233 
234 	*prefix = 0;
235 
236 	return -1;
237 }
238 
239 static void
record_chan_mode(session * sess,char sign,char mode,char * arg)240 record_chan_mode (session *sess, char sign, char mode, char *arg)
241 {
242 	/* Somebody needed to acutally update sess->current_modes, needed to
243 		play nice with bouncers, and less mode calls. Also keeps modes up
244 		to date for scripts */
245 	server *serv = sess->server;
246 	GString *current = g_string_new(sess->current_modes);
247 	gint mode_pos = -1;
248 	gchar *current_char = current->str;
249 	gint modes_length;
250 	gint argument_num = 0;
251 	gint argument_offset = 0;
252 	gint argument_length = 0;
253 	int i = 0;
254 	gchar *arguments_start;
255 
256 	/* find out if the mode currently exists */
257 	arguments_start = g_strstr_len(current->str	, -1, " ");
258 	if (arguments_start) {
259 		modes_length = arguments_start - current->str;
260 	}
261 	else {
262 		modes_length = current->len;
263 		/* set this to the end of the modes */
264 		arguments_start = current->str + current->len;
265 	}
266 
267 	while (mode_pos == -1 && i < modes_length)
268 	{
269 		if (*current_char == mode)
270 		{
271 			mode_pos = i;
272 		}
273 		else
274 		{
275 			i++;
276 			current_char++;
277 		}
278 	}
279 
280 	/* if the mode currently exists and has an arg, need to know where
281 	 * (including leading space) */
282 	if (mode_pos != -1 && mode_has_arg(serv, '+', mode))
283 	{
284 		current_char = current->str;
285 
286 		i = 0;
287 		while (i <= mode_pos)
288 		{
289 			if (mode_has_arg(serv, '+', *current_char))
290 				argument_num++;
291 			current_char++;
292 			i++;
293 		}
294 
295 		/* check through arguments for where to start */
296 		current_char = arguments_start;
297 		i = 0;
298 		while (i < argument_num && *current_char != '\0')
299 		{
300 			if (*current_char == ' ')
301 				i++;
302 			if (i != argument_num)
303 				current_char++;
304 		}
305 		argument_offset = current_char - current->str;
306 
307 		/* how long the existing argument is for this key
308 		 * important for malloc and strncpy */
309 		if (i == argument_num)
310 		{
311 			argument_length++;
312 			current_char++;
313 			while (*current_char != '\0' && *current_char != ' ')
314 			{
315 				argument_length++;
316 				current_char++;
317 			}
318 		}
319 	}
320 
321 	/* two cases, adding and removing a mode, handled differently */
322 	if (sign == '+')
323 	{
324 		if (mode_pos != -1)
325 		{
326 			/* if it already exists, only need to do something (change)
327 			 * if there should be a param */
328 			if (mode_has_arg(serv, sign, mode))
329 			{
330 				/* leave the old space there */
331 				current = g_string_erase(current, argument_offset+1, argument_length-1);
332 				current = g_string_insert(current, argument_offset+1, arg);
333 
334 				g_free(sess->current_modes);
335 				sess->current_modes = g_string_free(current, FALSE);
336 			}
337 		}
338 		/* mode wasn't there before */
339 		else
340 		{
341 			/* insert the new mode character */
342 			current = g_string_insert_c(current, modes_length, mode);
343 
344 			/* add the argument, with space if there is one */
345 			if (mode_has_arg(serv, sign, mode))
346 			{
347 				current = g_string_append_c(current, ' ');
348 				current = g_string_append(current, arg);
349 			}
350 
351 			g_free(sess->current_modes);
352 			sess->current_modes = g_string_free(current, FALSE);
353 		}
354 	}
355 	else if (sign == '-' && mode_pos != -1)
356 	{
357 		/* remove the argument first if it has one*/
358 		if (mode_has_arg(serv, '+', mode))
359 			current = g_string_erase(current, argument_offset, argument_length);
360 
361 		/* remove the mode character */
362 		current = g_string_erase(current, mode_pos, 1);
363 
364 		g_free(sess->current_modes);
365 		sess->current_modes = g_string_free(current, FALSE);
366 	}
367 }
368 
369 static char *
mode_cat(char * str,char * addition)370 mode_cat (char *str, char *addition)
371 {
372 	int len;
373 
374 	if (str)
375 	{
376 		len = strlen (str) + strlen (addition) + 2;
377 		str = g_realloc (str, len);
378 		strcat (str, " ");
379 		strcat (str, addition);
380 	}
381 	else
382 	{
383 		str = g_strdup (addition);
384 	}
385 
386 	return str;
387 }
388 
389 /* handle one mode, e.g.
390    handle_single_mode (mr,'+','b',"elite","#warez","banneduser",) */
391 
392 static void
handle_single_mode(mode_run * mr,char sign,char mode,char * nick,char * chan,char * arg,int quiet,int is_324,const message_tags_data * tags_data)393 handle_single_mode (mode_run *mr, char sign, char mode, char *nick,
394 						  char *chan, char *arg, int quiet, int is_324,
395 						  const message_tags_data *tags_data)
396 {
397 	session *sess;
398 	server *serv = mr->serv;
399 	char outbuf[4];
400 	char *cm = serv->chanmodes;
401 	gboolean supportsq = FALSE;
402 
403 	outbuf[0] = sign;
404 	outbuf[1] = 0;
405 	outbuf[2] = mode;
406 	outbuf[3] = 0;
407 
408 	sess = find_channel (serv, chan);
409 	if (!sess || !is_channel (serv, chan))
410 	{
411 		/* got modes for a chan we're not in! probably nickmode +isw etc */
412 		sess = serv->front_session;
413 		goto genmode;
414 	}
415 
416 	/* is this a nick mode? */
417 	if (strchr (serv->nick_modes, mode))
418 	{
419 		/* update the user in the userlist */
420 		userlist_update_mode (sess, /*nickname */ arg, mode, sign);
421 	} else
422 	{
423 		if (!is_324 && !sess->ignore_mode && mode_chanmode_type(serv, mode) >= 1)
424 			record_chan_mode (sess, sign, mode, arg);
425 	}
426 
427 	/* Is q a chanmode on this server? */
428 	if (cm)
429 		while (*cm)
430 		{
431 			if (*cm == ',')
432 				break;
433 			if (*cm == 'q')
434 				supportsq = TRUE;
435 			cm++;
436 		}
437 
438 	switch (sign)
439 	{
440 	case '+':
441 		switch (mode)
442 		{
443 		case 'k':
444 			safe_strcpy (sess->channelkey, arg, sizeof (sess->channelkey));
445 			fe_update_channel_key (sess);
446 			fe_update_mode_buttons (sess, mode, sign);
447 			if (!quiet)
448 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANSETKEY, sess, nick, arg, NULL,
449 											  NULL, 0, tags_data->timestamp);
450 			return;
451 		case 'l':
452 			sess->limit = atoi (arg);
453 			fe_update_channel_limit (sess);
454 			fe_update_mode_buttons (sess, mode, sign);
455 			if (!quiet)
456 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANSETLIMIT, sess, nick, arg, NULL,
457 											  NULL, 0, tags_data->timestamp);
458 			return;
459 		case 'o':
460 			if (!quiet)
461 				mr->op = mode_cat (mr->op, arg);
462 			return;
463 		case 'h':
464 			if (!quiet)
465 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANHOP, sess, nick, arg, NULL, NULL,
466 											  0, tags_data->timestamp);
467 			return;
468 		case 'v':
469 			if (!quiet)
470 				mr->voice = mode_cat (mr->voice, arg);
471 			return;
472 		case 'b':
473 			if (!quiet)
474 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANBAN, sess, nick, arg, NULL, NULL,
475 											  0, tags_data->timestamp);
476 			return;
477 		case 'e':
478 			if (!quiet)
479 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANEXEMPT, sess, nick, arg, NULL,
480 											  NULL, 0, tags_data->timestamp);
481 			return;
482 		case 'I':
483 			if (!quiet)
484 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANINVITE, sess, nick, arg, NULL, NULL,
485 											  0, tags_data->timestamp);
486 			return;
487 		case 'q':
488 			if (!supportsq)
489 				break; /* +q is owner on this server */
490 			if (!quiet)
491 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANQUIET, sess, nick, arg, NULL, NULL, 0,
492 								 tags_data->timestamp);
493 			return;
494 		}
495 		break;
496 	case '-':
497 		switch (mode)
498 		{
499 		case 'k':
500 			sess->channelkey[0] = 0;
501 			fe_update_channel_key (sess);
502 			fe_update_mode_buttons (sess, mode, sign);
503 			if (!quiet)
504 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANRMKEY, sess, nick, NULL, NULL,
505 											  NULL, 0, tags_data->timestamp);
506 			return;
507 		case 'l':
508 			sess->limit = 0;
509 			fe_update_channel_limit (sess);
510 			fe_update_mode_buttons (sess, mode, sign);
511 			if (!quiet)
512 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANRMLIMIT, sess, nick, NULL, NULL,
513 											  NULL, 0, tags_data->timestamp);
514 			return;
515 		case 'o':
516 			if (!quiet)
517 				mr->deop = mode_cat (mr->deop, arg);
518 			return;
519 		case 'h':
520 			if (!quiet)
521 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANDEHOP, sess, nick, arg, NULL,
522 											  NULL, 0, tags_data->timestamp);
523 			return;
524 		case 'v':
525 			if (!quiet)
526 				mr->devoice = mode_cat (mr->devoice, arg);
527 			return;
528 		case 'b':
529 			if (!quiet)
530 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANUNBAN, sess, nick, arg, NULL, NULL,
531 											  0, tags_data->timestamp);
532 			return;
533 		case 'e':
534 			if (!quiet)
535 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANRMEXEMPT, sess, nick, arg, NULL,
536 											  NULL, 0, tags_data->timestamp);
537 			return;
538 		case 'I':
539 			if (!quiet)
540 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANRMINVITE, sess, nick, arg, NULL,
541 											  NULL, 0, tags_data->timestamp);
542 			return;
543 		case 'q':
544 			if (!supportsq)
545 				break; /* -q is owner on this server */
546 			if (!quiet)
547 				EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANUNQUIET, sess, nick, arg, NULL,
548 											  NULL, 0, tags_data->timestamp);
549 			return;
550 		}
551 	}
552 
553 	fe_update_mode_buttons (sess, mode, sign);
554 
555  genmode:
556 	/* Received umode +e. If we're waiting to send JOIN then send now! */
557 	if (mode == 'e' && sign == '+' && !serv->p_cmp (chan, serv->nick))
558 		inbound_identified (serv);
559 
560 	if (!quiet)
561 	{
562 		if (*arg)
563 		{
564 			char *buf = g_strdup_printf ("%s %s", chan, arg);
565 			EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMODEGEN, sess, nick, outbuf,
566 										  outbuf + 2, buf, 0, tags_data->timestamp);
567 			g_free (buf);
568 		}
569 		else
570 			EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMODEGEN, sess, nick, outbuf,
571 										  outbuf + 2, chan, 0, tags_data->timestamp);
572 	}
573 }
574 
575 /* does this mode have an arg? like +b +l +o */
576 
577 static int
mode_has_arg(server * serv,char sign,char mode)578 mode_has_arg (server * serv, char sign, char mode)
579 {
580 	int type;
581 
582 	/* if it's a nickmode, it must have an arg */
583 	if (strchr (serv->nick_modes, mode))
584 		return 1;
585 
586 	type = mode_chanmode_type (serv, mode);
587 	switch (type)
588 	{
589 	case 0:					  /* type A */
590 	case 1:					  /* type B */
591 		return 1;
592 	case 2:					  /* type C */
593 		if (sign == '+')
594 			return 1;
595 	case 3:					  /* type D */
596 		return 0;
597 	default:
598 		return 0;
599 	}
600 
601 }
602 
603 /* what type of chanmode is it? -1 for not in chanmode */
604 static int
mode_chanmode_type(server * serv,char mode)605 mode_chanmode_type (server * serv, char mode)
606 {
607 	/* see what numeric 005 CHANMODES=xxx said */
608 	char *cm = serv->chanmodes;
609 	int type = 0;
610 	int found = 0;
611 
612 	while (*cm && !found)
613 	{
614 		if (*cm == ',')
615 		{
616 			type++;
617 		} else if (*cm == mode)
618 		{
619 			found = 1;
620 		}
621 		cm++;
622 	}
623 	if (found)
624 		return type;
625 	/* not found? -1 */
626 	else
627 		return -1;
628 }
629 
630 static void
mode_print_grouped(session * sess,char * nick,mode_run * mr,const message_tags_data * tags_data)631 mode_print_grouped (session *sess, char *nick, mode_run *mr,
632 						  const message_tags_data *tags_data)
633 {
634 	/* print all the grouped Op/Deops */
635 	if (mr->op)
636 	{
637 		EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANOP, sess, nick, mr->op, NULL, NULL, 0,
638 									  tags_data->timestamp);
639 		g_free(mr->op);
640 		mr->op = NULL;
641 	}
642 
643 	if (mr->deop)
644 	{
645 		EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANDEOP, sess, nick, mr->deop, NULL, NULL,
646 									  0, tags_data->timestamp);
647 		g_free(mr->deop);
648 		mr->deop = NULL;
649 	}
650 
651 	if (mr->voice)
652 	{
653 		EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANVOICE, sess, nick, mr->voice, NULL, NULL,
654 									  0, tags_data->timestamp);
655 		g_free(mr->voice);
656 		mr->voice = NULL;
657 	}
658 
659 	if (mr->devoice)
660 	{
661 		EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANDEVOICE, sess, nick, mr->devoice, NULL,
662 									  NULL, 0, tags_data->timestamp);
663 		g_free(mr->devoice);
664 		mr->devoice = NULL;
665 	}
666 }
667 
668 
669 /* handle a MODE or numeric 324 from server */
670 
671 void
handle_mode(server * serv,char * word[],char * word_eol[],char * nick,int numeric_324,const message_tags_data * tags_data)672 handle_mode (server * serv, char *word[], char *word_eol[],
673 				 char *nick, int numeric_324, const message_tags_data *tags_data)
674 {
675 	session *sess;
676 	char *chan;
677 	char *modes;
678 	char *argstr;
679 	char sign;
680 	int len;
681 	size_t arg;
682 	size_t i, num_args;
683 	int num_modes;
684 	size_t offset = 3;
685 	int all_modes_have_args = FALSE;
686 	int using_front_tab = FALSE;
687 	mode_run mr;
688 
689 	mr.serv = serv;
690 	mr.op = mr.deop = mr.voice = mr.devoice = NULL;
691 
692 	/* numeric 324 has everything 1 word later (as opposed to MODE) */
693 	if (numeric_324)
694 		offset++;
695 
696 	chan = word[offset];
697 	modes = word[offset + 1];
698 	if (*modes == ':')
699 		modes++;
700 
701 	if (*modes == 0)
702 		return;	/* beyondirc's blank modes */
703 
704 	sess = find_channel (serv, chan);
705 	if (!sess)
706 	{
707 		sess = serv->front_session;
708 		using_front_tab = TRUE;
709 	}
710 	/* remove trailing space */
711 	len = strlen (word_eol[offset]) - 1;
712 	if (word_eol[offset][len] == ' ')
713 		word_eol[offset][len] = 0;
714 
715 	if (prefs.hex_irc_raw_modes && !numeric_324)
716 		EMIT_SIGNAL_TIMESTAMP (XP_TE_RAWMODES, sess, nick, word_eol[offset], 0, 0, 0,
717 									  tags_data->timestamp);
718 
719 	if (numeric_324 && !using_front_tab)
720 	{
721 		g_free (sess->current_modes);
722 		sess->current_modes = g_strdup (word_eol[offset+1]);
723 	}
724 
725 	sign = *modes;
726 	modes++;
727 	arg = 1;
728 
729 	/* count the number of arguments (e.g. after the -o+v) */
730 	num_args = 0;
731 	i = 1;
732 	while ((i + offset + 1) < PDIWORDS)
733 	{
734 		i++;
735 		if (!(*word[i + offset]))
736 			break;
737 		num_args++;
738 		if (word[i + offset][0] == ':')
739 			break;
740 	}
741 
742 	/* count the number of modes (without the -/+ chars */
743 	num_modes = 0;
744 	i = 0;
745 	while (i < strlen (modes))
746 	{
747 		if (modes[i] != '+' && modes[i] != '-')
748 			num_modes++;
749 		i++;
750 	}
751 
752 	if (num_args == num_modes)
753 		all_modes_have_args = TRUE;
754 
755 	while (*modes)
756 	{
757 		switch (*modes)
758 		{
759 		case '-':
760 		case '+':
761 			/* print all the grouped Op/Deops */
762 			mode_print_grouped (sess, nick, &mr, tags_data);
763 			sign = *modes;
764 			break;
765 		default:
766 			argstr = "";
767 			if ((all_modes_have_args || mode_has_arg (serv, sign, *modes)) && arg < (num_args + 1))
768 			{
769 				arg++;
770 				argstr = STRIP_COLON(word, word_eol, arg+offset);
771 			}
772 			handle_single_mode (&mr, sign, *modes, nick, chan,
773 									  argstr, numeric_324 || prefs.hex_irc_raw_modes,
774 									  numeric_324, tags_data);
775 		}
776 
777 		modes++;
778 	}
779 
780 	/* update the title at the end, now that the mode update is internal now */
781 	if (!using_front_tab)
782 		fe_set_title (sess);
783 
784 	/* print all the grouped Op/Deops */
785 	mode_print_grouped (sess, nick, &mr, tags_data);
786 }
787 
788 static char
hex_to_chr(char chr)789 hex_to_chr(char chr)
790 {
791 	return g_ascii_isdigit (chr) ? chr - '0' : g_ascii_tolower (chr) - 'a' + 10;
792 }
793 
794 static void
parse_005_token(const char * token,char ** name,char ** value,gboolean * adding)795 parse_005_token (const char *token, char **name, char **value, gboolean *adding)
796 {
797 	char *toksplit, *valuecurr;
798 	size_t idx;
799 
800 	if (token[0] == '-')
801 	{
802 		*adding = FALSE;
803 		token++;
804 	} else
805 	{
806 		*adding = TRUE;
807 	}
808 
809 	toksplit = strchr (token, '=');
810 	if (toksplit && *toksplit++)
811 	{
812 		/* The token has a value; parse any escape codes. */
813 		*name = g_strndup (token, toksplit - token - 1);
814 		*value = g_malloc (strlen (toksplit) + 1);
815 		valuecurr = *value;
816 
817 		while (*toksplit)
818 		{
819 			if (toksplit[0] == '\\')
820 			{
821 				/** If it's a malformed escape then just skip it. */
822 				if (toksplit[1] == 'x' && g_ascii_isxdigit (toksplit[2]) && g_ascii_isxdigit (toksplit[3]))
823 					*valuecurr++ = hex_to_chr (toksplit[2]) << 4 | hex_to_chr (toksplit[3]);
824 
825 				for (idx = 0; idx < 4; ++idx)
826 				{
827 					/* We need to do this to avoid jumping past the end of the array. */
828 					if (*toksplit)
829 						toksplit++;
830 				}
831 			} else
832 			{
833 				/** Non-escape characters can be copied as is. */
834 				*valuecurr++ = *toksplit++;
835 			}
836 		}
837 		*valuecurr++ = 0;
838 	} else
839 	{
840 		/* The token has no value; store a dummy value instead. */
841 		*name = g_strdup (token);
842 		*value = g_strdup ("");
843 	}
844 }
845 
846 /* handle the 005 numeric */
847 
848 void
inbound_005(server * serv,char * word[],const message_tags_data * tags_data)849 inbound_005 (server * serv, char *word[], const message_tags_data *tags_data)
850 {
851 	int w;
852 	char *pre;
853 	char *tokname, *tokvalue;
854 	gboolean tokadding;
855 
856 	w = 4;							  /* start at the 4th word */
857 	while (w < PDIWORDS && *word[w])
858 	{
859 		if (word[w][0] == ':')
860 			break; // :are supported by this server
861 
862 		parse_005_token(word[w], &tokname, &tokvalue, &tokadding);
863 		if (g_strcmp0 (tokname, "MODES") == 0)
864 		{
865 			serv->modes_per_line = atoi (tokvalue);
866 		} else if (g_strcmp0 (tokname, "CHANTYPES") == 0)
867 		{
868 			g_free (serv->chantypes);
869 			serv->chantypes = g_strdup (tokvalue);
870 		} else if (g_strcmp0 (tokname, "CHANMODES") == 0)
871 		{
872 			g_free (serv->chanmodes);
873 			serv->chanmodes = g_strdup (tokvalue);
874 		} else if (g_strcmp0 (tokname, "PREFIX") == 0)
875 		{
876 			pre = strchr (tokvalue, ')');
877 			if (pre)
878 			{
879 				pre[0] = 0;			  /* NULL out the ')' */
880 				g_free (serv->nick_prefixes);
881 				g_free (serv->nick_modes);
882 				serv->nick_prefixes = g_strdup (pre + 1);
883 				serv->nick_modes = g_strdup (tokvalue + 1);
884 			} else
885 			{
886 				/* bad! some ircds don't give us the modes. */
887 				/* in this case, we use it only to strip /NAMES */
888 				serv->bad_prefix = TRUE;
889 				g_free (serv->bad_nick_prefixes);
890 				serv->bad_nick_prefixes = g_strdup (tokvalue);
891 			}
892 		} else if (g_strcmp0 (tokname, "WATCH") == 0)
893 		{
894 			serv->supports_watch = tokadding;
895 		} else if (g_strcmp0 (tokname, "MONITOR") == 0)
896 		{
897 			serv->supports_monitor = tokadding;
898 		} else if (g_strcmp0 (tokname, "NETWORK") == 0)
899 		{
900 			if (serv->server_session->type == SESS_SERVER && strlen (tokvalue))
901 			{
902 				safe_strcpy (serv->server_session->channel, tokvalue, CHANLEN);
903 				fe_set_channel (serv->server_session);
904 			}
905 
906 		} else if (g_strcmp0 (tokname, "CASEMAPPING") == 0)
907 		{
908 			if (g_strcmp0 (tokvalue, "ascii") == 0)
909 				serv->p_cmp = (void *)g_ascii_strcasecmp;
910 		} else if (g_strcmp0 (tokname, "CHARSET") == 0)
911 		{
912 			if (g_ascii_strcasecmp (tokvalue, "UTF-8") == 0)
913 			{
914 				server_set_encoding (serv, "UTF-8");
915 			}
916 		} else if (g_strcmp0 (tokname, "UTF8ONLY") == 0)
917 		{
918 			server_set_encoding (serv, "UTF-8");
919 		} else if (g_strcmp0 (tokname, "NAMESX") == 0)
920 		{
921 									/* 12345678901234567 */
922 			tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17);
923 		} else if (g_strcmp0 (tokname, "WHOX") == 0)
924 		{
925 			serv->have_whox = tokadding;
926 		} else if (g_strcmp0 (tokname, "EXCEPTS") == 0)
927 		{
928 			serv->have_except = tokadding;
929 		} else if (g_strcmp0 (tokname, "INVEX") == 0)
930 		{
931 			/* supports mode letter +I, default channel invite */
932 			serv->have_invite = tokadding;
933 		} else if (g_strcmp0 (tokname, "ELIST") == 0)
934 		{
935 			/* supports LIST >< min/max user counts? */
936 			if (strchr (tokvalue, 'U') || strchr (tokvalue, 'u'))
937 				serv->use_listargs = TRUE;
938 		}
939 
940 		g_free (tokname);
941 		g_free (tokvalue);
942 		w++;
943 	}
944 }
945