/* Routines to handle the NickServ SET command.
*
* IRC Services is copyright (c) 1996-2007 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 int cb_unset = -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, "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 (call_callback_4(module, cb_unset, u, ni, ngi, cmd) > 0) {
return;
} 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)
{
const char *nick = ngi_mainnick(ngi);
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_mainnick(ngi);
char oldemail[BUFSIZE];
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;
}
}
if (ngi->email) {
strscpy(oldemail, ngi->email, sizeof(oldemail));
free(ngi->email);
} else {
*oldemail = 0;
}
if (param) {
ngi->email = sstrdup(param);
if (*oldemail) {
module_log("%s E-mail address changed from %s to %s by %s!%s@%s",
nick, oldemail, param, u->nick, u->username, u->host);
} else {
module_log("%s E-mail address set 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)) {
/* Note: this (like the SET EMAIL callback in mail-auth)
* assumes that only the user or a servadmin can change the
* E-mail address; if the user isn't a servadmin, therefore,
* they must be doing it to their own nick, and we use/modify
* the User structure. */
if (user_ident_nomail(u)) {
u->ni->authstat &= ~NA_IDENT_NOMAIL;
u->ni->authstat |= NA_IDENTIFIED;
}
}
} else {
ngi->email = NULL;
if (*oldemail) {
module_log("%s E-mail address cleared by %s!%s@%s (was %s)",
nick, u->nick, u->username, u->host, oldemail);
}
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_mainnick(ngi);
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");
cb_unset = register_callback(module, "UNSET");
if (cb_set < 0 || cb_set_email < 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_email);
unregister_callback(module, cb_set);
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1