/* Routines to handle the NickServ SET command.
 *
 * 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/operserv/operserv.h"

#include "nickserv.h"
#include "ns-local.h"

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

static Module *module;
static int cb_set = -1;
static int cb_set_email = -1;

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

static void do_set_password(User *u, NickGroupInfo *ngi, NickInfo *ni,
			    char *param);
static void do_set_language(User *u, NickGroupInfo *ngi, char *param);
static void do_set_url(User *u, NickGroupInfo *ngi, char *param);
static void do_set_email(User *u, NickGroupInfo *ngi, char *param);
static void do_set_info(User *u, NickGroupInfo *ngi, char *param);
static void do_set_kill(User *u, NickGroupInfo *ngi, char *param);
static void do_set_secure(User *u, NickGroupInfo *ngi, char *param);
static void do_set_private(User *u, NickGroupInfo *ngi, char *param);
static void do_set_hide(User *u, NickGroupInfo *ngi, char *param,
			char *setting);
static void do_set_timezone(User *u, NickGroupInfo *ngi, char *param);
static void do_set_noexpire(User *u, NickInfo *ni, char *param);

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

void do_set(User *u)
{
    char *cmd   = strtok(NULL, " ");
    char *param = strtok_remaining();
    char *extra = NULL;
    NickInfo *ni;
    NickGroupInfo *ngi;
    int is_servadmin = is_services_admin(u);

    if (readonly) {
	notice_lang(s_NickServ, u, NICK_SET_DISABLED);
	return;
    }

    if (is_servadmin && param && strchr(param, ' ')
     && (ni = get_nickinfo(cmd))
    ) {
	cmd = strtok(param, " ");
	param = strtok_remaining();
    } else {
	ni = u->ni;
    }
    if (cmd && stricmp(cmd, "PASSWORD") != 0 && stricmp(cmd, "INFO") != 0) {
	param = strtok(param, " ");
	extra = strtok(NULL, " ");
    }
    if (!cmd || !param || (stricmp(cmd,"HIDE")==0 && !extra)) {
	if (is_oper(u))
	    syntax_error(s_NickServ, u, "SET", NICK_SET_OPER_SYNTAX);
	else
	    syntax_error(s_NickServ, u, "SET", NICK_SET_SYNTAX);
    } else if (!ni) {
	notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
    } else if (ni->status & NS_VERBOTEN) {
	notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, ni->nick);
    } else if (!(ngi = get_ngi(ni))) {
	notice_lang(s_NickServ, u, INTERNAL_ERROR);
    } else if (!is_servadmin && !user_identified(u)
	       && !(stricmp(cmd,"EMAIL")==0 && user_ident_nomail(u))) {
	notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
    } else if (call_callback_5(module, cb_set, u, ni, ngi, cmd, param) > 0) {
	return;
    } else if (stricmp(cmd, "PASSWORD") == 0) {
	do_set_password(u, ngi, ni, param);
    } else if (stricmp(cmd, "LANGUAGE") == 0) {
	do_set_language(u, ngi, param);
    } else if (stricmp(cmd, "URL") == 0) {
	do_set_url(u, ngi, param);
    } else if (stricmp(cmd, "EMAIL") == 0) {
	do_set_email(u, ngi, param);
    } else if (stricmp(cmd, "INFO") == 0) {
	do_set_info(u, ngi, param);
    } else if (stricmp(cmd, "KILL") == 0) {
	do_set_kill(u, ngi, param);
    } else if (stricmp(cmd, "SECURE") == 0) {
	do_set_secure(u, ngi, param);
    } else if (stricmp(cmd, "PRIVATE") == 0) {
	do_set_private(u, ngi, param);
    } else if (stricmp(cmd, "HIDE") == 0) {
	do_set_hide(u, ngi, param, extra);
    } else if (stricmp(cmd, "TIMEZONE") == 0) {
	do_set_timezone(u, ngi, param);
    } else if (stricmp(cmd, "NOEXPIRE") == 0) {
	do_set_noexpire(u, ni, param);
    } else {
	if (is_servadmin)
	    notice_lang(s_NickServ, u, NICK_SET_UNKNOWN_OPTION_OR_BAD_NICK,
			strupper(cmd));
	else
	    notice_lang(s_NickServ, u, NICK_SET_UNKNOWN_OPTION, strupper(cmd));
    }
}

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

void do_unset(User *u)
{
    char *cmd   = strtok(NULL, " ");
    char *extra = strtok(NULL, " ");
    NickInfo *ni;
    NickGroupInfo *ngi;
    int is_servadmin = is_services_admin(u);

    if (readonly) {
	notice_lang(s_NickServ, u, NICK_SET_DISABLED);
	return;
    }

    if (is_servadmin && extra && (ni = get_nickinfo(cmd))) {
	cmd = extra;
	extra = strtok(NULL, " ");
    } else {
	ni = u->ni;
    }
    if (!cmd || extra) {
	syntax_error(s_NickServ, u, "UNSET",
	    NSRequireEmail ? NICK_UNSET_SYNTAX_REQ_EMAIL : NICK_UNSET_SYNTAX);
    } else if (!ni) {
	notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
    } else if (ni->status & NS_VERBOTEN) {
	notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, ni->nick);
    } else if (!(ngi = get_ngi(ni))) {
	notice_lang(s_NickServ, u, INTERNAL_ERROR);
    } else if (!is_servadmin && !user_identified(u)) {
	notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
    } else if (stricmp(cmd, "URL") == 0) {
	do_set_url(u, ngi, NULL);
    } else if (stricmp(cmd, "EMAIL") == 0) {
	if (NSRequireEmail) {
	    if (ni != u->ni)
		notice_lang(s_NickServ, u, NICK_UNSET_EMAIL_OTHER_BAD);
	    else
		notice_lang(s_NickServ, u, NICK_UNSET_EMAIL_BAD);
	} else {
	    do_set_email(u, ngi, NULL);
	}
    } else if (stricmp(cmd, "INFO") == 0) {
	do_set_info(u, ngi, NULL);
    } else {
	syntax_error(s_NickServ, u, "UNSET",
	    NSRequireEmail ? NICK_UNSET_SYNTAX_REQ_EMAIL : NICK_UNSET_SYNTAX);
    }
}

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

static void do_set_password(User *u, NickGroupInfo *ngi, NickInfo *ni,
			    char *param)
{
    int len = strlen(param), max;

    if (NSSecureAdmins && u->ni != ni && nick_is_services_admin(ni)
     && !is_services_root(u)
    ) {
	notice_lang(s_NickServ, u, PERMISSION_DENIED);
	return;
    } else if (stricmp(param, ni->nick) == 0 || (StrictPasswords && len < 5)) {
	notice_lang(s_NickServ, u, MORE_OBSCURE_PASSWORD);
	return;
    }

    /* Password length checks and truncation (see do_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_NickServ, u, PASSWORD_TRUNCATED, max);
    }
    if (encrypt(param, len, ngi->pass, PASSMAX) < 0) {
	memset(param, 0, len);
	module_log("Failed to encrypt password for %s (set)", ni->nick);
	notice_lang(s_NickServ, u, NICK_SET_PASSWORD_FAILED);
	return;
    }
    put_nickgroupinfo(ngi);
    if (NSShowPassword)
	notice_lang(s_NickServ, u, NICK_SET_PASSWORD_CHANGED_TO, param);
    else
	notice_lang(s_NickServ, u, NICK_SET_PASSWORD_CHANGED);
    memset(param, 0, strlen(param));
    if (u->ni != ni) {
	module_log("%s!%s@%s used SET PASSWORD as Services admin on %s",
		   u->nick, u->username, u->host, ni->nick);
	if (WallSetpass) {
	    wallops(s_NickServ, "\2%s\2 used SET PASSWORD as Services admin "
			"on \2%s\2", u->nick, ni->nick);
	}
    }
}

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

static void do_set_language(User *u, NickGroupInfo *ngi, char *param)
{
    int langnum;

    if (param[strspn(param, "0123456789")] != 0) {  /* i.e. not a number */
	syntax_error(s_NickServ, u, "SET LANGUAGE", NICK_SET_LANGUAGE_SYNTAX);
	return;
    }
    langnum = atoi(param)-1;
    if (langnum < 0 || langnum >= NUM_LANGS || langlist[langnum] < 0) {
	notice_lang(s_NickServ, u, NICK_SET_LANGUAGE_UNKNOWN,
		    langnum+1, s_NickServ);
	return;
    }
    ngi->language = langlist[langnum];
    put_nickgroupinfo(ngi);
    notice_lang(s_NickServ, u, NICK_SET_LANGUAGE_CHANGED,
		getstring(ngi,LANG_NAME));
}

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

