/* Nickname linking module (4.x compatibility version). * * 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 "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; } /*************************************************************************/