/* Nickname linking module (4.x compatibility version).
*
* 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 "conffile.h"
#include "language.h"
#include "commands.h"
#include "modules/operserv/operserv.h"
#include "modules/chanserv/chanserv.h"
#include "nickserv.h"
#include "ns-local.h"
/*************************************************************************/
static Module *module;
static Module *module_nickserv;
static Module *module_chanserv;
/*************************************************************************/
static void do_link(User *u);
static void do_unlink(User *u);
static void do_listlinks(User *u);
static Command cmds[] = {
{ "LINK", do_link, NULL, NICK_HELP_OLD_LINK, -1,-1 },
{ "UNLINK", do_unlink, NULL, -1,
NICK_HELP_OLD_UNLINK, NICK_OPER_HELP_OLD_UNLINK, },
{ "LISTLINKS",do_listlinks,is_services_admin, -1,
-1, NICK_OPER_HELP_OLD_LISTLINKS },
{ NULL }
};
/*************************************************************************/
/******************************** Imports ********************************/
/*************************************************************************/
static int (*p_check_channel_limit)(NickGroupInfo *ngi, int *max_ret);
static int my_check_channel_limit(NickGroupInfo *ngi, int *max_ret)
{
if (p_check_channel_limit)
return p_check_channel_limit(ngi, max_ret);
else
return -1;
}
#define check_channel_limit my_check_channel_limit
/*************************************************************************/
/*************************** Command functions ***************************/
/*************************************************************************/
static void do_link(User *u)
{
char *nick = strtok(NULL, " ");
char *pass = strtok_remaining();
NickInfo *ni = u->ni, *target;
NickGroupInfo *ngi = u->ngi;
if (readonly && !is_services_admin(u)) {
notice_lang(s_NickServ, u, NICK_LINK_DISABLED);
return;
}
if (!pass) {
syntax_error(s_NickServ, u, "LINK", NICK_OLD_LINK_SYNTAX);
} else if (!ni || !ngi || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (!user_identified(u)) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (!(target = get_nickinfo(nick))) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
} else if (target == ni) {
notice_lang(s_NickServ, u, NICK_OLD_LINK_SAME, nick);
} else if (target->status & NS_VERBOTEN) {
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, nick);
} else if (!nick_check_password(u, target, pass, "LINK",
NICK_LINK_FAILED)) {
return;
} else {
NickGroupInfo *target_ngi = get_ngi(target);
ChannelInfo *ci;
User *u2;
int max, i, n, res;
/* Make sure the target nickgroup actually exists. */
if (!target_ngi || target_ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, INTERNAL_ERROR);
return;
}
/* Make sure the target isn't suspended. */
if (target_ngi->suspendinfo) {
notice_lang(s_NickServ, u, NICK_X_SUSPENDED, nick);
return;
}
/* Check for exceeding the per-email nick registration limit. */
if (NSRegEmailMax && target_ngi->email && !is_services_admin(u)
&& abs(n=count_nicks_with_email(target_ngi->email)) >= NSRegEmailMax
) {
notice_lang(s_NickServ, u, NICK_LINK_TOO_MANY_NICKS, n,
NSRegEmailMax);
return;
}
/* Check for exceeding the channel registration limit. */
target_ngi->channels_count += ngi->channels_count;
res = check_channel_limit(target_ngi, &max);
target_ngi->channels_count -= ngi->channels_count;
if (res >= 0) {
notice_lang(s_NickServ, u, NICK_OLD_LINK_TOO_MANY_CHANNELS,
nick, max);
return;
}
/* Put each of the old group's nicks in the new group */
ARRAY_FOREACH (i, ngi->nicks) {
NickInfo *ni2 = get_nickinfo_noexpire(ngi->nicks[i]);
ARRAY_EXTEND(target_ngi->nicks);
strscpy(target_ngi->nicks[target_ngi->nicks_count-1],
ngi->nicks[i], NICKMAX);
ni2->nickgroup = target_ngi->id;
put_nickinfo(ni2);
}
/* Append old group's list of owned channels to new group */
ARRAY_FOREACH (i, ngi->channels) {
ARRAY_EXTEND(target_ngi->channels);
strscpy(target_ngi->channels[target_ngi->channels_count-1],
ngi->channels[i], CHANMAX);
}
/* Merge memos */
if (ngi->memos.memos_count) {
int i, num;
Memo *memo;
if (target_ngi->memos.memos_count) {
num = 0;
ARRAY_FOREACH (i, target_ngi->memos.memos) {
if (target_ngi->memos.memos[i].number > num)
num = target_ngi->memos.memos[i].number;
}
num++;
target_ngi->memos.memos =
srealloc(target_ngi->memos.memos,
sizeof(Memo) * (ngi->memos.memos_count +
target_ngi->memos.memos_count));
} else {
num = 1;
target_ngi->memos.memos =
smalloc(sizeof(Memo) * ngi->memos.memos_count);
target_ngi->memos.memos_count = 0;
}
memo = target_ngi->memos.memos + target_ngi->memos.memos_count;
ARRAY_FOREACH (i, ngi->memos.memos) {
*memo = ngi->memos.memos[i];
memo->number = num++;
memo++;
}
target_ngi->memos.memos_count += ngi->memos.memos_count;
ngi->memos.memos_count = 0;
free(ngi->memos.memos);
ngi->memos.memos = NULL;
}
/* Fix up channel access lists -- this takes a long time */
for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup == ngi->id)
ci->access[i].nickgroup = target_ngi->id;
}
}
/* Fix up users' NickGroupInfo pointers */
for (u2 = first_user(); u2; u2 = next_user()) {
if (u2->ngi == ngi)
u2->ngi = target_ngi;
}
/* Finally, delete the old nickgroup and store the new one */
del_nickgroupinfo(ngi);
free_nickgroupinfo(ngi);
put_nickgroupinfo(target_ngi);
/* Tell everybody about it */
module_log("%s!%s@%s linked nick %s to %s",
u->nick, u->username, u->host, u->nick, nick);
notice_lang(s_NickServ, u, NICK_OLD_LINKED, nick);
if (readonly)
notice_lang(s_NickServ, u, READ_ONLY_MODE);
}
} /* do_link() */
/*************************************************************************/
static void do_unlink(User *u)
{
NickInfo *ni;
NickGroupInfo *ngi = NULL, *new_ngi;
char *nick = strtok(NULL, " ");
char *pass = strtok_remaining();
int msg = -1; /* -1 = flag meaning "error, abort" */
char *msgparam[2] = {NULL, NULL};
int i;
if (readonly && !is_services_admin(u)) {
notice_lang(s_NickServ, u, NICK_LINK_DISABLED);
return;
}
if (nick) {
int is_servadmin = is_services_admin(u);
ni = get_nickinfo(nick);
if (!ni) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
} 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 (ngi->nicks_count <= 1) {
notice_lang(s_NickServ, u, NICK_UNLINK_NOT_LINKED, nick);
} else if (!is_servadmin && !pass) {
syntax_error(s_NickServ, u, "UNLINK", NICK_OLD_UNLINK_SYNTAX);
} else if (!is_servadmin &&
!nick_check_password(u, ni, pass, "UNLINK",
NICK_UNLINK_FAILED)) {
return;
} else {
msg = NICK_X_UNLINKED;
msgparam[0] = ni->nick;
}
} else {
ni = u->ni;
ngi = u->ngi;
if (!ni || !ngi || ngi == NICKGROUPINFO_INVALID) {
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED);
} else if (!user_identified(u)) {
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
} else if (ngi->nicks_count <= 1) {
notice_lang(s_NickServ, u, NICK_OLD_UNLINK_NOT_LINKED);
} else {
msg = NICK_OLD_UNLINKED;
msgparam[0] = NULL;
}
}
if (msg >= 0) {
if (msgparam[0])
msgparam[1] = ngi_mainnick(ngi);
else
msgparam[0] = ngi_mainnick(ngi);
/* Set up new nick group */
new_ngi = new_nickgroupinfo(ni->nick);
ARRAY_EXTEND(new_ngi->nicks);
strscpy(new_ngi->nicks[0], ni->nick, NICKMAX);
strscpy(new_ngi->pass, ngi->pass, PASSMAX);
if (ngi->url)
new_ngi->url = sstrdup(ngi->url);
if (ngi->email)
new_ngi->email = sstrdup(ngi->email);
if (ngi->info)
new_ngi->info = sstrdup(ngi->info);
new_ngi->authcode = ngi->authcode;
new_ngi->authset = ngi->authset;
new_ngi->flags = ngi->flags;
new_ngi->os_priv = ngi->os_priv;
new_ngi->channelmax = ngi->channelmax;
new_ngi->memos.memomax = ngi->memos.memomax;
new_ngi->language = ngi->language;
if (ngi->access_count) {
new_ngi->access =
smalloc(sizeof(*new_ngi->access) * ngi->access_count);
ARRAY_FOREACH (i, ngi->access) {
new_ngi->access[i] = sstrdup(ngi->access[i]);
}
}
u->ngi = new_ngi;
add_nickgroupinfo(new_ngi);
/* Update NickInfo with new group ID */
ni->nickgroup = new_ngi->id;
put_nickinfo(ni);
/* Remove nick from old nick group */
ARRAY_SEARCH_PLAIN(ngi->nicks, ni->nick, irc_stricmp, i);
if (i < ngi->nicks_count) {
ARRAY_REMOVE(ngi->nicks, i);
} else {
module_log("UNLINK %s by %s: nick not found in old nickgroup %u!",
ni->nick, u->nick, ngi->id);
}
/* Tell people about it */
notice_lang(s_NickServ, u, msg, msgparam[0], msgparam[1]);
module_log("%s!%s@%s unlinked nick %s from %s", u->nick,
u->username, u->host, u->nick, ngi_mainnick(ngi));
if (readonly)
notice_lang(s_NickServ, u, READ_ONLY_MODE);
}
}
/*************************************************************************/
static void do_listlinks(User *u)
{
char *nick = strtok(NULL, " ");
char *param = strtok(NULL, " ");
NickInfo *ni;
NickGroupInfo *ngi;
int i;
if (!nick || param) {
syntax_error(s_NickServ, u, "LISTLINKS", NICK_OLD_LISTLINKS_SYNTAX);
} else if (!(ni = get_nickinfo(nick))) {
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, nick);
} 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 {
notice_lang(s_NickServ, u, NICK_LISTLINKS_HEADER, ni->nick);
ARRAY_FOREACH (i, ngi->nicks) {
if (irc_stricmp(ngi->nicks[i], ni->nick) != 0)
notice(s_NickServ, u->nick, " %s", ngi->nicks[i]);
}
notice_lang(s_NickServ, u, NICK_LISTLINKS_FOOTER, ngi->nicks_count-1);
}
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
const int32 module_version = MODULE_VERSION_CODE;
ConfigDirective module_config[] = {
{ NULL }
};
static int old_NICK_DROPPED = -1;
static int old_NICK_X_DROPPED = -1;
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "chanserv/main") == 0) {
module_chanserv = mod;
p_check_channel_limit = get_module_symbol(mod, "check_channel_limit");
if (!p_check_channel_limit) {
module_log("Unable to resolve symbol `check_channel_limit' in"
" module `chanserv/main'");
}
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_chanserv) {
p_check_channel_limit = NULL;
module_chanserv = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(Module *module_)
{
module = module_;
if (find_module("nickserv/link")) {
module_log("link and oldlink modules cannot be loaded at the same"
" time");
return 0;
}
module_nickserv = find_module("nickserv/main");
if (!module_nickserv) {
module_log("Main NickServ module not loaded");
return 0;
}
use_module(module_nickserv);
if (!register_commands(module_nickserv, cmds)) {
module_log("Unable to register commands");
exit_module(0);
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
) {
exit_module(0);
return 0;
}
module_ = find_module("chanserv/main");
if (module_)
do_load_module(module_, "chanserv/main");
old_NICK_DROPPED = setstring(NICK_DROPPED, NICK_DROPPED_LINKS);
old_NICK_X_DROPPED = setstring(NICK_X_DROPPED, NICK_X_DROPPED_LINKS);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown_unused)
{
#ifdef CLEAN_COMPILE
shutdown_unused = shutdown_unused;
#endif
if (old_NICK_DROPPED >= 0) {
setstring(NICK_DROPPED, old_NICK_DROPPED);
old_NICK_DROPPED = -1;
}
if (old_NICK_X_DROPPED >= 0) {
setstring(NICK_X_DROPPED, old_NICK_X_DROPPED);
old_NICK_X_DROPPED = -1;
}
if (module_chanserv)
do_unload_module(module_chanserv);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
if (module_nickserv) {
unregister_commands(module_nickserv, cmds);
unuse_module(module_nickserv);
module_nickserv = NULL;
}
return 1;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1