static void do_set_url(User *u, NickGroupInfo *ngi, char *param)
{
    /* FIXME: is this the best way to get the nick? (see also do_set_email, do_set_info) */
    const char *nick = ngi->nicks[ngi->mainnick];

    if (param && !valid_url(param)) {
	notice_lang(s_NickServ, u, BAD_URL);
	return;
    }

    free(ngi->url);
    if (param) {
	ngi->url = sstrdup(param);
	notice_lang(s_NickServ, u, NICK_SET_URL_CHANGED, nick, param);
    } else {
	ngi->url = NULL;
	notice_lang(s_NickServ, u, NICK_UNSET_URL, nick);
    }
    put_nickgroupinfo(ngi);
}

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

static void do_set_email(User *u, NickGroupInfo *ngi, char *param)
{
    const char *nick = ngi->nicks[ngi->mainnick];

    if (param && !valid_email(param)) {
	notice_lang(s_NickServ, u, BAD_EMAIL);
	return;
    }

    if (param && !is_services_admin(u)) {
	int n = count_nicks_with_email(param);
	if (n < 0) {
	    notice_lang(s_NickServ, u, NICK_SET_EMAIL_UNAUTHED);
	    return;
	} else if (NSRegEmailMax && n >= NSRegEmailMax) {
	    notice_lang(s_NickServ, u, NICK_SET_EMAIL_TOO_MANY_NICKS,
			param, n, NSRegEmailMax);
	    return;
	}
    }

    free(ngi->email);
    if (param) {
	ngi->email = sstrdup(param);
	module_log("%s E-mail address changed to %s by %s!%s@%s",
		   nick, param, u->nick, u->username, u->host);
	notice_lang(s_NickServ, u, NICK_SET_EMAIL_CHANGED, nick, param);
	if (!is_services_admin(u)) {
	    /* FIXME (like mail-auth) this prolly wants a flag for self/other*/
	    if (user_ident_nomail(u)) {
		u->ni->authstat &= ~NA_IDENT_NOMAIL;
		u->ni->authstat |= NA_IDENTIFIED;
	    }
	}
    } else {
	ngi->email = NULL;
	module_log("%s E-mail address cleared by %s!%s@%s",
		   nick, u->nick, u->username, u->host);
	notice_lang(s_NickServ, u, NICK_UNSET_EMAIL, nick);
    }
    put_nickgroupinfo(ngi);
    call_callback_2(module, cb_set_email, u, ngi);
}

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

