/* SET command handling.
 *
 * IRC Services is copyright (c) 1996-2002 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * 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 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, "PASSWORD") == 0 || 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 (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;

    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);
    ci->founder = ngi->id;
    count_chan(ci);
    module_log("Changing founder of %s to %s by %s!%s@%s",
	       ci->name, param, u->nick, u->username, u->host);
    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;
	}
	module_log("Changing 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;

    /* 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 (get_access(u, ci) < ACCLEV_FOUNDER) {
	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);
}

/*************************************************************************/

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;
    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;
    ci->mlock_on = 0;
    ci->mlock_off = 0;
    ci->mlock_limit = 0;
    ci->mlock_key = NULL;
    ci->mlock_link = NULL;
    ci->mlock_flood = NULL;

    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_LIMIT_POSITIVE);
		    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);
    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;
}

/*************************************************************************/

#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;
	/* Only set CA_NOJOIN if the levels have already been changed from
	 * the default; otherwise check_access() will take care of it */
	if (co->flag == CI_RESTRICTED
	 && ci->levels
	 && ci->levels[CA_NOJOIN] < 0
	) {
	    ci->levels[CA_NOJOIN] = 0;
	}
	notice_lang(s_ChanServ, u, co->onstr, ci->name);
    } else if (stricmp(param, "OFF") == 0) {
	ci->flags &= ~co->flag;
	if (co->flag == CI_RESTRICTED
	 && ci->levels
	 && ci->levels[CA_NOJOIN] >= 0
	) {
	    ci->levels[CA_NOJOIN] = -100;
	}
	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");
    if (cb_set < 0 || cb_set_mlock < 0) {
	module_log("set: Unable to register callbacks");
	exit_set();
	return 0;
    }
    return 1;
}

/*************************************************************************/

void exit_set()
{
    unregister_callback(module, cb_set_mlock);
    unregister_callback(module, cb_set);
}

/*************************************************************************/
