/* SET command handling. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include "modules.h" #include "language.h" #include "encrypt.h" #include "modules/nickserv/nickserv.h" #include "modules/operserv/operserv.h" #include "chanserv.h" #include "cs-local.h" /*************************************************************************/ static Module *module; static int cb_set = -1; static int cb_set_mlock = -1; static int cb_unset = -1; /*************************************************************************/ static void do_set_founder(User *u, ChannelInfo *ci, char *param); static void do_set_successor(User *u, ChannelInfo *ci, char *param); static void do_set_password(User *u, ChannelInfo *ci, char *param); static void do_set_desc(User *u, ChannelInfo *ci, char *param); static void do_set_url(User *u, ChannelInfo *ci, char *param); static void do_set_email(User *u, ChannelInfo *ci, char *param); static void do_set_entrymsg(User *u, ChannelInfo *ci, char *param); static void do_set_mlock(User *u, ChannelInfo *ci, char *param); static void do_set_hide(User *u, ChannelInfo *ci, char *param, char *extra); static void do_set_boolean(User *u, ChannelInfo *ci, ChanOpt *co, char *param); /*************************************************************************/ /*************************************************************************/ /* Main SET routine. Calls other routines as follows: * do_set_command(User *command_sender, ChannelInfo *ci, char *param); * The parameter passed is the first space-delimited parameter after the * option name, except in the case of DESC, TOPIC, and ENTRYMSG, in which * it is the entire remainder of the line. Additional parameters beyond * the first passed in the function call can be retrieved using * strtok(NULL, toks). * * do_set_boolean, the default handler, is an exception to this in that it * also takes the ChanOpt structure for the selected option as a parameter. */ void do_set(User *u) { char *chan = strtok(NULL, " "); char *cmd = strtok(NULL, " "); char *param; ChannelInfo *ci; int is_servadmin = is_services_admin(u); if (readonly) { notice_lang(s_ChanServ, u, CHAN_SET_DISABLED); return; } if (cmd) { if (stricmp(cmd, "DESC") == 0 || stricmp(cmd, "TOPIC") == 0 || stricmp(cmd, "ENTRYMSG") == 0) param = strtok_remaining(); else param = strtok(NULL, " "); } else { param = NULL; } if (!param) { syntax_error(s_ChanServ, u, "SET", CHAN_SET_SYNTAX); } else if (!(ci = get_channelinfo(chan))) { notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan); } else if (ci->flags & CI_VERBOTEN) { notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan); } else if (!is_servadmin && !check_access_cmd(u, ci, "SET", cmd)) { notice_lang(s_ChanServ, u, ACCESS_DENIED); } else if (call_callback_4(module, cb_set, u, ci, cmd, param) > 0) { return; } else if (stricmp(cmd, "FOUNDER") == 0) { if (!is_servadmin && !is_founder(u, ci)) { notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan); } else { do_set_founder(u, ci, param); } } else if (stricmp(cmd, "SUCCESSOR") == 0) { if (!is_servadmin && !is_founder(u, ci)) { notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan); } else { do_set_successor(u, ci, param); } } else if (stricmp(cmd, "PASSWORD") == 0) { if (!is_servadmin && !is_founder(u, ci)) { notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan); } else { do_set_password(u, ci, param); } } else if (stricmp(cmd, "DESC") == 0) { do_set_desc(u, ci, param); } else if (stricmp(cmd, "URL") == 0) { do_set_url(u, ci, param); } else if (stricmp(cmd, "EMAIL") == 0) { do_set_email(u, ci, param); } else if (stricmp(cmd, "ENTRYMSG") == 0) { do_set_entrymsg(u, ci, param); } else if (stricmp(cmd, "MLOCK") == 0) { do_set_mlock(u, ci, param); } else if (stricmp(cmd, "HIDE") == 0) { do_set_hide(u, ci, param, strtok(NULL," ")); } else { ChanOpt *co = chanopt_from_name(cmd); if (co && co->flag == CI_NOEXPIRE && !is_servadmin) co = NULL; if (co) { do_set_boolean(u, ci, co, param); } else { notice_lang(s_ChanServ, u, CHAN_SET_UNKNOWN_OPTION, strupper(cmd)); notice_lang(s_ChanServ, u, MORE_INFO, s_ChanServ, "SET"); } } } /*************************************************************************/ /* Handler for UNSET. */ void do_unset(User *u) { char *chan = strtok(NULL, " "); char *cmd = strtok(NULL, " "); ChannelInfo *ci; int is_servadmin = is_services_admin(u); if (readonly) { notice_lang(s_ChanServ, u, CHAN_SET_DISABLED); return; } if (!cmd) { syntax_error(s_ChanServ, u, "UNSET", CHAN_UNSET_SYNTAX); } else if (!(ci = get_channelinfo(chan))) { notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan); } else if (ci->flags & CI_VERBOTEN) { notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan); } else if (!is_servadmin && !check_access_cmd(u, ci, "SET", cmd)) { notice_lang(s_ChanServ, u, ACCESS_DENIED); } else if (call_callback_3(module, cb_unset, u, ci, cmd) > 0) { return; } else if (stricmp(cmd, "SUCCESSOR") == 0) { if (!is_servadmin && !is_founder(u, ci)) { notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan); } else { do_set_successor(u, ci, NULL); } } else if (stricmp(cmd, "URL") == 0) { do_set_url(u, ci, NULL); } else if (stricmp(cmd, "EMAIL") == 0) { do_set_email(u, ci, NULL); } else if (stricmp(cmd, "ENTRYMSG") == 0) { do_set_entrymsg(u, ci, NULL); } else { syntax_error(s_ChanServ, u, "UNSET", CHAN_UNSET_SYNTAX); } } /*************************************************************************/ /*************************************************************************/ static void do_set_founder(User *u, ChannelInfo *ci, char *param) { NickInfo *ni = get_nickinfo(param); NickGroupInfo *ngi, *oldngi; if (!ni) { notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param); return; } else if (ni->status & NS_VERBOTEN) { notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param); return; } else if (!(ngi = get_ngi(ni))) { notice_lang(s_ChanServ, u, INTERNAL_ERROR); return; } if ((!is_services_admin(u) && check_channel_limit(ngi, NULL) >= 0) || ngi->channels_count >= MAX_CHANNELCOUNT ) { notice_lang(s_ChanServ, u, CHAN_SET_FOUNDER_TOO_MANY_CHANS, param); return; } uncount_chan(ci); oldngi = get_ngi_id(ci->founder); module_log("Changing founder of %s from %s to %s by %s!%s@%s", ci->name, oldngi ? ngi_mainnick(oldngi) : "", param, u->nick, u->username, u->host); ci->founder = ngi->id; count_chan(ci); if (ci->successor == ci->founder) { module_log("Successor for %s is same as new founder, clearing", ci->name); ci->successor = 0; } notice_lang(s_ChanServ, u, CHAN_FOUNDER_CHANGED, ci->name, param); put_channelinfo(ci); } /*************************************************************************/ static void do_set_successor(User *u, ChannelInfo *ci, char *param) { if (param) { NickInfo *ni; NickGroupInfo *ngi; ni = get_nickinfo(param); if (!ni) { notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param); return; } else if (ni->status & NS_VERBOTEN) { notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param); return; } else if (!(ngi = get_ngi(ni))) { notice_lang(s_ChanServ, u, INTERNAL_ERROR); return; } else if (ngi->id == ci->founder) { notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_IS_FOUNDER); return; } if (ci->successor) { NickGroupInfo *oldngi = get_ngi_id(ci->successor); module_log("Changing successor of %s from %s to %s by %s!%s@%s", ci->name, oldngi ? ngi_mainnick(oldngi) : "", param, u->nick, u->username, u->host); } else { module_log("Setting successor of %s to %s by %s!%s@%s", ci->name, param, u->nick, u->username, u->host); } ci->successor = ngi->id; notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_CHANGED, ci->name, param); } else { module_log("Clearing successor of %s by %s!%s@%s", ci->name, u->nick, u->username, u->host); ci->successor = 0; notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_UNSET, ci->name); } put_channelinfo(ci); } /*************************************************************************/ static void do_set_password(User *u, ChannelInfo *ci, char *param) { int len = strlen(param), max; User *u2; /* Password length check and truncation (like NickServ REGISTER) */ max = encrypt_check_len(len, PASSMAX); if ((max == 0 && len > PASSMAX-1) || max > PASSMAX-1) max = PASSMAX-1; if (max > 0) { memset(param+max, 0, len-max); len = max; notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, max); } if (encrypt(param, len, ci->founderpass, PASSMAX) < 0) { memset(param, 0, len); module_log("Failed to encrypt password for %s (set)", ci->name); notice_lang(s_ChanServ, u, CHAN_SET_PASSWORD_FAILED); return; } if (CSShowPassword) notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED_TO, ci->name, param); else notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED, ci->name); memset(param, 0, len); if (!is_founder(u, ci)) { module_log("%s!%s@%s set password as Services admin for %s", u->nick, u->username, u->host, ci->name); if (WallSetpass) wallops(s_ChanServ, "\2%s\2 set password as Services admin for " "channel \2%s\2", u->nick, ci->name); } put_channelinfo(ci); /* Clear founder privileges from all other users who might have * identified earlier. */ for (u2 = first_user(); u2; u2 = next_user()) { if (u2 != u) { struct u_chaninfolist *c, *c2; LIST_FOREACH_SAFE (c, u2->id_chans, c2) { if (irc_stricmp(c->chan, ci->name) == 0) { LIST_REMOVE(c, u2->id_chans); free(c); } } } } } /*************************************************************************/ static void do_set_desc(User *u, ChannelInfo *ci, char *param) { free(ci->desc); ci->desc = sstrdup(param); notice_lang(s_ChanServ, u, CHAN_DESC_CHANGED, ci->name, param); put_channelinfo(ci); } /*************************************************************************/ static void do_set_url(User *u, ChannelInfo *ci, char *param) { if (param && !valid_url(param)) { notice_lang(s_ChanServ, u, BAD_URL); return; } free(ci->url); if (param) { ci->url = sstrdup(param); notice_lang(s_ChanServ, u, CHAN_URL_CHANGED, ci->name, param); } else { ci->url = NULL; notice_lang(s_ChanServ, u, CHAN_URL_UNSET, ci->name); } put_channelinfo(ci); } /*************************************************************************/ static void do_set_email(User *u, ChannelInfo *ci, char *param) { if (param && !valid_email(param)) { notice_lang(s_ChanServ, u, BAD_EMAIL); return; } free(ci->email); if (param) { ci->email = sstrdup(param); notice_lang(s_ChanServ, u, CHAN_EMAIL_CHANGED, ci->name, param); } else { ci->email = NULL; notice_lang(s_ChanServ, u, CHAN_EMAIL_UNSET, ci->name); } put_channelinfo(ci); } /*************************************************************************/ static void do_set_entrymsg(User *u, ChannelInfo *ci, char *param) { free(ci->entry_message); if (param) { ci->entry_message = sstrdup(param); notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_CHANGED, ci->name, param); } else { ci->entry_message = NULL; notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_UNSET, ci->name); } put_channelinfo(ci); } /*************************************************************************/ static void do_set_mlock(User *u, ChannelInfo *ci, char *param) { char *s, modebuf[40], *end, c; int ac = 0; char *av[MAX_MLOCK_PARAMS]; char **avptr = &av[0]; int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */ int32 flag; int params; int32 oldlock_on, oldlock_off, oldlock_limit, oldlock_joindelay; int32 oldlock_joinrate1, oldlock_joinrate2; char *oldlock_key, *oldlock_link, *oldlock_flood; oldlock_on = ci->mlock_on; oldlock_off = ci->mlock_off; oldlock_limit = ci->mlock_limit; oldlock_key = ci->mlock_key; oldlock_link = ci->mlock_link; oldlock_flood = ci->mlock_flood; oldlock_joindelay = ci->mlock_joindelay; oldlock_joinrate1 = ci->mlock_joinrate1; oldlock_joinrate2 = ci->mlock_joinrate2; ci->mlock_on = 0; ci->mlock_off = 0; ci->mlock_limit = 0; ci->mlock_key = NULL; ci->mlock_link = NULL; ci->mlock_flood = NULL; ci->mlock_joindelay = 0; ci->mlock_joinrate1 = 0; ci->mlock_joinrate2 = 0; while (ac < MAX_MLOCK_PARAMS && (s = strtok(NULL, " ")) != NULL) av[ac++] = s; while (*param) { if (*param == '+') { add = 1; param++; continue; } else if (*param == '-') { add = 0; param++; continue; } else if (add < 0) { notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_NEED_PLUS_MINUS); goto fail; } c = *param++; flag = mode_char_to_flag(c, MODE_CHANNEL); params = mode_char_to_params(c, MODE_CHANNEL); if (!flag) { notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_UNKNOWN_CHAR, c); continue; } else if (flag == MODE_INVALID) { notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_CANNOT_LOCK, c); continue; } /* "Off" locks never take parameters (they prevent the mode from * ever being set in the first place) */ params = add ? (params>>8) & 0xFF : 0; if (params > ac) { notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_NEED_PARAM, c); goto fail; } if (flag & chanmode_reg) notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_MODE_REG_BAD, c); else if (add) ci->mlock_on |= flag, ci->mlock_off &= ~flag; else ci->mlock_off |= flag, ci->mlock_on &= ~flag; switch (c) { case 'k': if (add) { free(ci->mlock_key); ci->mlock_key = sstrdup(avptr[0]); } else { free(ci->mlock_key); ci->mlock_key = NULL; } break; case 'l': if (add) { ci->mlock_limit = atol(avptr[0]); if (ci->mlock_limit <= 0) { notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_NEED_POSITIVE, 'l'); goto fail; } } else { ci->mlock_limit = 0; } break; } /* switch */ if (call_callback_5(module, cb_set_mlock, u, ci, c, add, avptr) > 0) goto fail; ac -= params; avptr += params; } /* while (*param) */ /* Make sure there are no problems. */ if (call_callback_5(module, cb_set_mlock, u, ci, 0, 0, NULL) > 0) goto fail; /* Tell the user about the new mode lock. */ end = modebuf; *end = 0; if (ci->mlock_on || ci->mlock_key || ci->mlock_limit) end += snprintf(end, sizeof(modebuf)-(end-modebuf), "+%s", mode_flags_to_string(ci->mlock_on, MODE_CHANNEL)); if (ci->mlock_off) end += snprintf(end, sizeof(modebuf)-(end-modebuf), "-%s", mode_flags_to_string(ci->mlock_off, MODE_CHANNEL)); if (*modebuf) { notice_lang(s_ChanServ, u, CHAN_MLOCK_CHANGED, ci->name, modebuf); } else { notice_lang(s_ChanServ, u, CHAN_MLOCK_REMOVED, ci->name); } /* Clean up, implement the new lock and return. */ free(oldlock_key); free(oldlock_link); free(oldlock_flood); put_channelinfo(ci); check_modes(ci->c); return; fail: /* Failure; restore the old mode lock. */ free(ci->mlock_key); free(ci->mlock_link); free(ci->mlock_flood); ci->mlock_on = oldlock_on; ci->mlock_off = oldlock_off; ci->mlock_limit = oldlock_limit; ci->mlock_key = oldlock_key; ci->mlock_link = oldlock_link; ci->mlock_flood = oldlock_flood; ci->mlock_joindelay = oldlock_joindelay; ci->mlock_joinrate1 = oldlock_joinrate1; ci->mlock_joinrate2 = oldlock_joinrate2; } /*************************************************************************/ #define HIDE(x) do { \ flag = CI_HIDE_##x; \ onmsg = CHAN_SET_HIDE_##x##_ON; \ offmsg = CHAN_SET_HIDE_##x##_OFF; \ } while (0) static void do_set_hide(User *u, ChannelInfo *ci, char *param, char *extra) { int32 flag; int onmsg, offmsg; #ifdef CLEAN_COMPILE flag = 0; onmsg = offmsg = -1; #endif if (!extra) { syntax_error(s_ChanServ, u, "SET HIDE", CHAN_SET_HIDE_SYNTAX); return; } if (stricmp(param, "EMAIL") == 0) { HIDE(EMAIL); } else if (stricmp(param, "TOPIC") == 0) { HIDE(TOPIC); } else if (stricmp(param, "MLOCK") == 0) { HIDE(MLOCK); } else { syntax_error(s_ChanServ, u, "SET HIDE", CHAN_SET_HIDE_SYNTAX); return; } if (stricmp(extra, "ON") == 0) { ci->flags |= flag; notice_lang(s_ChanServ, u, onmsg, ci->name, s_ChanServ); } else if (stricmp(extra, "OFF") == 0) { ci->flags &= ~flag; notice_lang(s_ChanServ, u, offmsg, ci->name, s_ChanServ); } else { syntax_error(s_ChanServ, u, "SET HIDE", CHAN_SET_HIDE_SYNTAX); } } #undef HIDE /*************************************************************************/ static void do_set_boolean(User *u, ChannelInfo *ci, ChanOpt *co, char *param) { if (stricmp(param, "ON") == 0) { ci->flags |= co->flag; notice_lang(s_ChanServ, u, co->onstr, ci->name); } else if (stricmp(param, "OFF") == 0) { ci->flags &= ~co->flag; notice_lang(s_ChanServ, u, co->offstr, ci->name); } else { char buf[BUFSIZE]; snprintf(buf, sizeof(buf), "SET %s", co->name); syntax_error(s_ChanServ, u, buf, co->syntaxstr); return; } put_channelinfo(ci); } /*************************************************************************/ /*************************************************************************/ int init_set(Module *my_module) { module = my_module; cb_set = register_callback(module, "SET"); cb_set_mlock = register_callback(module, "SET MLOCK"); cb_unset = register_callback(module, "UNSET"); if (cb_set < 0 || cb_set_mlock < 0 || cb_unset < 0) { module_log("set: Unable to register callbacks"); exit_set(); return 0; } return 1; } /*************************************************************************/ void exit_set() { unregister_callback(module, cb_unset); unregister_callback(module, cb_set_mlock); unregister_callback(module, cb_set); } /*************************************************************************/