static void do_set_info(User *u, NickGroupInfo *ngi, char *param)
{
    const char *nick = ngi->nicks[ngi->mainnick];

    free(ngi->info);
    if (param) {
	ngi->info = sstrdup(param);
	notice_lang(s_NickServ, u, NICK_SET_INFO_CHANGED, nick, param);
    } else {
	ngi->info = NULL;
	notice_lang(s_NickServ, u, NICK_UNSET_INFO, nick);
    }
    put_nickgroupinfo(ngi);
}

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

static void do_set_kill(User *u, NickGroupInfo *ngi, char *param)
{
    if (stricmp(param, "ON") == 0) {
	ngi->flags |= NF_KILLPROTECT;
	ngi->flags &= ~(NF_KILL_QUICK | NF_KILL_IMMED);
	notice_lang(s_NickServ, u, NICK_SET_KILL_ON);
    } else if (stricmp(param, "QUICK") == 0) {
	ngi->flags |= NF_KILLPROTECT | NF_KILL_QUICK;
	ngi->flags &= ~NF_KILL_IMMED;
	notice_lang(s_NickServ, u, NICK_SET_KILL_QUICK);
    } else if (stricmp(param, "IMMED") == 0) {
	if (NSAllowKillImmed) {
	    ngi->flags |= NF_KILLPROTECT | NF_KILL_QUICK | NF_KILL_IMMED;
	    notice_lang(s_NickServ, u, NICK_SET_KILL_IMMED);
	} else {
	    notice_lang(s_NickServ, u, NICK_SET_KILL_IMMED_DISABLED);
	    return;
	}
    } else if (stricmp(param, "OFF") == 0) {
	ngi->flags &= ~(NF_KILLPROTECT | NF_KILL_QUICK | NF_KILL_IMMED);
	notice_lang(s_NickServ, u, NICK_SET_KILL_OFF);
    } else {
	syntax_error(s_NickServ, u, "SET KILL",
		     NSAllowKillImmed ? NICK_SET_KILL_IMMED_SYNTAX
				      : NICK_SET_KILL_SYNTAX);
	return;
    }
    put_nickgroupinfo(ngi);
}

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

