summaryrefslogtreecommitdiffstats
path: root/src/irc/core/modes.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/irc/core/modes.c')
-rw-r--r--src/irc/core/modes.c932
1 files changed, 932 insertions, 0 deletions
diff --git a/src/irc/core/modes.c b/src/irc/core/modes.c
new file mode 100644
index 0000000..b0de2f1
--- /dev/null
+++ b/src/irc/core/modes.c
@@ -0,0 +1,932 @@
+/*
+ modes.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/irc/core/irc-commands.h>
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/irc/core/servers-redirect.h>
+#include <irssi/src/irc/core/modes.h>
+#include <irssi/src/irc/core/mode-lists.h>
+#include <irssi/src/core/nicklist.h>
+
+/* Change nick's mode in channel */
+static void nick_mode_change(IRC_CHANNEL_REC *channel, const char *nick,
+ char mode, int type, const char *setby)
+{
+ NICK_REC *nickrec;
+ char modestr[2], typestr[2];
+
+ g_return_if_fail(IS_IRC_CHANNEL(channel));
+ g_return_if_fail(nick != NULL);
+
+ nickrec = nicklist_find(CHANNEL(channel), nick);
+ if (nickrec == NULL) return; /* No /names list got yet */
+
+ if (mode == '@') nickrec->op = type == '+';
+ else if (mode == '+') nickrec->voice = type == '+';
+ else if (mode == '%') nickrec->halfop = type == '+';
+ if (channel->server->prefix[(unsigned char) mode] != '\0') {
+ if (type == '+')
+ prefix_add(nickrec->prefixes, mode, (SERVER_REC *) channel->server);
+ else
+ prefix_del(nickrec->prefixes, mode);
+ }
+
+ modestr[0] = mode; modestr[1] = '\0';
+ typestr[0] = type; typestr[1] = '\0';
+ signal_emit("nick mode changed", 5,
+ channel, nickrec, setby, modestr, typestr);
+}
+
+void prefix_add(char prefixes[MAX_USER_PREFIXES+1], char newprefix, SERVER_REC *server)
+{
+ const char *prefixlst;
+ char newprefixes[MAX_USER_PREFIXES+1]; /* to hold the new prefixes */
+ unsigned int newpos = 0; /* to hold our position in the new prefixes */
+ unsigned int oldpos = 0; /* to hold our position in the old prefixes */
+
+ prefixlst = server->get_nick_flags(server);
+
+ /* go through the possible prefixes, copy higher ones, and find this one's place
+ * always leave room for the current prefix to be added, though.
+ */
+ while (*prefixlst != '\0' && prefixes[oldpos] != '\0' &&
+ newpos < MAX_USER_PREFIXES - 1) {
+ if (prefixes[oldpos] == newprefix)
+ return; /* already inserted. why are we here? */
+
+ if (*prefixlst == newprefix)
+ break; /* insert the new prefix here */
+
+ if (*prefixlst == prefixes[oldpos]) {
+ /* this prefix is present.
+ * the one we are inserting goes after it.
+ * copy it over, and continue searching.
+ */
+ newprefixes[newpos++] = prefixes[oldpos++];
+ }
+ prefixlst++;
+ }
+
+ /* newpos is now the position in which we wish to insert the prefix */
+ newprefixes[newpos++] = newprefix;
+
+ /* finish copying the remaining prefixes */
+ while (prefixes[oldpos] != '\0' && newpos < MAX_USER_PREFIXES)
+ newprefixes[newpos++] = prefixes[oldpos++];
+
+ newprefixes[newpos] = '\0';
+
+ strcpy(prefixes, newprefixes);
+}
+
+void prefix_del(char prefixes[MAX_USER_PREFIXES+1], char oldprefix)
+{
+ char *todel;
+
+ todel = strchr(prefixes, oldprefix);
+ if (todel)
+ memmove(todel, todel+1, strlen(todel));
+}
+
+static int mode_is_set(const char *str, char mode)
+{
+ char *end, *pos;
+
+ g_return_val_if_fail(str != NULL, FALSE);
+
+ end = strchr(str, ' ');
+ pos = strchr(str, mode);
+ return pos != NULL && (end == NULL || pos < end);
+}
+
+/* add argument to specified position */
+static void mode_add_arg(GString *str, int pos, int updating, const char *arg)
+{
+ char *p;
+
+ for (p = str->str; *p != '\0'; p++) {
+ if (*p != ' ')
+ continue;
+
+ if (pos == 0)
+ break;
+ pos--;
+ }
+
+ pos = (int) (p-str->str);
+ if (updating && *p != '\0') {
+ /* remove the old argument */
+ p++;
+ while (*p != '\0' && *p != ' ') p++;
+ g_string_erase(str, pos, (int) (p-str->str)-pos);
+ }
+
+ g_string_insert_c(str, pos, ' ');
+ g_string_insert(str, pos+1, arg);
+}
+
+/* Add mode character to list sorted alphabetically */
+static void mode_add_sorted(IRC_SERVER_REC *server, GString *str,
+ char mode, const char *arg, int user)
+{
+ char *p;
+ int updating, argpos = 0;
+
+ /* check that mode isn't already set */
+ if ((!user && !HAS_MODE_ARG_SET(server, mode)) &&
+ mode_is_set(str->str, mode))
+ return;
+
+ updating = FALSE;
+ for (p = str->str; *p != '\0' && *p != ' '; p++) {
+ if (mode < *p)
+ break;
+ if (mode == *p) {
+ updating = TRUE;
+ break;
+ }
+ if (!user && HAS_MODE_ARG_SET(server, *p))
+ argpos++;
+ }
+
+ /* .. GLib shouldn't fail when inserting at the end of the string */
+ if (!updating) {
+ if (*p == '\0')
+ g_string_append_c(str, mode);
+ else
+ g_string_insert_c(str, (int) (p-str->str), mode);
+ }
+ if (arg != NULL)
+ mode_add_arg(str, argpos, updating, arg);
+}
+
+/* remove the n'th argument */
+static void node_remove_arg(GString *str, int pos)
+{
+ char *p;
+ int startpos;
+
+ startpos = -1;
+ for (p = str->str; *p != '\0'; p++) {
+ if (*p != ' ')
+ continue;
+
+ if (pos < 0)
+ break;
+ if (pos == 0)
+ startpos = (int) (p-str->str);
+ pos--;
+ }
+
+ if (startpos == -1)
+ return; /* not found */
+
+ g_string_erase(str, startpos, (int) (p-str->str)-startpos);
+}
+
+/* remove mode (and it's argument) from string */
+static void mode_remove(IRC_SERVER_REC *server, GString *str, char mode, int user)
+{
+ char *p;
+ int argpos = 0;
+
+ for (p = str->str; *p != '\0' && *p != ' '; p++) {
+ if (mode == *p) {
+ g_string_erase(str, (int) (p-str->str), 1);
+ if (!user && HAS_MODE_ARG_SET(server, mode))
+ node_remove_arg(str, argpos);
+ break;
+ }
+ if (!user && HAS_MODE_ARG_SET(server, *p))
+ argpos++;
+ }
+}
+
+static void mode_set(IRC_SERVER_REC *server, GString *str,
+ char type, char mode, int user)
+{
+ g_return_if_fail(str != NULL);
+
+ if (type == '-')
+ mode_remove(server, str, mode, user);
+ else
+ mode_add_sorted(server, str, mode, NULL, user);
+}
+
+static void mode_set_arg(IRC_SERVER_REC *server, GString *str,
+ char type, char mode, const char *arg, int user)
+{
+ g_return_if_fail(str != NULL);
+ g_return_if_fail(type == '-' || arg != NULL);
+
+ if (type == '-')
+ mode_remove(server, str, mode, user);
+ else
+ mode_add_sorted(server, str, mode, arg, user);
+}
+
+/* Mode that needs a parameter of a mask for both setting and removing
+ (eg: bans) */
+void modes_type_a(IRC_CHANNEL_REC *channel, const char *setby, char type,
+ char mode, char *arg, GString *newmode)
+{
+ if (mode == 'b') {
+ if (type == '+')
+ banlist_add(channel, arg, setby, time(NULL));
+ else
+ banlist_remove(channel, arg, setby);
+ }
+}
+
+/* Mode that needs parameter for both setting and removing (eg: +k) */
+void modes_type_b(IRC_CHANNEL_REC *channel, const char *setby, char type,
+ char mode, char *arg, GString *newmode)
+{
+ if (mode == 'k') {
+ if (*arg == '\0' && type == '+')
+ arg = channel->key != NULL ? channel->key : "???";
+
+ if (arg != channel->key) {
+ g_free_and_null(channel->key);
+ if (type == '+')
+ channel->key = g_strdup(arg);
+ }
+ }
+
+ mode_set_arg(channel->server, newmode, type, mode, arg, FALSE);
+}
+
+/* Mode that needs parameter only for adding */
+void modes_type_c(IRC_CHANNEL_REC *channel, const char *setby,
+ char type, char mode, char *arg, GString *newmode)
+{
+ if (mode == 'l') {
+ channel->limit = type == '-' ? 0 : atoi(arg);
+ }
+
+ mode_set_arg(channel->server, newmode, type, mode, arg, FALSE);
+}
+
+/* Mode that takes no parameter */
+void modes_type_d(IRC_CHANNEL_REC *channel, const char *setby,
+ char type, char mode, char *arg, GString *newmode)
+{
+ mode_set(channel->server, newmode, type, mode, FALSE);
+}
+
+void modes_type_prefix(IRC_CHANNEL_REC *channel, const char *setby,
+ char type, char mode, char *arg, GString *newmode)
+{
+ int umode = (unsigned char) mode;
+
+ if (g_ascii_strcasecmp(channel->server->nick, arg) == 0) {
+ /* see if we need to update channel->chanop */
+ const char *prefix =
+ g_hash_table_lookup(channel->server->isupport, "PREFIX");
+ if (prefix != NULL && *prefix == '(') {
+ prefix++;
+ while (*prefix != ')' && *prefix != '\0') {
+ if (*prefix == mode) {
+ channel->chanop = type == '+';
+ break;
+ }
+ if (*prefix == 'o')
+ break;
+ prefix++;
+ }
+ } else {
+ if (mode == 'o' || mode == 'O')
+ channel->chanop = type == '+';
+ }
+ }
+
+ nick_mode_change(channel, arg, channel->server->modes[umode].prefix,
+ type, setby);
+}
+
+int channel_mode_is_set(IRC_CHANNEL_REC *channel, char mode)
+{
+ g_return_val_if_fail(IS_IRC_CHANNEL(channel), FALSE);
+
+ return channel->mode == NULL ? FALSE :
+ mode_is_set(channel->mode, mode);
+}
+
+/* Parse channel mode string */
+void parse_channel_modes(IRC_CHANNEL_REC *channel, const char *setby,
+ const char *mode, int update_key)
+{
+ IRC_SERVER_REC *server = channel->server;
+ GString *newmode;
+ char *dup, *modestr, *arg, *curmode, type, *old_key;
+ int umode;
+
+ g_return_if_fail(IS_IRC_CHANNEL(channel));
+ g_return_if_fail(mode != NULL);
+
+ type = '+';
+ newmode = g_string_new(channel->mode);
+ old_key = update_key ? NULL : g_strdup(channel->key);
+
+ dup = modestr = g_strdup(mode);
+ curmode = cmd_get_param(&modestr);
+ while (*curmode != '\0') {
+ if (HAS_MODE_ARG(server, type, *curmode)) {
+ /* get the argument for the mode. NOTE: We don't
+ get the +k's argument when joining to channel. */
+ arg = cmd_get_param(&modestr);
+ } else {
+ arg = NULL;
+ }
+
+ switch (*curmode) {
+ case '+':
+ case '-':
+ type = *curmode;
+ break;
+ default:
+ umode = (unsigned char) *curmode;
+ if (server->modes[umode].func != NULL) {
+ server->modes[umode].func(channel, setby,
+ type, *curmode, arg,
+ newmode);
+ } else {
+ /* Treat unknown modes as ones without params */
+ modes_type_d(channel, setby, type, *curmode,
+ arg, newmode);
+ }
+ }
+
+ curmode++;
+ }
+ g_free(dup);
+
+ if (channel->key != NULL &&
+ strchr(channel->mode, 'k') == NULL &&
+ strchr(newmode->str, 'k') == NULL) {
+ /* join was used with key but there's no key set
+ in channel modes.. */
+ g_free(channel->key);
+ channel->key = NULL;
+ } else if (!update_key && old_key != NULL) {
+ /* get the old one back, just in case it was replaced */
+ g_free(channel->key);
+ channel->key = old_key;
+ mode_set_arg(channel->server, newmode, '+', 'k', old_key, FALSE);
+ old_key = NULL;
+ }
+
+ if (g_strcmp0(newmode->str, channel->mode) != 0) {
+ g_free(channel->mode);
+ channel->mode = g_strdup(newmode->str);
+
+ signal_emit("channel mode changed", 2, channel, setby);
+ }
+
+ g_string_free(newmode, TRUE);
+ g_free(old_key);
+}
+
+/* add `mode' to `old' - return newly allocated mode.
+ `channel' specifies if we're parsing channel mode and we should try
+ to join mode arguments too. */
+char *modes_join(IRC_SERVER_REC *server, const char *old,
+ const char *mode, int channel)
+{
+ GString *newmode;
+ char *dup, *modestr, *curmode, type;
+
+ g_return_val_if_fail(mode != NULL, NULL);
+
+ type = '+';
+ newmode = g_string_new(old);
+
+ dup = modestr = g_strdup(mode);
+ curmode = cmd_get_param(&modestr);
+ while (*curmode != '\0' && *curmode != ' ') {
+ if (*curmode == '+' || *curmode == '-') {
+ type = *curmode;
+ curmode++;
+ continue;
+ }
+
+ if (!channel || !HAS_MODE_ARG(server, type, *curmode))
+ mode_set(server, newmode, type, *curmode, !channel);
+ else {
+ mode_set_arg(server, newmode, type, *curmode,
+ cmd_get_param(&modestr), !channel);
+ }
+
+ curmode++;
+ }
+ g_free(dup);
+
+ modestr = newmode->str;
+ g_string_free(newmode, FALSE);
+ return modestr;
+}
+
+/* Parse user mode string */
+static void parse_user_mode(IRC_SERVER_REC *server, const char *modestr)
+{
+ char *newmode, *oldmode;
+
+ g_return_if_fail(IS_IRC_SERVER(server));
+ g_return_if_fail(modestr != NULL);
+
+ newmode = modes_join(NULL, server->usermode, modestr, FALSE);
+ oldmode = server->usermode;
+ server->usermode = newmode;
+ server->server_operator = ((strchr(newmode, 'o') != NULL) || (strchr(newmode, 'O') != NULL));
+
+ signal_emit("user mode changed", 2, server, oldmode);
+ g_free_not_null(oldmode);
+}
+
+static void event_user_mode(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &mode);
+ parse_user_mode(server, mode);
+
+ g_free(params);
+}
+
+static void event_mode(IRC_SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ IRC_CHANNEL_REC *chanrec;
+ char *params, *channel, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ &channel, &mode);
+
+ if (!server_ischannel(SERVER(server), channel)) {
+ /* user mode change */
+ parse_user_mode(server, mode);
+ } else {
+ /* channel mode change */
+ chanrec = irc_channel_find(server, channel);
+ if (chanrec != NULL)
+ parse_channel_modes(chanrec, nick, mode, TRUE);
+ }
+
+ g_free(params);
+}
+
+static void event_oper(IRC_SERVER_REC *server, const char *data)
+{
+ const char *opermode;
+
+ opermode = settings_get_str("opermode");
+ if (*opermode != '\0')
+ irc_send_cmdv(server, "MODE %s %s", server->nick, opermode);
+}
+
+static void event_away(IRC_SERVER_REC *server, const char *data)
+{
+ g_return_if_fail(server != NULL);
+
+ server->usermode_away = TRUE;
+ signal_emit("away mode changed", 1, server);
+}
+
+static void event_unaway(IRC_SERVER_REC *server, const char *data)
+{
+ g_return_if_fail(server != NULL);
+
+ server->usermode_away = FALSE;
+ g_free_and_null(server->away_reason);
+ signal_emit("away mode changed", 1, server);
+}
+
+static void sig_req_usermode_change(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *target, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ &target, &mode);
+ if (!server_ischannel(SERVER(server), target)) {
+ /* we requested a user mode change, save this */
+ mode = modes_join(NULL, server->wanted_usermode, mode, FALSE);
+ g_free_not_null(server->wanted_usermode);
+ server->wanted_usermode = mode;
+ }
+
+ g_free(params);
+
+ signal_emit("event mode", 4, server, data, nick, addr);
+}
+
+void channel_set_singlemode(IRC_CHANNEL_REC *channel, const char *nicks,
+ const char *mode)
+{
+ GString *str;
+ int num, modepos;
+ char **nick, **nicklist;
+
+ g_return_if_fail(IS_IRC_CHANNEL(channel));
+ g_return_if_fail(nicks != NULL && mode != NULL);
+ if (*nicks == '\0') return;
+
+ num = modepos = 0;
+ str = g_string_new(NULL);
+
+ nicklist = g_strsplit(nicks, " ", -1);
+ for (nick = nicklist; *nick != NULL; nick++) {
+ if (**nick == '\0')
+ continue;
+
+ if (num == 0)
+ {
+ g_string_printf(str, "MODE %s %s",
+ channel->name, mode);
+ modepos = str->len;
+ } else {
+ /* insert the mode string */
+ g_string_insert(str, modepos, mode);
+ }
+
+ g_string_append_printf(str, " %s", *nick);
+
+ if (++num == channel->server->max_modes_in_cmd) {
+ /* max. modes / command reached, send to server */
+ irc_send_cmd(channel->server, str->str);
+ num = 0;
+ }
+ }
+ if (num > 0) irc_send_cmd(channel->server, str->str);
+
+ g_strfreev(nicklist);
+ g_string_free(str, TRUE);
+}
+
+void channel_set_mode(IRC_SERVER_REC *server, const char *channel,
+ const char *mode)
+{
+ IRC_CHANNEL_REC *chanrec;
+ GString *tmode, *targs;
+ char *modestr, *curmode, *orig, type, prevtype;
+ int count;
+
+ g_return_if_fail(IS_IRC_SERVER(server));
+ g_return_if_fail(channel != NULL && mode != NULL);
+
+ tmode = g_string_new(NULL);
+ targs = g_string_new(NULL);
+ count = 0;
+
+ chanrec = irc_channel_find(server, channel);
+ if (chanrec != NULL)
+ channel = chanrec->name;
+
+ orig = modestr = g_strdup(mode);
+
+ type = '+'; prevtype = '\0';
+ curmode = cmd_get_param(&modestr);
+ for (;; curmode++) {
+ if (*curmode == '\0') {
+ /* support for +o nick +o nick2 */
+ curmode = cmd_get_param(&modestr);
+ if (*curmode == '\0')
+ break;
+ }
+
+ if (*curmode == '+' || *curmode == '-') {
+ type = *curmode;
+ continue;
+ }
+
+ if (count == server->max_modes_in_cmd &&
+ HAS_MODE_ARG(server, type, *curmode)) {
+ irc_send_cmdv(server, "MODE %s %s%s",
+ channel, tmode->str, targs->str);
+
+ count = 0; prevtype = '\0';
+ g_string_truncate(tmode, 0);
+ g_string_truncate(targs, 0);
+ }
+
+ if (type != prevtype) {
+ prevtype = type;
+ g_string_append_c(tmode, type);
+ }
+ g_string_append_c(tmode, *curmode);
+
+ if (HAS_MODE_ARG(server, type, *curmode)) {
+ char *arg;
+
+ count++;
+ arg = cmd_get_param(&modestr);
+ if (*arg == '\0' && type == '-' && *curmode == 'k') {
+ /* "/mode #channel -k" - no reason why it
+ shouldn't work really, so append the key */
+ IRC_CHANNEL_REC *chanrec;
+
+ chanrec = irc_channel_find(server, channel);
+ if (chanrec != NULL && chanrec->key != NULL)
+ arg = chanrec->key;
+ }
+
+ if (*arg != '\0')
+ g_string_append_printf(targs, " %s", arg);
+ }
+ }
+
+ if (tmode->len > 0) {
+ irc_send_cmdv(server, "MODE %s %s%s",
+ channel, tmode->str, targs->str);
+ }
+
+ g_string_free(tmode, TRUE);
+ g_string_free(targs, TRUE);
+ g_free(orig);
+}
+
+static int get_wildcard_nicks(GString *output, const char *mask,
+ IRC_CHANNEL_REC *channel, int op, int voice)
+{
+ GSList *nicks, *tmp;
+ int count;
+
+ g_return_val_if_fail(output != NULL, 0);
+ g_return_val_if_fail(mask != NULL, 0);
+ g_return_val_if_fail(IS_IRC_CHANNEL(channel), 0);
+
+ count = 0;
+ nicks = nicklist_find_multiple(CHANNEL(channel), mask);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ if ((op == 1 && !rec->op) || (op == 0 && rec->op) ||
+ (voice == 1 && !rec->voice) || (voice == 0 && rec->voice))
+ continue;
+
+ if (g_ascii_strcasecmp(rec->nick, channel->server->nick) == 0)
+ continue;
+
+ g_string_append_printf(output, "%s ", rec->nick);
+ count++;
+ }
+ g_slist_free(nicks);
+
+ return count;
+}
+
+static char *get_nicks(IRC_SERVER_REC *server, WI_ITEM_REC *item,
+ const char *data, int op, int voice,
+ IRC_CHANNEL_REC **ret_channel)
+{
+ IRC_CHANNEL_REC *channel;
+ GString *str;
+ GHashTable *optlist;
+ char **matches, **match, *ret, *channame, *nicks;
+ void *free_arg;
+ int count, max_modes;
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
+ PARAM_FLAG_OPTIONS | PARAM_FLAG_OPTCHAN_NAME,
+ item, "op", &optlist, &channame, &nicks))
+ return NULL;
+
+ if (*nicks == '\0')
+ return NULL;
+
+ channel = irc_channel_find(server, channame);
+ if (channel == NULL) {
+ cmd_params_free(free_arg);
+ return NULL;
+ }
+
+ str = g_string_new(NULL);
+ matches = g_strsplit(nicks, " ", -1);
+ for (match = matches; *match != NULL; match++) {
+ if (strchr(*match, '*') == NULL &&
+ strchr(*match, '?') == NULL) {
+ /* no wildcards */
+ g_string_append_printf(str, "%s ", *match);
+ } else {
+ count = get_wildcard_nicks(str, *match, channel,
+ op, voice);
+ max_modes = settings_get_int("max_wildcard_modes");
+ if (max_modes > 0 && count > max_modes &&
+ g_hash_table_lookup(optlist, "yes") == NULL) {
+ /* too many matches */
+ g_string_free(str, TRUE);
+ g_strfreev(matches);
+ cmd_params_free(free_arg);
+
+ signal_emit("error command", 1,
+ GINT_TO_POINTER(CMDERR_NOT_GOOD_IDEA));
+ signal_stop();
+ return NULL;
+ }
+ }
+ }
+
+ if (str->len > 0) g_string_truncate(str, str->len-1);
+ ret = str->str;
+ g_string_free(str, FALSE);
+ g_strfreev(matches);
+ cmd_params_free(free_arg);
+
+ *ret_channel = channel;
+ return ret;
+}
+
+/* SYNTAX: OP <nicks> */
+static void cmd_op(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ IRC_CHANNEL_REC *channel;
+ char *nicks;
+
+ CMD_IRC_SERVER(server);
+
+ nicks = get_nicks(server, item, data, 0, -1, &channel);
+ if (nicks != NULL && *nicks != '\0')
+ channel_set_singlemode(channel, nicks, "+o");
+ g_free_not_null(nicks);
+}
+
+/* SYNTAX: DEOP <nicks> */
+static void cmd_deop(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ IRC_CHANNEL_REC *channel;
+ char *nicks;
+
+ CMD_IRC_SERVER(server);
+
+ nicks = get_nicks(server, item, data, 1, -1, &channel);
+ if (nicks != NULL && *nicks != '\0')
+ channel_set_singlemode(channel, nicks, "-o");
+ g_free_not_null(nicks);
+}
+
+/* SYNTAX: VOICE <nicks> */
+static void cmd_voice(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ IRC_CHANNEL_REC *channel;
+ char *nicks;
+
+ CMD_IRC_SERVER(server);
+
+ nicks = get_nicks(server, item, data, 0, 0, &channel);
+ if (nicks != NULL && *nicks != '\0')
+ channel_set_singlemode(channel, nicks, "+v");
+ g_free_not_null(nicks);
+}
+
+/* SYNTAX: DEVOICE <nicks> */
+static void cmd_devoice(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ IRC_CHANNEL_REC *channel;
+ char *nicks;
+
+ CMD_IRC_SERVER(server);
+
+ nicks = get_nicks(server, item, data, -1, 1, &channel);
+ if (nicks != NULL && *nicks != '\0')
+ channel_set_singlemode(channel, nicks, "-v");
+ g_free_not_null(nicks);
+}
+
+/* SYNTAX: MODE <your nick>|<channel> [<mode> [<mode parameters>]] */
+static void cmd_mode(const char *data, IRC_SERVER_REC *server,
+ IRC_CHANNEL_REC *channel)
+{
+ IRC_CHANNEL_REC *chanrec;
+ char *target, *mode;
+ void *free_arg;
+
+ CMD_IRC_SERVER(server);
+
+ if (*data == '+' || *data == '-') {
+ target = "*";
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, &mode))
+ return;
+ } else {
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, &target, &mode))
+ return;
+ }
+
+ if (g_strcmp0(target, "*") == 0) {
+ if (!IS_IRC_CHANNEL(channel))
+ cmd_param_error(CMDERR_NOT_JOINED);
+
+ target = channel->name;
+ }
+ if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (*mode == '\0') {
+ chanrec = irc_channel_find(server, target);
+ if (chanrec != NULL)
+ target = chanrec->name;
+
+ irc_send_cmdv(server, "MODE %s", target);
+ } else if (server_ischannel(SERVER(server), target))
+ channel_set_mode(server, target, mode);
+ else {
+ if (g_ascii_strcasecmp(target, server->nick) == 0) {
+ server_redirect_event(server, "mode user", 1, target, -1, NULL,
+ "event mode", "requested usermode change", NULL);
+ }
+
+ irc_send_cmdv(server, "MODE %s %s", target, mode);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+void modes_server_init(IRC_SERVER_REC *server)
+{
+ server->modes['b'].func = modes_type_a;
+ server->modes['e'].func = modes_type_a;
+ server->modes['I'].func = modes_type_a;
+
+ server->modes['h'].func = modes_type_prefix;
+ server->modes['h'].prefix = '%';
+ server->modes['o'].func = modes_type_prefix;
+ server->modes['o'].prefix = '@';
+ server->modes['O'].func = modes_type_prefix;
+ server->modes['O'].prefix = '@';
+ server->modes['v'].func = modes_type_prefix;
+ server->modes['v'].prefix = '+';
+
+ server->prefix['%'] = 'h';
+ server->prefix['@'] = 'o';
+ server->prefix['+'] = 'v';
+
+ server->modes['k'].func = modes_type_b;
+ server->modes['l'].func = modes_type_c;
+}
+
+void modes_init(void)
+{
+ settings_add_str("misc", "opermode", "");
+ settings_add_int("misc", "max_wildcard_modes", 6);
+
+ signal_add("event 221", (SIGNAL_FUNC) event_user_mode);
+ signal_add("event 305", (SIGNAL_FUNC) event_unaway);
+ signal_add("event 306", (SIGNAL_FUNC) event_away);
+ signal_add("event 381", (SIGNAL_FUNC) event_oper);
+ signal_add("event mode", (SIGNAL_FUNC) event_mode);
+ signal_add("requested usermode change", (SIGNAL_FUNC) sig_req_usermode_change);
+
+ command_bind_irc("op", NULL, (SIGNAL_FUNC) cmd_op);
+ command_bind_irc("deop", NULL, (SIGNAL_FUNC) cmd_deop);
+ command_bind_irc("voice", NULL, (SIGNAL_FUNC) cmd_voice);
+ command_bind_irc("devoice", NULL, (SIGNAL_FUNC) cmd_devoice);
+ command_bind_irc("mode", NULL, (SIGNAL_FUNC) cmd_mode);
+
+ command_set_options("op", "yes");
+}
+
+void modes_deinit(void)
+{
+ signal_remove("event 221", (SIGNAL_FUNC) event_user_mode);
+ signal_remove("event 305", (SIGNAL_FUNC) event_unaway);
+ signal_remove("event 306", (SIGNAL_FUNC) event_away);
+ signal_remove("event 381", (SIGNAL_FUNC) event_oper);
+ signal_remove("event mode", (SIGNAL_FUNC) event_mode);
+ signal_remove("requested usermode change", (SIGNAL_FUNC) sig_req_usermode_change);
+
+ command_unbind("op", (SIGNAL_FUNC) cmd_op);
+ command_unbind("deop", (SIGNAL_FUNC) cmd_deop);
+ command_unbind("voice", (SIGNAL_FUNC) cmd_voice);
+ command_unbind("devoice", (SIGNAL_FUNC) cmd_devoice);
+ command_unbind("mode", (SIGNAL_FUNC) cmd_mode);
+}