4
Copyright (C) 1999-2000 Timo Sirainen
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.
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.
16
You should have received a copy of the GNU General Public License
17
along with this program; if not, write to the Free Software
18
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
#include "irc-commands.h"
26
#include "irc-servers.h"
27
#include "irc-channels.h"
28
#include "servers-redirect.h"
30
#include "mode-lists.h"
33
/* Change nick's mode in channel */
34
static void nick_mode_change(IRC_CHANNEL_REC *channel, const char *nick,
35
char mode, int type, const char *setby)
38
char modestr[2], typestr[2];
40
g_return_if_fail(IS_IRC_CHANNEL(channel));
41
g_return_if_fail(nick != NULL);
43
nickrec = nicklist_find(CHANNEL(channel), nick);
44
if (nickrec == NULL) return; /* No /names list got yet */
46
if (mode == '@') nickrec->op = type == '+';
47
else if (mode == '+') nickrec->voice = type == '+';
48
else if (mode == '%') nickrec->halfop = type == '+';
49
else if (channel->server->prefix[(unsigned char) mode] != '\0')
50
nickrec->other = (type == '+' ? mode : '\0');
52
modestr[0] = mode; modestr[1] = '\0';
53
typestr[0] = type; typestr[1] = '\0';
54
signal_emit("nick mode changed", 5,
55
channel, nickrec, setby, modestr, typestr);
58
static int mode_is_set(const char *str, char mode)
62
g_return_val_if_fail(str != NULL, FALSE);
64
end = strchr(str, ' ');
65
pos = strchr(str, mode);
66
return pos != NULL && (end == NULL || pos < end);
69
/* add argument to specified position */
70
static void mode_add_arg(GString *str, int pos, int updating, const char *arg)
74
for (p = str->str; *p != '\0'; p++) {
83
pos = (int) (p-str->str);
84
if (updating && *p != '\0') {
85
/* remove the old argument */
87
while (*p != '\0' && *p != ' ') p++;
88
g_string_erase(str, pos, (int) (p-str->str)-pos);
91
/* .. GLib shouldn't fail when inserting at the end of the string */
92
if (pos == str->len) {
93
g_string_append_c(str, ' ');
94
g_string_append(str, arg);
96
g_string_insert_c(str, pos, ' ');
97
g_string_insert(str, pos+1, arg);
101
/* Add mode character to list sorted alphabetically */
102
static void mode_add_sorted(IRC_SERVER_REC *server, GString *str,
103
char mode, const char *arg, int user)
106
int updating, argpos = 0;
108
/* check that mode isn't already set */
109
if ((!user && !HAS_MODE_ARG_SET(server, mode)) &&
110
mode_is_set(str->str, mode))
114
for (p = str->str; *p != '\0' && *p != ' '; p++) {
121
if (!user && HAS_MODE_ARG_SET(server, *p))
125
/* .. GLib shouldn't fail when inserting at the end of the string */
128
g_string_append_c(str, mode);
130
g_string_insert_c(str, (int) (p-str->str), mode);
133
mode_add_arg(str, argpos, updating, arg);
136
/* remove the n'th argument */
137
static void node_remove_arg(GString *str, int pos)
143
for (p = str->str; *p != '\0'; p++) {
150
startpos = (int) (p-str->str);
155
return; /* not found */
157
g_string_erase(str, startpos, (int) (p-str->str)-startpos);
160
/* remove mode (and it's argument) from string */
161
static void mode_remove(IRC_SERVER_REC *server, GString *str, char mode, int user)
166
for (p = str->str; *p != '\0' && *p != ' '; p++) {
168
g_string_erase(str, (int) (p-str->str), 1);
169
if (!user && HAS_MODE_ARG_SET(server, mode))
170
node_remove_arg(str, argpos);
173
if (!user && HAS_MODE_ARG_SET(server, *p))
178
static void mode_set(IRC_SERVER_REC *server, GString *str,
179
char type, char mode, int user)
181
g_return_if_fail(str != NULL);
184
mode_remove(server, str, mode, user);
186
mode_add_sorted(server, str, mode, NULL, user);
189
static void mode_set_arg(IRC_SERVER_REC *server, GString *str,
190
char type, char mode, const char *arg, int user)
192
g_return_if_fail(str != NULL);
193
g_return_if_fail(type == '-' || arg != NULL);
196
mode_remove(server, str, mode, user);
198
mode_add_sorted(server, str, mode, arg, user);
201
/* Mode that needs a parameter of a mask for both setting and removing
203
void modes_type_a(IRC_CHANNEL_REC *channel, const char *setby, char type,
204
char mode, char *arg, GString *newmode)
208
banlist_add(channel, arg, setby, time(NULL));
210
banlist_remove(channel, arg, setby);
214
/* Mode that needs parameter for both setting and removing (eg: +k) */
215
void modes_type_b(IRC_CHANNEL_REC *channel, const char *setby, char type,
216
char mode, char *arg, GString *newmode)
219
if (*arg == '\0' && type == '+')
220
arg = channel->key != NULL ? channel->key : "???";
222
if (arg != channel->key) {
223
g_free_and_null(channel->key);
225
channel->key = g_strdup(arg);
229
mode_set_arg(channel->server, newmode, type, mode, arg, FALSE);
232
/* Mode that needs parameter only for adding */
233
void modes_type_c(IRC_CHANNEL_REC *channel, const char *setby,
234
char type, char mode, char *arg, GString *newmode)
237
channel->limit = type == '-' ? 0 : atoi(arg);
240
mode_set_arg(channel->server, newmode, type, mode, arg, FALSE);
243
/* Mode that takes no parameter */
244
void modes_type_d(IRC_CHANNEL_REC *channel, const char *setby,
245
char type, char mode, char *arg, GString *newmode)
247
mode_set(channel->server, newmode, type, mode, FALSE);
250
void modes_type_prefix(IRC_CHANNEL_REC *channel, const char *setby,
251
char type, char mode, char *arg, GString *newmode)
253
int umode = (unsigned char) mode;
254
nick_mode_change(channel, arg, channel->server->modes[umode].prefix,
257
if (g_strcasecmp(channel->server->nick, arg) == 0) {
258
/* see if we need to update channel->chanop */
260
g_hash_table_lookup(channel->server->isupport, "PREFIX");
261
if (prefix != NULL && *prefix == '(') {
263
while (*prefix != ')' && *prefix != '\0') {
264
if (*prefix == mode) {
265
channel->chanop = type == '+';
273
if (mode == 'o' || mode == 'O')
274
channel->chanop = type == '+';
279
int channel_mode_is_set(IRC_CHANNEL_REC *channel, char mode)
281
g_return_val_if_fail(IS_IRC_CHANNEL(channel), FALSE);
283
return channel->mode == NULL ? FALSE :
284
mode_is_set(channel->mode, mode);
287
/* Parse channel mode string */
288
void parse_channel_modes(IRC_CHANNEL_REC *channel, const char *setby,
289
const char *mode, int update_key)
291
IRC_SERVER_REC *server = channel->server;
293
char *dup, *modestr, *arg, *curmode, type, *old_key;
296
g_return_if_fail(IS_IRC_CHANNEL(channel));
297
g_return_if_fail(mode != NULL);
300
newmode = g_string_new(channel->mode);
301
old_key = update_key ? NULL : g_strdup(channel->key);
303
dup = modestr = g_strdup(mode);
304
curmode = cmd_get_param(&modestr);
305
while (*curmode != '\0') {
306
if (HAS_MODE_ARG(server, type, *curmode)) {
307
/* get the argument for the mode. NOTE: We don't
308
get the +k's argument when joining to channel. */
309
arg = cmd_get_param(&modestr);
320
umode = (unsigned char) *curmode;
321
if (server->modes[umode].func != NULL) {
322
server->modes[umode].func(channel, setby,
326
/* Treat unknown modes as ones without params */
327
modes_type_d(channel, setby, type, *curmode,
336
if (channel->key != NULL &&
337
strchr(channel->mode, 'k') == NULL &&
338
strchr(newmode->str, 'k') == NULL) {
339
/* join was used with key but there's no key set
340
in channel modes.. */
341
g_free(channel->key);
343
} else if (!update_key && old_key != NULL) {
344
/* get the old one back, just in case it was replaced */
345
g_free(channel->key);
346
channel->key = old_key;
347
mode_set_arg(channel->server, newmode, '+', 'k', old_key, FALSE);
351
if (strcmp(newmode->str, channel->mode) != 0) {
352
g_free(channel->mode);
353
channel->mode = g_strdup(newmode->str);
355
signal_emit("channel mode changed", 2, channel, setby);
358
g_string_free(newmode, TRUE);
362
/* add `mode' to `old' - return newly allocated mode.
363
`channel' specifies if we're parsing channel mode and we should try
364
to join mode arguments too. */
365
char *modes_join(IRC_SERVER_REC *server, const char *old,
366
const char *mode, int channel)
369
char *dup, *modestr, *curmode, type;
371
g_return_val_if_fail(mode != NULL, NULL);
374
newmode = g_string_new(old);
376
dup = modestr = g_strdup(mode);
377
curmode = cmd_get_param(&modestr);
378
while (*curmode != '\0' && *curmode != ' ') {
379
if (*curmode == '+' || *curmode == '-') {
385
if (!channel || !HAS_MODE_ARG(server, type, *curmode))
386
mode_set(server, newmode, type, *curmode, !channel);
388
mode_set_arg(server, newmode, type, *curmode,
389
cmd_get_param(&modestr), !channel);
396
modestr = newmode->str;
397
g_string_free(newmode, FALSE);
401
/* Parse user mode string */
402
static void parse_user_mode(IRC_SERVER_REC *server, const char *modestr)
404
char *newmode, *oldmode;
406
g_return_if_fail(IS_IRC_SERVER(server));
407
g_return_if_fail(modestr != NULL);
409
newmode = modes_join(NULL, server->usermode, modestr, FALSE);
410
oldmode = server->usermode;
411
server->usermode = newmode;
412
server->server_operator = (strchr(newmode, 'o') != NULL);
414
signal_emit("user mode changed", 2, server, oldmode);
415
g_free_not_null(oldmode);
418
static void event_user_mode(IRC_SERVER_REC *server, const char *data)
420
char *params, *nick, *mode;
422
g_return_if_fail(data != NULL);
424
params = event_get_params(data, 3, NULL, &nick, &mode);
425
parse_user_mode(server, mode);
430
static void event_mode(IRC_SERVER_REC *server, const char *data,
433
IRC_CHANNEL_REC *chanrec;
434
char *params, *channel, *mode;
436
g_return_if_fail(data != NULL);
438
params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
441
if (!ischannel(*channel)) {
442
/* user mode change */
443
parse_user_mode(server, mode);
445
/* channel mode change */
446
chanrec = irc_channel_find(server, channel);
448
parse_channel_modes(chanrec, nick, mode, TRUE);
454
static void event_oper(IRC_SERVER_REC *server, const char *data)
456
const char *opermode;
458
opermode = settings_get_str("opermode");
459
if (*opermode != '\0')
460
irc_send_cmdv(server, "MODE %s %s", server->nick, opermode);
463
static void event_away(IRC_SERVER_REC *server, const char *data)
465
g_return_if_fail(server != NULL);
467
server->usermode_away = TRUE;
468
signal_emit("away mode changed", 1, server);
471
static void event_unaway(IRC_SERVER_REC *server, const char *data)
473
g_return_if_fail(server != NULL);
475
server->usermode_away = FALSE;
476
g_free_and_null(server->away_reason);
477
signal_emit("away mode changed", 1, server);
480
static void sig_req_usermode_change(IRC_SERVER_REC *server, const char *data,
481
const char *nick, const char *addr)
483
char *params, *target, *mode;
485
g_return_if_fail(data != NULL);
487
params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
489
if (!ischannel(*target)) {
490
/* we requested a user mode change, save this */
491
mode = modes_join(NULL, server->wanted_usermode, mode, FALSE);
492
g_free_not_null(server->wanted_usermode);
493
server->wanted_usermode = mode;
498
signal_emit("event mode", 4, server, data, nick, addr);
501
void channel_set_singlemode(IRC_CHANNEL_REC *channel, const char *nicks,
506
char **nick, **nicklist;
508
g_return_if_fail(IS_IRC_CHANNEL(channel));
509
g_return_if_fail(nicks != NULL && mode != NULL);
510
if (*nicks == '\0') return;
513
str = g_string_new(NULL);
515
nicklist = g_strsplit(nicks, " ", -1);
516
for (nick = nicklist; *nick != NULL; nick++) {
522
g_string_sprintf(str, "MODE %s %s",
523
channel->name, mode);
526
/* insert the mode string */
527
g_string_insert(str, modepos, mode);
530
g_string_sprintfa(str, " %s", *nick);
532
if (++num == channel->server->max_modes_in_cmd) {
533
/* max. modes / command reached, send to server */
534
irc_send_cmd(channel->server, str->str);
538
if (num > 0) irc_send_cmd(channel->server, str->str);
540
g_strfreev(nicklist);
541
g_string_free(str, TRUE);
544
void channel_set_mode(IRC_SERVER_REC *server, const char *channel,
547
IRC_CHANNEL_REC *chanrec;
548
GString *tmode, *targs;
549
char *modestr, *curmode, *orig, type, prevtype;
552
g_return_if_fail(IS_IRC_SERVER(server));
553
g_return_if_fail(channel != NULL && mode != NULL);
555
tmode = g_string_new(NULL);
556
targs = g_string_new(NULL);
559
chanrec = irc_channel_find(server, channel);
561
channel = chanrec->name;
563
orig = modestr = g_strdup(mode);
565
type = '+'; prevtype = '\0';
566
curmode = cmd_get_param(&modestr);
568
if (*curmode == '\0') {
569
/* support for +o nick +o nick2 */
570
curmode = cmd_get_param(&modestr);
571
if (*curmode == '\0')
575
if (*curmode == '+' || *curmode == '-') {
580
if (count == server->max_modes_in_cmd &&
581
HAS_MODE_ARG(server, type, *curmode)) {
582
irc_send_cmdv(server, "MODE %s %s%s",
583
channel, tmode->str, targs->str);
585
count = 0; prevtype = '\0';
586
g_string_truncate(tmode, 0);
587
g_string_truncate(targs, 0);
590
if (type != prevtype) {
592
g_string_append_c(tmode, type);
594
g_string_append_c(tmode, *curmode);
596
if (HAS_MODE_ARG(server, type, *curmode)) {
600
arg = cmd_get_param(&modestr);
601
if (*arg == '\0' && type == '-' && *curmode == 'k') {
602
/* "/mode #channel -k" - no reason why it
603
shouldn't work really, so append the key */
604
IRC_CHANNEL_REC *chanrec;
606
chanrec = irc_channel_find(server, channel);
607
if (chanrec != NULL && chanrec->key != NULL)
612
g_string_sprintfa(targs, " %s", arg);
616
if (tmode->len > 0) {
617
irc_send_cmdv(server, "MODE %s %s%s",
618
channel, tmode->str, targs->str);
621
g_string_free(tmode, TRUE);
622
g_string_free(targs, TRUE);
626
static int get_wildcard_nicks(GString *output, const char *mask,
627
IRC_CHANNEL_REC *channel, int op, int voice)
632
g_return_val_if_fail(output != NULL, 0);
633
g_return_val_if_fail(mask != NULL, 0);
634
g_return_val_if_fail(IS_IRC_CHANNEL(channel), 0);
637
nicks = nicklist_find_multiple(CHANNEL(channel), mask);
638
for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
639
NICK_REC *rec = tmp->data;
641
if ((op == 1 && !rec->op) || (op == 0 && rec->op) ||
642
(voice == 1 && !rec->voice) || (voice == 0 && rec->voice))
645
if (g_strcasecmp(rec->nick, channel->server->nick) == 0)
648
g_string_sprintfa(output, "%s ", rec->nick);
656
static char *get_nicks(IRC_SERVER_REC *server, WI_ITEM_REC *item,
657
const char *data, int op, int voice,
658
IRC_CHANNEL_REC **ret_channel)
660
IRC_CHANNEL_REC *channel;
663
char **matches, **match, *ret, *channame, *nicks;
665
int count, max_modes;
667
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
668
PARAM_FLAG_OPTIONS | PARAM_FLAG_OPTCHAN_NAME,
669
item, "op", &optlist, &channame, &nicks))
675
channel = irc_channel_find(server, channame);
676
if (channel == NULL) {
677
cmd_params_free(free_arg);
681
str = g_string_new(NULL);
682
matches = g_strsplit(nicks, " ", -1);
683
for (match = matches; *match != NULL; match++) {
684
if (strchr(*match, '*') == NULL &&
685
strchr(*match, '?') == NULL) {
687
g_string_sprintfa(str, "%s ", *match);
689
count = get_wildcard_nicks(str, *match, channel,
691
max_modes = settings_get_int("max_wildcard_modes");
692
if (max_modes > 0 && count > max_modes &&
693
g_hash_table_lookup(optlist, "yes") == NULL) {
694
/* too many matches */
695
g_string_free(str, TRUE);
696
cmd_params_free(free_arg);
698
signal_emit("error command", 1,
699
GINT_TO_POINTER(CMDERR_NOT_GOOD_IDEA));
706
if (str->len > 0) g_string_truncate(str, str->len-1);
708
g_string_free(str, FALSE);
710
cmd_params_free(free_arg);
712
*ret_channel = channel;
716
/* SYNTAX: OP <nicks> */
717
static void cmd_op(const char *data, IRC_SERVER_REC *server,
720
IRC_CHANNEL_REC *channel;
723
CMD_IRC_SERVER(server);
725
nicks = get_nicks(server, item, data, 0, -1, &channel);
726
if (nicks != NULL && *nicks != '\0')
727
channel_set_singlemode(channel, nicks, "+o");
728
g_free_not_null(nicks);
731
/* SYNTAX: DEOP <nicks> */
732
static void cmd_deop(const char *data, IRC_SERVER_REC *server,
735
IRC_CHANNEL_REC *channel;
738
CMD_IRC_SERVER(server);
740
nicks = get_nicks(server, item, data, 1, -1, &channel);
741
if (nicks != NULL && *nicks != '\0')
742
channel_set_singlemode(channel, nicks, "-o");
743
g_free_not_null(nicks);
746
/* SYNTAX: VOICE <nicks> */
747
static void cmd_voice(const char *data, IRC_SERVER_REC *server,
750
IRC_CHANNEL_REC *channel;
753
CMD_IRC_SERVER(server);
755
nicks = get_nicks(server, item, data, 0, 0, &channel);
756
if (nicks != NULL && *nicks != '\0')
757
channel_set_singlemode(channel, nicks, "+v");
758
g_free_not_null(nicks);
761
/* SYNTAX: DEVOICE <nicks> */
762
static void cmd_devoice(const char *data, IRC_SERVER_REC *server,
765
IRC_CHANNEL_REC *channel;
768
CMD_IRC_SERVER(server);
770
nicks = get_nicks(server, item, data, -1, 1, &channel);
771
if (nicks != NULL && *nicks != '\0')
772
channel_set_singlemode(channel, nicks, "-v");
773
g_free_not_null(nicks);
776
/* SYNTAX: MODE <your nick>|<channel> [<mode> [<mode parameters>]] */
777
static void cmd_mode(const char *data, IRC_SERVER_REC *server,
778
IRC_CHANNEL_REC *channel)
780
IRC_CHANNEL_REC *chanrec;
784
CMD_IRC_SERVER(server);
786
if (*data == '+' || *data == '-') {
788
if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST, &mode))
791
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &target, &mode))
795
if (strcmp(target, "*") == 0) {
796
if (!IS_IRC_CHANNEL(channel))
797
cmd_param_error(CMDERR_NOT_JOINED);
799
target = channel->name;
801
if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
804
chanrec = irc_channel_find(server, target);
806
target = chanrec->name;
808
irc_send_cmdv(server, "MODE %s", target);
809
} else if (ischannel(*target))
810
channel_set_mode(server, target, mode);
812
if (g_strcasecmp(target, server->nick) == 0) {
813
server_redirect_event(server, "mode user", 1, target, -1, NULL,
814
"event mode", "requested usermode change", NULL);
817
irc_send_cmdv(server, "MODE %s %s", target, mode);
820
cmd_params_free(free_arg);
823
void modes_server_init(IRC_SERVER_REC *server)
825
server->modes['b'].func = modes_type_a;
826
server->modes['e'].func = modes_type_a;
827
server->modes['I'].func = modes_type_a;
829
server->modes['h'].func = modes_type_prefix;
830
server->modes['h'].prefix = '%';
831
server->modes['o'].func = modes_type_prefix;
832
server->modes['o'].prefix = '@';
833
server->modes['O'].func = modes_type_prefix;
834
server->modes['O'].prefix = '@';
835
server->modes['v'].func = modes_type_prefix;
836
server->modes['v'].prefix = '+';
838
server->prefix['%'] = 'h';
839
server->prefix['@'] = 'o';
840
server->prefix['+'] = 'v';
842
server->modes['k'].func = modes_type_b;
843
server->modes['l'].func = modes_type_c;
846
void modes_init(void)
848
settings_add_str("misc", "opermode", "");
849
settings_add_int("misc", "max_wildcard_modes", 6);
851
signal_add("event 221", (SIGNAL_FUNC) event_user_mode);
852
signal_add("event 305", (SIGNAL_FUNC) event_unaway);
853
signal_add("event 306", (SIGNAL_FUNC) event_away);
854
signal_add("event 381", (SIGNAL_FUNC) event_oper);
855
signal_add("event mode", (SIGNAL_FUNC) event_mode);
856
signal_add("requested usermode change", (SIGNAL_FUNC) sig_req_usermode_change);
858
command_bind_irc("op", NULL, (SIGNAL_FUNC) cmd_op);
859
command_bind_irc("deop", NULL, (SIGNAL_FUNC) cmd_deop);
860
command_bind_irc("voice", NULL, (SIGNAL_FUNC) cmd_voice);
861
command_bind_irc("devoice", NULL, (SIGNAL_FUNC) cmd_devoice);
862
command_bind_irc("mode", NULL, (SIGNAL_FUNC) cmd_mode);
864
command_set_options("op", "yes");
867
void modes_deinit(void)
869
signal_remove("event 221", (SIGNAL_FUNC) event_user_mode);
870
signal_remove("event 305", (SIGNAL_FUNC) event_unaway);
871
signal_remove("event 306", (SIGNAL_FUNC) event_away);
872
signal_remove("event 381", (SIGNAL_FUNC) event_oper);
873
signal_remove("event mode", (SIGNAL_FUNC) event_mode);
874
signal_remove("requested usermode change", (SIGNAL_FUNC) sig_req_usermode_change);
876
command_unbind("op", (SIGNAL_FUNC) cmd_op);
877
command_unbind("deop", (SIGNAL_FUNC) cmd_deop);
878
command_unbind("voice", (SIGNAL_FUNC) cmd_voice);
879
command_unbind("devoice", (SIGNAL_FUNC) cmd_devoice);
880
command_unbind("mode", (SIGNAL_FUNC) cmd_mode);