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
23 #include <glib/gprintf.h>
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' */
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
);
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? */
91 wlen
= strlen (word
[wpos
+ i
]) + 1;
94 len
+= wlen
; /* length of our whole string so far */
98 usable_modes
= i
; /* this is how many we'll send on this line */
100 /* add the +/-modemodemodemode */
104 for (i
= 0; i
< usable_modes
; i
++)
109 tbuf
[len
] = 0; /* null terminate for the strcat() to work */
111 /* add all the nicknames */
112 for (i
= 0; i
< usable_modes
; i
++)
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]))
133 /* is the given char a valid nick mode char? e.g. @ or + */
136 is_prefix_char (server
* serv
, char c
)
139 char *np
= serv
->nick_prefixes
;
149 if (serv
->bad_prefix
)
151 if (strchr (serv
->bad_nick_prefixes
, c
))
152 /* valid prefix char, but mode unknown */
159 /* returns '@' for ops etc... */
162 get_nick_prefix (server
* serv
, unsigned int access
)
167 for (pos
= 0; pos
< USERACCESS_SIZE
; pos
++)
169 c
= serv
->nick_prefixes
[pos
];
172 if (access
& (1 << pos
))
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 */
185 nick_access (server
* serv
, char *nick
, int *modechars
)
188 unsigned int access
= 0;
193 i
= is_prefix_char (serv
, *nick
);
197 /* -2 == valid prefix char, but mode unknown */
204 *modechars
= nick
- orig
;
209 /* returns the access number for a particular mode. e.g.
214 Also puts the nick-prefix-char in 'prefix' */
217 mode_access (server
* serv
, char mode
, char *prefix
)
221 while (serv
->nick_modes
[pos
])
223 if (serv
->nick_modes
[pos
] == mode
)
225 *prefix
= serv
->nick_prefixes
[pos
];
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
);
245 gchar
*current_char
= current
->str
;
247 gint argument_num
= 0;
248 gint argument_offset
= 0;
249 gint argument_length
= 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
;
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
)
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
;
284 while (i
<= mode_pos
)
286 if (mode_has_arg(serv
, '+', *current_char
))
292 /* check through arguments for where to start */
293 current_char
= arguments_start
;
295 while (i
< argument_num
&& *current_char
!= '\0')
297 if (*current_char
== ' ')
299 if (i
!= argument_num
)
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
)
310 while (*current_char
!= '\0' && *current_char
!= ' ')
318 /* two cases, adding and removing a mode, handled differently */
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 */
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
);
367 mode_cat (char *str
, char *addition
)
373 len
= strlen (str
) + strlen (addition
) + 2;
374 str
= realloc (str
, len
);
376 strcat (str
, addition
);
379 str
= strdup (addition
);
385 /* handle one mode, e.g.
386 handle_single_mode (mr,'+','b',"elite","#warez","banneduser",) */
389 handle_single_mode (mode_run
*mr
, char sign
, char mode
, char *nick
,
390 char *chan
, char *arg
, int quiet
, int is_324
)
393 server
*serv
= mr
->serv
;
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
;
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
);
416 if (!is_324
&& !sess
->ignore_mode
&& mode_chanmode_type(serv
, mode
) >= 1)
417 record_chan_mode (sess
, sign
, mode
, arg
);
426 safe_strcpy (sess
->channelkey
, arg
, sizeof (sess
->channelkey
));
427 fe_update_channel_key (sess
);
428 fe_update_mode_buttons (sess
, mode
, sign
);
430 EMIT_SIGNAL (XP_TE_CHANSETKEY
, sess
, nick
, arg
, NULL
, NULL
, 0);
433 sess
->limit
= atoi (arg
);
434 fe_update_channel_limit (sess
);
435 fe_update_mode_buttons (sess
, mode
, sign
);
437 EMIT_SIGNAL (XP_TE_CHANSETLIMIT
, sess
, nick
, arg
, NULL
, NULL
, 0);
441 mr
->op
= mode_cat (mr
->op
, arg
);
445 EMIT_SIGNAL (XP_TE_CHANHOP
, sess
, nick
, arg
, NULL
, NULL
, 0);
449 mr
->voice
= mode_cat (mr
->voice
, arg
);
453 EMIT_SIGNAL (XP_TE_CHANBAN
, sess
, nick
, arg
, NULL
, NULL
, 0);
457 EMIT_SIGNAL (XP_TE_CHANEXEMPT
, sess
, nick
, arg
, NULL
, NULL
, 0);
461 EMIT_SIGNAL (XP_TE_CHANINVITE
, sess
, nick
, arg
, NULL
, NULL
, 0);
469 sess
->channelkey
[0] = 0;
470 fe_update_channel_key (sess
);
471 fe_update_mode_buttons (sess
, mode
, sign
);
473 EMIT_SIGNAL (XP_TE_CHANRMKEY
, sess
, nick
, NULL
, NULL
, NULL
, 0);
477 fe_update_channel_limit (sess
);
478 fe_update_mode_buttons (sess
, mode
, sign
);
480 EMIT_SIGNAL (XP_TE_CHANRMLIMIT
, sess
, nick
, NULL
, NULL
, NULL
, 0);
484 mr
->deop
= mode_cat (mr
->deop
, arg
);
488 EMIT_SIGNAL (XP_TE_CHANDEHOP
, sess
, nick
, arg
, NULL
, NULL
, 0);
492 mr
->devoice
= mode_cat (mr
->devoice
, arg
);
496 EMIT_SIGNAL (XP_TE_CHANUNBAN
, sess
, nick
, arg
, NULL
, NULL
, 0);
500 EMIT_SIGNAL (XP_TE_CHANRMEXEMPT
, sess
, nick
, arg
, NULL
, NULL
, 0);
504 EMIT_SIGNAL (XP_TE_CHANRMINVITE
, sess
, nick
, arg
, NULL
, NULL
, 0);
509 fe_update_mode_buttons (sess
, mode
, sign
);
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
);
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);
525 EMIT_SIGNAL (XP_TE_CHANMODEGEN
, sess
, nick
, outbuf
, outbuf
+ 2, chan
, 0);
529 /* does this mode have an arg? like +b +l +o */
532 mode_has_arg (server
* serv
, char sign
, char mode
)
536 /* if it's a nickmode, it must have an arg */
537 if (strchr (serv
->nick_modes
, mode
))
540 type
= mode_chanmode_type (serv
, mode
);
557 /* what type of chanmode is it? -1 for not in chanmode */
559 mode_chanmode_type (server
* serv
, char mode
)
561 /* see what numeric 005 CHANMODES=xxx said */
562 char *cm
= serv
->chanmodes
;
566 while (*cm
&& !found
)
571 } else if (*cm
== mode
)
585 mode_print_grouped (session
*sess
, char *nick
, mode_run
*mr
)
587 /* print all the grouped Op/Deops */
590 EMIT_SIGNAL (XP_TE_CHANOP
, sess
, nick
, mr
->op
, NULL
, NULL
, 0);
597 EMIT_SIGNAL (XP_TE_CHANDEOP
, sess
, nick
, mr
->deop
, NULL
, NULL
, 0);
604 EMIT_SIGNAL (XP_TE_CHANVOICE
, sess
, nick
, mr
->voice
, NULL
, NULL
, 0);
611 EMIT_SIGNAL (XP_TE_CHANDEVOICE
, sess
, nick
, mr
->devoice
, NULL
, NULL
, 0);
618 /* handle a MODE or numeric 324 from server */
621 handle_mode (server
* serv
, char *word
[], char *word_eol
[],
622 char *nick
, int numeric_324
)
634 int all_modes_have_args
= FALSE
;
635 int using_front_tab
= FALSE
;
639 mr
.op
= mr
.deop
= mr
.voice
= mr
.devoice
= NULL
;
641 /* numeric 324 has everything 1 word later (as opposed to MODE) */
646 modes
= word
[offset
+ 1];
651 return; /* beyondirc's blank modes */
653 sess
= find_channel (serv
, chan
);
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]);
678 /* count the number of arguments (e.g. after the -o+v) */
681 while ((i
+ offset
+ 1) < PDIWORDS
)
684 if (!(*word
[i
+ offset
]))
689 /* count the number of modes (without the -/+ chars */
692 while (i
< strlen (modes
))
694 if (modes
[i
] != '+' && modes
[i
] != '-')
699 if (num_args
== num_modes
)
700 all_modes_have_args
= TRUE
;
708 /* print all the grouped Op/Deops */
709 mode_print_grouped (sess
, nick
, &mr
);
714 if ((all_modes_have_args
|| mode_has_arg (serv
, sign
, *modes
)) && arg
< (num_args
+1))
717 argstr
= word
[arg
+ offset
];
719 handle_single_mode (&mr
, sign
, *modes
, nick
, chan
,
720 argstr
, numeric_324
|| prefs
.raw_modes
,
727 /* update the title at the end, now that the mode update is internal now */
728 if (!using_front_tab
)
731 /* print all the grouped Op/Deops */
732 mode_print_grouped (sess
, nick
, &mr
);
735 /* handle the 005 numeric */
738 inbound_005 (server
* serv
, char *word
[])
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, ')');
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);
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
;