static void do_set_secure(User *u, NickGroupInfo *ngi, char *param)
{
    if (stricmp(param, "ON") == 0) {
	ngi->flags |= NF_SECURE;
	notice_lang(s_NickServ, u, NICK_SET_SECURE_ON);
    } else if (stricmp(param, "OFF") == 0) {
	ngi->flags &= ~NF_SECURE;
	notice_lang(s_NickServ, u, NICK_SET_SECURE_OFF);
    } else {
	syntax_error(s_NickServ, u, "SET SECURE", NICK_SET_SECURE_SYNTAX);
	return;
    }
    put_nickgroupinfo(ngi);
}

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

static void do_set_private(User *u, NickGroupInfo *ngi, char *param)
{
    if (stricmp(param, "ON") == 0) {
	ngi->flags |= NF_PRIVATE;
	notice_lang(s_NickServ, u, NICK_SET_PRIVATE_ON);
    } else if (stricmp(param, "OFF") == 0) {
	ngi->flags &= ~NF_PRIVATE;
	notice_lang(s_NickServ, u, NICK_SET_PRIVATE_OFF);
    } else {
	syntax_error(s_NickServ, u, "SET PRIVATE", NICK_SET_PRIVATE_SYNTAX);
	return;
    }
    put_nickgroupinfo(ngi);
}

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

static void do_set_hide(User *u, NickGroupInfo *ngi, char *param,
			char *setting)
{
    int flag, onmsg, offmsg;

    if (stricmp(param, "EMAIL") == 0) {
	flag = NF_HIDE_EMAIL;
	onmsg = NICK_SET_HIDE_EMAIL_ON;
	offmsg = NICK_SET_HIDE_EMAIL_OFF;
    } else if (stricmp(param, "USERMASK") == 0) {
	flag = NF_HIDE_MASK;
	onmsg = NICK_SET_HIDE_MASK_ON;
	offmsg = NICK_SET_HIDE_MASK_OFF;
    } else if (stricmp(param, "QUIT") == 0) {
	flag = NF_HIDE_QUIT;
	onmsg = NICK_SET_HIDE_QUIT_ON;
	offmsg = NICK_SET_HIDE_QUIT_OFF;
    } else {
	syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
	return;
    }
    if (stricmp(setting, "ON") == 0) {
	ngi->flags |= flag;
	notice_lang(s_NickServ, u, onmsg, s_NickServ);
    } else if (stricmp(setting, "OFF") == 0) {
	ngi->flags &= ~flag;
	notice_lang(s_NickServ, u, offmsg, s_NickServ);
    } else {
	syntax_error(s_NickServ, u, "SET HIDE", NICK_SET_HIDE_SYNTAX);
	return;
    }
    put_nickgroupinfo(ngi);
}

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

/* Timezone definitions. */
static struct {
    const char *name;
    int16 offset;
} timezones[] = {
    {"GMT-12",-720},
    {"GMT-11",-660},
    {"GMT-10",-600}, {"HST",-600},
    {"GMT-9", -540},
    {"GMT-8", -480}, {"PST",-480},
    {"GMT-7", -420}, {"MST",-420}, {"PDT",-420},
    {"GMT-6", -360}, {"CST",-360}, {"MDT",-360},
    {"GMT-5", -300}, {"EST",-300}, {"CDT",-300},
    {"GMT-4", -240}, {"AST",-240}, {"EDT",-240},
    {"GMT-3", -180}, {"BRT",-180}, {"ADT",-180},
    {"GMT-2", -120}, {"BRST",-120},
    {"GMT-1",  -60},
    {"GMT+0",    0}, {"GMT",   0}, {"UTC",   0}, {"WET",   0},
    {"GMT+1",   60}, {"MET",  60}, {"BST",  60}, {"IST",  60},
    {"GMT+2",  120}, {"EET", 120},
    {"GMT+3",  180}, {"MSK", 180},
    {"GMT+4",  240}, {"MSD", 240},
    {"GMT+5",  300},
    {"GMT+6",  360},
    {"GMT+7",  420},
    {"GMT+8",  480},
    {"GMT+9",  540}, {"JST", 540}, {"KST", 540},
    {"GMT+10", 600},
    {"GMT+11", 660},
    {"GMT+12", 720}, {"NZST",720},
    {"GMT+13", 780}, {"NZDT",780},
    { NULL }
};

