Now inbound_cap_ls() can enable extensions when a bouncer uses a namespace for the...
[rofl0r-ixchat.git] / src / common / modes.c
bloba8155f8380f8f7ed1868bfd90a419e1a4cab874b
1 /* X-Chat
2 * Copyright (C) 1998 Peter Zelezny.
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.
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.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 #include <string.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <glib.h>
23 #include <glib/gprintf.h>
25 #include "xchat.h"
26 #include "xchatc.h"
27 #include "modes.h"
28 #include "server.h"
29 #include "text.h"
30 #include "fe.h"
31 #include "util.h"
32 #include "inbound.h"
33 #ifdef HAVE_STRINGS_H
34 #include <strings.h>
35 #endif
37 typedef struct
39 server *serv;
40 char *op;
41 char *deop;
42 char *voice;
43 char *devoice;
44 } mode_run;
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, char *chan, char *arg, int quiet, int is_324);
50 static int mode_has_arg (server *serv, char sign, char mode);
51 static void mode_print_grouped (session *sess, char *nick, mode_run *mr);
52 static int mode_chanmode_type (server * serv, char mode);
55 /* word[] - list of nicks.
56 wpos - index into word[]. Where nicks really start.
57 end - index into word[]. Last entry plus one.
58 sign - a char, e.g. '+' or '-'
59 mode - a mode, e.g. 'o' or 'v' */
60 void
61 send_channel_modes (session *sess, char *tbuf, char *word[], int wpos,
62 int end, char sign, char mode, int modes_per_line)
64 int usable_modes, orig_len, len, wlen, i, max;
65 server *serv = sess->server;
67 /* sanity check. IRC RFC says three per line. */
68 if (serv->modes_per_line < 3)
69 serv->modes_per_line = 3;
70 if (modes_per_line < 1)
71 modes_per_line = serv->modes_per_line;
73 /* RFC max, minus length of "MODE %s " and "\r\n" and 1 +/- sign */
74 /* 512 - 6 - 2 - 1 - strlen(chan) */
75 max = 503 - strlen (sess->channel);
77 while (wpos < end)
79 tbuf[0] = '\0';
80 orig_len = len = 0;
82 /* we'll need this many modechars too */
83 len += modes_per_line;
85 /* how many can we fit? */
86 for (i = 0; i < modes_per_line; i++)
88 /* no more nicks left? */
89 if (wpos + i >= end)
90 break;
91 wlen = strlen (word[wpos + i]) + 1;
92 if (wlen + len > max)
93 break;
94 len += wlen; /* length of our whole string so far */
96 if (i < 1)
97 return;
98 usable_modes = i; /* this is how many we'll send on this line */
100 /* add the +/-modemodemodemode */
101 len = orig_len;
102 tbuf[len] = sign;
103 len++;
104 for (i = 0; i < usable_modes; i++)
106 tbuf[len] = mode;
107 len++;
109 tbuf[len] = 0; /* null terminate for the strcat() to work */
111 /* add all the nicknames */
112 for (i = 0; i < usable_modes; i++)
114 strcat (tbuf, " ");
115 strcat (tbuf, word[wpos + i]);
117 serv->p_mode (serv, sess->channel, tbuf);
119 wpos += usable_modes;
123 /* does 'chan' have a valid prefix? e.g. # or & */
126 is_channel (server * serv, char *chan)
128 if (strchr (serv->chantypes, chan[0]))
129 return 1;
130 return 0;
133 /* is the given char a valid nick mode char? e.g. @ or + */
135 static int
136 is_prefix_char (server * serv, char c)
138 int pos = 0;
139 char *np = serv->nick_prefixes;
141 while (np[0])
143 if (np[0] == c)
144 return pos;
145 pos++;
146 np++;
149 if (serv->bad_prefix)
151 if (strchr (serv->bad_nick_prefixes, c))
152 /* valid prefix char, but mode unknown */
153 return -2;
156 return -1;
159 /* returns '@' for ops etc... */
161 char
162 get_nick_prefix (server * serv, unsigned int access)
164 int pos;
165 char c;
167 for (pos = 0; pos < USERACCESS_SIZE; pos++)
169 c = serv->nick_prefixes[pos];
170 if (c == 0)
171 break;
172 if (access & (1 << pos))
173 return c;
176 return 0;
179 /* returns the access bitfield for a nickname. E.g.
180 @nick would return 000010 in binary
181 %nick would return 000100 in binary
182 +nick would return 001000 in binary */
184 unsigned int
185 nick_access (server * serv, char *nick, int *modechars)
187 int i;
188 unsigned int access = 0;
189 char *orig = nick;
191 while (*nick)
193 i = is_prefix_char (serv, *nick);
194 if (i == -1)
195 break;
197 /* -2 == valid prefix char, but mode unknown */
198 if (i != -2)
199 access |= (1 << i);
201 nick++;
204 *modechars = nick - orig;
206 return access;
209 /* returns the access number for a particular mode. e.g.
210 mode 'a' returns 0
211 mode 'o' returns 1
212 mode 'h' returns 2
213 mode 'v' returns 3
214 Also puts the nick-prefix-char in 'prefix' */
217 mode_access (server * serv, char mode, char *prefix)
219 int pos = 0;
221 while (serv->nick_modes[pos])
223 if (serv->nick_modes[pos] == mode)
225 *prefix = serv->nick_prefixes[pos];
226 return pos;
228 pos++;
231 *prefix = 0;
233 return -1;
236 static void
237 record_chan_mode (session *sess, char sign, char mode, char *arg)
239 /* Somebody needed to acutally update sess->current_modes, needed to
240 play nice with bouncers, and less mode calls. Also keeps modes up
241 to date for scripts */
242 server *serv = sess->server;
243 GString *current = g_string_new(sess->current_modes);
244 gint mode_pos = -1;
245 gchar *current_char = current->str;
246 gint modes_length;
247 gint argument_num = 0;
248 gint argument_offset = 0;
249 gint argument_length = 0;
250 int i = 0;
251 gchar *arguments_start;
253 /* find out if the mode currently exists */
254 arguments_start = g_strstr_len(current->str , -1, " ");
255 if (arguments_start) {
256 modes_length = arguments_start - current->str;
258 else {
259 modes_length = current->len;
260 /* set this to the end of the modes */
261 arguments_start = current->str + current->len;
264 while (mode_pos == -1 && i < modes_length)
266 if (*current_char == mode)
268 mode_pos = i;
270 else
272 i++;
273 current_char++;
277 /* if the mode currently exists and has an arg, need to know where
278 * (including leading space) */
279 if (mode_pos != -1 && mode_has_arg(serv, '+', mode))
281 current_char = current->str;
283 i = 0;
284 while (i <= mode_pos)
286 if (mode_has_arg(serv, '+', *current_char))
287 argument_num++;
288 current_char++;
289 i++;
292 /* check through arguments for where to start */
293 current_char = arguments_start;
294 i = 0;
295 while (i < argument_num && *current_char != '\0')
297 if (*current_char == ' ')
298 i++;
299 if (i != argument_num)
300 current_char++;
302 argument_offset = current_char - current->str;
304 /* how long the existing argument is for this key
305 * important for malloc and strncpy */
306 if (i == argument_num)
308 argument_length++;
309 current_char++;
310 while (*current_char != '\0' && *current_char != ' ')
312 argument_length++;
313 current_char++;
318 /* two cases, adding and removing a mode, handled differently */
319 if (sign == '+')
321 if (mode_pos != -1)
323 /* if it already exists, only need to do something (change)
324 * if there should be a param */
325 if (mode_has_arg(serv, sign, mode))
327 /* leave the old space there */
328 current = g_string_erase(current, argument_offset+1, argument_length-1);
329 current = g_string_insert(current, argument_offset+1, arg);
331 free(sess->current_modes);
332 sess->current_modes = g_string_free(current, FALSE);
335 /* mode wasn't there before */
336 else
338 /* insert the new mode character */
339 current = g_string_insert_c(current, modes_length, mode);
341 /* add the argument, with space if there is one */
342 if (mode_has_arg(serv, sign, mode))
344 current = g_string_append_c(current, ' ');
345 current = g_string_append(current, arg);
348 free(sess->current_modes);
349 sess->current_modes = g_string_free(current, FALSE);
352 else if (sign == '-' && mode_pos != -1)
354 /* remove the argument first if it has one*/
355 if (mode_has_arg(serv, '+', mode))
356 current = g_string_erase(current, argument_offset, argument_length);
358 /* remove the mode character */
359 current = g_string_erase(current, mode_pos, 1);
361 free(sess->current_modes);
362 sess->current_modes = g_string_free(current, FALSE);
366 static char *
367 mode_cat (char *str, char *addition)
369 int len;
371 if (str)
373 len = strlen (str) + strlen (addition) + 2;
374 str = realloc (str, len);
375 strcat (str, " ");
376 strcat (str, addition);
377 } else
379 str = strdup (addition);
382 return str;
385 /* handle one mode, e.g.
386 handle_single_mode (mr,'+','b',"elite","#warez","banneduser",) */
388 static void
389 handle_single_mode (mode_run *mr, char sign, char mode, char *nick,
390 char *chan, char *arg, int quiet, int is_324)
392 session *sess;
393 server *serv = mr->serv;
394 char outbuf[4];
396 outbuf[0] = sign;
397 outbuf[1] = 0;
398 outbuf[2] = mode;
399 outbuf[3] = 0;
401 sess = find_channel (serv, chan);
402 if (!sess || !is_channel (serv, chan))
404 /* got modes for a chan we're not in! probably nickmode +isw etc */
405 sess = serv->front_session;
406 goto genmode;
409 /* is this a nick mode? */
410 if (strchr (serv->nick_modes, mode))
412 /* update the user in the userlist */
413 userlist_update_mode (sess, /*nickname */ arg, mode, sign);
414 } else
416 if (!is_324 && !sess->ignore_mode && mode_chanmode_type(serv, mode) >= 1)
417 record_chan_mode (sess, sign, mode, arg);
420 switch (sign)
422 case '+':
423 switch (mode)
425 case 'k':
426 safe_strcpy (sess->channelkey, arg, sizeof (sess->channelkey));
427 fe_update_channel_key (sess);
428 fe_update_mode_buttons (sess, mode, sign);
429 if (!quiet)
430 EMIT_SIGNAL (XP_TE_CHANSETKEY, sess, nick, arg, NULL, NULL, 0);
431 return;
432 case 'l':
433 sess->limit = atoi (arg);
434 fe_update_channel_limit (sess);
435 fe_update_mode_buttons (sess, mode, sign);
436 if (!quiet)
437 EMIT_SIGNAL (XP_TE_CHANSETLIMIT, sess, nick, arg, NULL, NULL, 0);
438 return;
439 case 'o':
440 if (!quiet)
441 mr->op = mode_cat (mr->op, arg);
442 return;
443 case 'h':
444 if (!quiet)
445 EMIT_SIGNAL (XP_TE_CHANHOP, sess, nick, arg, NULL, NULL, 0);
446 return;
447 case 'v':
448 if (!quiet)
449 mr->voice = mode_cat (mr->voice, arg);
450 return;
451 case 'b':
452 if (!quiet)
453 EMIT_SIGNAL (XP_TE_CHANBAN, sess, nick, arg, NULL, NULL, 0);
454 return;
455 case 'e':
456 if (!quiet)
457 EMIT_SIGNAL (XP_TE_CHANEXEMPT, sess, nick, arg, NULL, NULL, 0);
458 return;
459 case 'I':
460 if (!quiet)
461 EMIT_SIGNAL (XP_TE_CHANINVITE, sess, nick, arg, NULL, NULL, 0);
462 return;
464 break;
465 case '-':
466 switch (mode)
468 case 'k':
469 sess->channelkey[0] = 0;
470 fe_update_channel_key (sess);
471 fe_update_mode_buttons (sess, mode, sign);
472 if (!quiet)
473 EMIT_SIGNAL (XP_TE_CHANRMKEY, sess, nick, NULL, NULL, NULL, 0);
474 return;
475 case 'l':
476 sess->limit = 0;
477 fe_update_channel_limit (sess);
478 fe_update_mode_buttons (sess, mode, sign);
479 if (!quiet)
480 EMIT_SIGNAL (XP_TE_CHANRMLIMIT, sess, nick, NULL, NULL, NULL, 0);
481 return;
482 case 'o':
483 if (!quiet)
484 mr->deop = mode_cat (mr->deop, arg);
485 return;
486 case 'h':
487 if (!quiet)
488 EMIT_SIGNAL (XP_TE_CHANDEHOP, sess, nick, arg, NULL, NULL, 0);
489 return;
490 case 'v':
491 if (!quiet)
492 mr->devoice = mode_cat (mr->devoice, arg);
493 return;
494 case 'b':
495 if (!quiet)
496 EMIT_SIGNAL (XP_TE_CHANUNBAN, sess, nick, arg, NULL, NULL, 0);
497 return;
498 case 'e':
499 if (!quiet)
500 EMIT_SIGNAL (XP_TE_CHANRMEXEMPT, sess, nick, arg, NULL, NULL, 0);
501 return;
502 case 'I':
503 if (!quiet)
504 EMIT_SIGNAL (XP_TE_CHANRMINVITE, sess, nick, arg, NULL, NULL, 0);
505 return;
509 fe_update_mode_buttons (sess, mode, sign);
511 genmode:
512 /* Received umode +e. If we're waiting to send JOIN then send now! */
513 if (mode == 'e' && sign == '+' && !serv->p_cmp (chan, serv->nick))
514 inbound_identified (serv);
516 if (!quiet)
518 if (*arg)
520 char *buf = malloc (strlen (chan) + strlen (arg) + 2);
521 sprintf (buf, "%s %s", chan, arg);
522 EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, buf, 0);
523 free (buf);
524 } else
525 EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, chan, 0);
529 /* does this mode have an arg? like +b +l +o */
531 static int
532 mode_has_arg (server * serv, char sign, char mode)
534 int type;
536 /* if it's a nickmode, it must have an arg */
537 if (strchr (serv->nick_modes, mode))
538 return 1;
540 type = mode_chanmode_type (serv, mode);
541 switch (type)
543 case 0: /* type A */
544 case 1: /* type B */
545 return 1;
546 case 2: /* type C */
547 if (sign == '+')
548 return 1;
549 case 3: /* type D */
550 return 0;
551 default:
552 return 0;
557 /* what type of chanmode is it? -1 for not in chanmode */
558 static int
559 mode_chanmode_type (server * serv, char mode)
561 /* see what numeric 005 CHANMODES=xxx said */
562 char *cm = serv->chanmodes;
563 int type = 0;
564 int found = 0;
566 while (*cm && !found)
568 if (*cm == ',')
570 type++;
571 } else if (*cm == mode)
573 found = 1;
575 cm++;
577 if (found)
578 return type;
579 /* not found? -1 */
580 else
581 return -1;
584 static void
585 mode_print_grouped (session *sess, char *nick, mode_run *mr)
587 /* print all the grouped Op/Deops */
588 if (mr->op)
590 EMIT_SIGNAL (XP_TE_CHANOP, sess, nick, mr->op, NULL, NULL, 0);
591 free (mr->op);
592 mr->op = NULL;
595 if (mr->deop)
597 EMIT_SIGNAL (XP_TE_CHANDEOP, sess, nick, mr->deop, NULL, NULL, 0);
598 free (mr->deop);
599 mr->deop = NULL;
602 if (mr->voice)
604 EMIT_SIGNAL (XP_TE_CHANVOICE, sess, nick, mr->voice, NULL, NULL, 0);
605 free (mr->voice);
606 mr->voice = NULL;
609 if (mr->devoice)
611 EMIT_SIGNAL (XP_TE_CHANDEVOICE, sess, nick, mr->devoice, NULL, NULL, 0);
612 free (mr->devoice);
613 mr->devoice = NULL;
618 /* handle a MODE or numeric 324 from server */
620 void
621 handle_mode (server * serv, char *word[], char *word_eol[],
622 char *nick, int numeric_324)
624 session *sess;
625 char *chan;
626 char *modes;
627 char *argstr;
628 char sign;
629 int len;
630 int arg;
631 int i, num_args;
632 int num_modes;
633 int offset = 3;
634 int all_modes_have_args = FALSE;
635 int using_front_tab = FALSE;
636 mode_run mr;
638 mr.serv = serv;
639 mr.op = mr.deop = mr.voice = mr.devoice = NULL;
641 /* numeric 324 has everything 1 word later (as opposed to MODE) */
642 if (numeric_324)
643 offset++;
645 chan = word[offset];
646 modes = word[offset + 1];
647 if (*modes == ':')
648 modes++;
650 if (*modes == 0)
651 return; /* beyondirc's blank modes */
653 sess = find_channel (serv, chan);
654 if (!sess)
656 sess = serv->front_session;
657 using_front_tab = TRUE;
659 /* remove trailing space */
660 len = strlen (word_eol[offset]) - 1;
661 if (word_eol[offset][len] == ' ')
662 word_eol[offset][len] = 0;
664 if (prefs.raw_modes && !numeric_324)
665 EMIT_SIGNAL (XP_TE_RAWMODES, sess, nick, word_eol[offset], 0, 0, 0);
667 if (numeric_324 && !using_front_tab)
669 if (sess->current_modes)
670 free (sess->current_modes);
671 sess->current_modes = strdup (word_eol[offset+1]);
674 sign = *modes;
675 modes++;
676 arg = 1;
678 /* count the number of arguments (e.g. after the -o+v) */
679 num_args = 0;
680 i = 1;
681 while ((i + offset + 1) < PDIWORDS)
683 i++;
684 if (!(*word[i + offset]))
685 break;
686 num_args++;
689 /* count the number of modes (without the -/+ chars */
690 num_modes = 0;
691 i = 0;
692 while (i < strlen (modes))
694 if (modes[i] != '+' && modes[i] != '-')
695 num_modes++;
696 i++;
699 if (num_args == num_modes)
700 all_modes_have_args = TRUE;
702 while (*modes)
704 switch (*modes)
706 case '-':
707 case '+':
708 /* print all the grouped Op/Deops */
709 mode_print_grouped (sess, nick, &mr);
710 sign = *modes;
711 break;
712 default:
713 argstr = "";
714 if ((all_modes_have_args || mode_has_arg (serv, sign, *modes)) && arg < (num_args+1))
716 arg++;
717 argstr = word[arg + offset];
719 handle_single_mode (&mr, sign, *modes, nick, chan,
720 argstr, numeric_324 || prefs.raw_modes,
721 numeric_324);
724 modes++;
727 /* update the title at the end, now that the mode update is internal now */
728 if (!using_front_tab)
729 fe_set_title (sess);
731 /* print all the grouped Op/Deops */
732 mode_print_grouped (sess, nick, &mr);
735 /* handle the 005 numeric */
737 void
738 inbound_005 (server * serv, char *word[])
740 int w;
741 char *pre;
743 w = 4; /* start at the 4th word */
744 while (w < PDIWORDS && *word[w])
746 if (strncmp (word[w], "MODES=", 6) == 0)
748 serv->modes_per_line = atoi (word[w] + 6);
749 } else if (strncmp (word[w], "CHANTYPES=", 10) == 0)
751 free (serv->chantypes);
752 serv->chantypes = strdup (word[w] + 10);
753 } else if (strncmp (word[w], "CHANMODES=", 10) == 0)
755 free (serv->chanmodes);
756 serv->chanmodes = strdup (word[w] + 10);
757 } else if (strncmp (word[w], "PREFIX=", 7) == 0)
759 pre = strchr (word[w] + 7, ')');
760 if (pre)
762 pre[0] = 0; /* NULL out the ')' */
763 free (serv->nick_prefixes);
764 free (serv->nick_modes);
765 serv->nick_prefixes = strdup (pre + 1);
766 serv->nick_modes = strdup (word[w] + 8);
767 } else
769 /* bad! some ircds don't give us the modes. */
770 /* in this case, we use it only to strip /NAMES */
771 serv->bad_prefix = TRUE;
772 if (serv->bad_nick_prefixes)
773 free (serv->bad_nick_prefixes);
774 serv->bad_nick_prefixes = strdup (word[w] + 7);
776 } else if (strncmp (word[w], "WATCH=", 6) == 0)
778 serv->supports_watch = TRUE;
779 } else if (strncmp (word[w], "MONITOR=", 8) == 0)
781 serv->supports_monitor = TRUE;
782 } else if (strncmp (word[w], "NETWORK=", 8) == 0)
784 /* if (serv->networkname)
785 free (serv->networkname);
786 serv->networkname = strdup (word[w] + 8);*/
788 if (serv->server_session->type == SESS_SERVER)
790 safe_strcpy (serv->server_session->channel, word[w] + 8, CHANLEN);
791 fe_set_channel (serv->server_session);
794 } else if (strncmp (word[w], "CASEMAPPING=", 12) == 0)
796 if (strcmp (word[w] + 12, "ascii") == 0) /* bahamut */
797 serv->p_cmp = (void *)strcasecmp;
798 } else if (strncmp (word[w], "CHARSET=", 8) == 0)
800 if (strcasecmp (word[w] + 8, "UTF-8") == 0)
802 server_set_encoding (serv, "UTF-8");
804 } else if (strcmp (word[w], "NAMESX") == 0)
806 /* 12345678901234567 */
807 tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17);
808 } else if (strcmp (word[w], "WHOX") == 0)
810 serv->have_whox = TRUE;
811 } else if (strcmp (word[w], "EXCEPTS") == 0)
813 serv->have_except = TRUE;
814 } else if (strncmp (word[w], "ELIST=", 6) == 0)
816 /* supports LIST >< min/max user counts? */
817 if (strchr (word[w] + 6, 'U') || strchr (word[w] + 6, 'u'))
818 serv->use_listargs = TRUE;
821 w++;