static void do_set_timezone(User *u, NickGroupInfo *ngi, char *param)
{
    char *s;
    int i, j;
    char timebuf[BUFSIZE];

    if (stricmp(param, "DEFAULT") == 0) {
	ngi->timezone = TIMEZONE_DEFAULT;
	notice_lang(s_NickServ, u, NICK_SET_TIMEZONE_DEFAULT);
	return;
    } else if (*param == '+' || *param == '-') {
	i = strtol(param+1, &s, 10);
	if (*s == ':') {
	    if (s[1]>='0' && s[1]<='5' && s[2]>='0' && s[2]<='9')
		j = strtol(s+1, &s, 10);
	    else
		j = -1;
	} else {
	    j = 0;
	}
	if (i < 0 || i > 23 || j < 0 || j > 59 || *s) {
	    syntax_error(s_NickServ, u, "SET TIMEZONE",
			 NICK_SET_TIMEZONE_SYNTAX);
	    return;
	}
	ngi->timezone = i*60 + j;
	if (*param == '-')
	    ngi->timezone = -ngi->timezone;
    } else {
	for (i = 0; timezones[i].name; i++) {
	    if (stricmp(param, timezones[i].name) == 0)
		break;
	}
	if (!timezones[i].name) {
	    syntax_error(s_NickServ, u, "SET TIMEZONE",
			 NICK_SET_TIMEZONE_SYNTAX);
	    return;
	}
	ngi->timezone = timezones[i].offset;
    }
    /* This is tricky, because we want the calling user's language but the
     * target user's timezone. */
    if (valid_ngi(u)) {
	j = (ngi->timezone - u->ngi->timezone) * 60;
    } else {  /* just in case... */
	time_t tmp = 0;
	struct tm *tm = localtime(&tmp);
	tmp = tm->tm_hour*3600 + tm->tm_min*60 + tm->tm_sec;
	if (tm->tm_year < 70)
	    tmp -= 86400;
	j = ngi->timezone*60 - tmp;
    }
    strftime_lang(timebuf, sizeof(timebuf), u->ngi,
		  STRFTIME_DATE_TIME_FORMAT, time(NULL) + j);
    if (ngi->timezone < 0)
	i = -ngi->timezone;
    else
	i = ngi->timezone;
    notice_lang(s_NickServ, u, NICK_SET_TIMEZONE_TO,
		ngi->timezone<0 ? '-' : '+', i/60, i%60, timebuf);
    put_nickgroupinfo(ngi);
}

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

static void do_set_noexpire(User *u, NickInfo *ni, char *param)
{
    if (!is_services_admin(u)) {
	notice_lang(s_NickServ, u, PERMISSION_DENIED);
	return;
    }
    if (!param) {
	syntax_error(s_NickServ, u, "SET NOEXPIRE", NICK_SET_NOEXPIRE_SYNTAX);
	return;
    }
    if (stricmp(param, "ON") == 0) {
	ni->status |= NS_NOEXPIRE;
	notice_lang(s_NickServ, u, NICK_SET_NOEXPIRE_ON, ni->nick);
    } else if (stricmp(param, "OFF") == 0) {
	ni->status &= ~NS_NOEXPIRE;
	notice_lang(s_NickServ, u, NICK_SET_NOEXPIRE_OFF, ni->nick);
    } else {
	syntax_error(s_NickServ, u, "SET NOEXPIRE", NICK_SET_NOEXPIRE_SYNTAX);
	return;
    }
    put_nickinfo(ni);
}

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

int init_set(Module *my_module)
{
    module = my_module;
    cb_set = register_callback(module, "SET");
    cb_set_email = register_callback(module, "SET EMAIL");
    if (cb_set < 0 || cb_set_email < 0) {
	module_log("set: Unable to register callbacks");
	exit_set();
	return 0;
    }
    return 1;
}

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

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

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