/* NickServ utility routines. * * 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. */ /* If STANDALONE_NICKSERV is defined when compiling this file, the * following four routines will be defined as `static' for use in other * modules/programs: * new_nickinfo() * free_nickinfo() * new_nickgroupinfo() * free_nickgroupinfo() * This file should be #include'd in the file making use of the routines. * Note that new_nickgroupinfo() will not check for the presence of any * nickgroup ID it assigns, since get_nickgroupinfo() is not assumed to be * available. */ #ifdef STANDALONE_NICKSERV # define STANDALONE_STATIC static # undef EXPORT_FUNC # define EXPORT_FUNC(x) /*nothing*/ #else # define STANDALONE_STATIC /*nothing*/ # include "services.h" # include "modules.h" # include "language.h" # include "encrypt.h" # include "nickserv.h" # include "ns-local.h" #endif /*************************************************************************/ #ifndef STANDALONE_NICKSERV static Module *module; static int cb_cancel_user = -1; static int cb_check_expire = -1; static int cb_check_recognized = -1; static int cb_delete = -1; static int cb_groupdelete = -1; #endif /*************************************************************************/ /************************ Global utility routines ************************/ /*************************************************************************/ /* Allocate and initialize a new NickInfo structure. */ EXPORT_FUNC(new_nickinfo) STANDALONE_STATIC NickInfo *new_nickinfo(void) { NickInfo *ni = scalloc(sizeof(*ni), 1); return ni; } /*************************************************************************/ /* Free a NickInfo structure and all associated data. */ EXPORT_FUNC(free_nickinfo) STANDALONE_STATIC void free_nickinfo(NickInfo *ni) { if (ni) { free(ni->last_usermask); free(ni->last_realmask); free(ni->last_realname); free(ni->last_quit); free(ni); } } /*************************************************************************/ /* Allocate and initialize a new NickGroupInfo structure. If `seed' is * non-NULL, the group ID will be set to a value unique from any other * currently stored in the database (in this case `seed' is used in the * initial attempt to allocate the ID). If `seed' is NULL, the ID value * is left at zero. */ EXPORT_FUNC(new_nickgroupinfo) STANDALONE_STATIC NickGroupInfo *new_nickgroupinfo(const char *seed) { NickGroupInfo *ngi = scalloc(sizeof(*ngi), 1); if (seed) { uint32 id; int count; id = 0; for (count = 0; seed[count] != 0; count++) id ^= seed[count] << ((count % 6) * 5); if (id == 0) id = 1; #ifndef STANDALONE_NICKSERV count = 0; while (get_nickgroupinfo(id) != NULL && ++count < NEWNICKGROUP_TRIES) { id = rand() + rand(); /* 32 bits of randomness, hopefully */ if (id == 0) id = 1; } if (count >= NEWNICKGROUP_TRIES) { module_log("new_nickgroupinfo() unable to allocate unused ID" " after %d tries! Giving up.", NEWNICKGROUP_TRIES); free(ngi); return NULL; } #endif ngi->id = id; } ngi->memos.memomax = MEMOMAX_DEFAULT; ngi->channelmax = CHANMAX_DEFAULT; ngi->language = LANG_DEFAULT; ngi->timezone = TIMEZONE_DEFAULT; return ngi; } /*************************************************************************/ /* Free a NickGroupInfo structure and all associated data. */ EXPORT_FUNC(free_nickgroupinfo) STANDALONE_STATIC void free_nickgroupinfo(NickGroupInfo *ngi) { int i; if (!ngi) return; free(ngi->url); free(ngi->email); free(ngi->info); if (ngi->suspendinfo) free_suspendinfo(ngi->suspendinfo); free(ngi->nicks); ARRAY_FOREACH (i, ngi->access) free(ngi->access[i]); free(ngi->access); ARRAY_FOREACH (i, ngi->ajoin) free(ngi->ajoin[i]); free(ngi->ajoin); free(ngi->channels); ARRAY_FOREACH (i, ngi->memos.memos) free(ngi->memos.memos[i].text); free(ngi->memos.memos); ARRAY_FOREACH (i, ngi->ignore) free(ngi->ignore[i]); free(ngi->ignore); ARRAY_FOREACH (i, ngi->id_users) { User *u = ngi->id_users[i]; int j; ARRAY_SEARCH_PLAIN_SCALAR(u->id_nicks, ngi->id, j); if (j < u->id_nicks_count) { ARRAY_REMOVE(u->id_nicks, j); #ifndef STANDALONE_NICKSERV } else { module_log("BUG: free_nickgroupinfo(): user %p (%s) listed in" " id_users for nickgroup %u, but nickgroup not in" " id_nicks!", u, u->nick, ngi->id); #endif } } free(ngi->id_users); free(ngi); } /*************************************************************************/ #ifndef STANDALONE_NICKSERV /* to the end of the file */ /*************************************************************************/ /* Check whether the given nickname (or its suspension) should be expired, * and do the expiration if so. Return 1 if the nickname was deleted, else * 0. Note that we do last-seen-time updates here as well. */ EXPORT_FUNC(check_expire_nick) int check_expire_nick(NickInfo *ni) { User *u = ni->user; NickGroupInfo *ngi; time_t now = time(NULL); if (u && user_id_or_rec(u)) { if (debug >= 2) module_log("debug: updating last seen time for %s", u->nick); ni->last_seen = time(NULL); put_nickinfo(ni); } ngi = ni->nickgroup ? get_ngi_id(ni->nickgroup) : NULL; if (call_callback_2(module, cb_check_expire, ni, ngi) > 0) { if (u) notice_lang(s_NickServ, u, NICK_EXPIRED); delnick(ni); return 1; } if (NSExpire && now >= ni->last_seen + NSExpire && !(ni->status & (NS_VERBOTEN | NS_NOEXPIRE)) && (!ngi || !ngi->suspendinfo) ) { module_log("Expiring nickname %s", ni->nick); if (u) notice_lang(s_NickServ, u, NICK_EXPIRED); delnick(ni); return 1; } if (ngi && ngi->suspendinfo && ngi->suspendinfo->expires && now >= ngi->suspendinfo->expires ) { module_log("Expiring suspension for %s (nick group %u)", ni->nick, ngi->id); unsuspend_nick(ngi, 1); } return 0; } /*************************************************************************/ /* Retrieve a NickInfo structure without expiring it. Routines which hold * onto a NickGroupInfo pointer while calling get_nickinfo() need to use * this routine instead to avoid the NickGroupInfo being deallocated if the * nick being searched for is found to have expired. */ NickInfo *get_nickinfo_noexpire(const char *nick) { NickInfo *ni; int old_noexpire = noexpire; noexpire = 1; ni = get_nickinfo(nick); noexpire = old_noexpire; return ni; } /*************************************************************************/ /* Retrieve the NickGroupInfo structure associated with the given NickInfo. * If it cannot be retrieved, log an error message and return NULL. * Note: Callers should use get_ngi[_id]() or check_ngi[_id](), which embed * the current source file and line number in the log message. */ EXPORT_FUNC(_get_ngi) NickGroupInfo *_get_ngi(NickInfo *ni, const char *file, int line) { NickGroupInfo *ngi; if (!ni) { module_log("BUG: ni==NULL in get_ngi() (called from %s:%d)", file, line); return NULL; } ngi = get_nickgroupinfo(ni->nickgroup); if (!ngi) module_log("Unable to get NickGroupInfo (id %u) for %s at %s:%d", ni->nickgroup, ni->nick, file, line); return ngi; } EXPORT_FUNC(_get_ngi_id) NickGroupInfo *_get_ngi_id(uint32 id, const char *file, int line) { NickGroupInfo *ngi = get_nickgroupinfo(id); if (!ngi) module_log("Unable to get NickGroupInfo (id %u) at %s:%d", id, file, line); return ngi; } /*************************************************************************/ /* Return whether the user has previously identified for the given nick * group. (1=yes, 0=no) If the nick group does not exist or is in a * not-authenticated state (i.e. authcode != 0), this function will always * return 0. */ EXPORT_FUNC(has_identified_nick) int has_identified_nick(const User *u, uint32 group) { int i; NickGroupInfo *ngi; if (!(ngi = get_ngi_id(group)) || ngi->authcode) return 0; ARRAY_SEARCH_PLAIN_SCALAR(u->id_nicks, group, i); return i < u->id_nicks_count; } /*************************************************************************/ /*********************** Internal utility routines ***********************/ /*************************************************************************/ /* Check whether the given nickname is allowed to be registered (with the * given password and E-mail address) or linked (password/email NULL). * Returns nonzero if allowed, 0 if not. A wrapper for the "REGISTER/LINK * check" callback. */ int reglink_check(User *u, const char *nick, char *password, char *email) { int res = call_callback_4(module, cb_reglink_check, u,nick,password,email); if (res < 0) module_log("REGISTER/LINK callback returned an error"); return res == 0; } /*************************************************************************/ /* Update the last_usermask, last_realmask, and last_realname nick fields * from the given user's data, and sets last_seen to the current time. * Assumes u->ni is non-NULL. */ void update_userinfo(User *u) { const char *host; NickInfo *ni = u->ni; ni->last_seen = time(NULL); free(ni->last_usermask); if (u->fakehost) host = u->fakehost; else host = u->host; ni->last_usermask = smalloc(strlen(u->username)+strlen(host)+2); sprintf(ni->last_usermask, "%s@%s", u->username, host); free(ni->last_realmask); ni->last_realmask = smalloc(strlen(u->username)+strlen(u->host)+2); sprintf(ni->last_realmask, "%s@%s", u->username, u->host); free(ni->last_realname); ni->last_realname = sstrdup(u->realname); } /*************************************************************************/ /* Check whether a user is recognized for the nick they're using, or if * they're the same user who last identified for the nick. If not, send * warnings as appropriate. If so (and not NI_SECURE), update last seen * info. Return 1 if the user is valid and recognized, 0 otherwise (note * that this means an NI_SECURE nick will return 0 from here unless the * user has identified for the nick before). If the user's nick is not * registered, 0 is returned. * * The `nick' parameter is the nick the user is using. It is passed * separately to handle cases where the User structure has a different * nick (e.g. when changing nicks). */ int validate_user(User *u) { NickInfo *ni; NickGroupInfo *ngi; int is_recognized; ni = get_nickinfo(u->nick); if (ni && ni->nickgroup) { ngi = get_ngi(ni); if (ngi) { ni->locked++; ngi->locked++; } else { ni = NULL; ngi = NICKGROUPINFO_INVALID; } } else { if (ni) ni->locked++; ngi = NULL; } if (u->ni) u->ni->locked--; if (u->ngi && u->ngi != NICKGROUPINFO_INVALID) u->ngi->locked--; u->ni = ni; u->ngi = ngi; if (!ni) return 0; ni->authstat = 0; ni->user = u; if ((ni->status & NS_VERBOTEN) || ngi->suspendinfo) { if (usermode_reg) { send_cmd(s_NickServ, "SVSMODE %s :-%s", u->nick, mode_flags_to_string(usermode_reg, MODE_USER)); } notice_lang(s_NickServ, u, NICK_MAY_NOT_BE_USED); notice_lang(s_NickServ, u, DISCONNECT_IN_1_MINUTE); add_ns_timeout(ni, TO_SEND_433, 40); add_ns_timeout(ni, TO_COLLIDE, 60); return 0; } if (!ngi->authcode) { /* Neither of these checks should be done if the nick is awaiting * authentication */ if (has_identified_nick(u, ngi->id)) { set_identified(u, ni, ngi); return 1; } if (!NoSplitRecovery) { /* * This can be exploited to gain improper privilege if an attacker * has the same Services stamp, username and hostname as the * victim. * * Under ircd.dal 4.4.15+ (Dreamforge) and other servers supporting * a Services stamp, Services guarantees that the first condition * cannot occur unless the stamp counter rolls over (2^31-1 client * connections). This is practically infeasible given present * technology. As an example, on a network of 30 servers, an * attack introducing 50 new clients every second on every server, * requiring at least 10-15 megabits of bandwidth, would need to be * sustained for over 16 days to cause the stamp to roll over. * * Under other servers, an attack is theoretically possible, but * would require access to either the computer the victim is using * for IRC or the DNS servers for the victim's domain and IP * address range in order to have the same hostname, and would * require that the attacker connect so that he has the same server * timestamp as the victim. Practically, the former can be * accomplished either by finding a victim who uses a shell account * on a multiuser system and obtaining an account on the same * system, or through the scripting capabilities of many IRC * clients combined with social engineering; the latter could be * accomplished by finding a server with a clock slower than that * of the victim's server and timing the connection attempt * properly. * * If someone gets a hacked server into your network, all bets are * off. */ if (ni->id_stamp != 0 && u->servicestamp == ni->id_stamp) { char buf[BUFSIZE]; snprintf(buf, sizeof(buf), "%s@%s", u->username, u->host); if (ni->last_realmask && strcmp(buf, ni->last_realmask) == 0) { set_identified(u, ni, ngi); return 1; } } } } /* if (!ngi->authcode) */ /* From here on down, the user is known to be not identified. Clear * any "registered nick" mode from them. */ if (usermode_reg) { send_cmd(s_NickServ, "SVSMODE %s :-%s", u->nick, mode_flags_to_string(usermode_reg, MODE_USER)); } is_recognized = (call_callback_1(module, cb_check_recognized, u) == 1); if (!(ngi->flags & NF_SECURE) && !ngi->authcode && is_recognized) { ni->authstat |= NA_RECOGNIZED; update_userinfo(u); put_nickinfo(u->ni); return 1; } if (is_recognized || !NSAllowKillImmed || !(ngi->flags & NF_KILL_IMMED)) { if (ngi->flags & NF_SECURE) notice_lang(s_NickServ, u, NICK_IS_SECURE, s_NickServ); else notice_lang(s_NickServ, u, NICK_IS_REGISTERED, s_NickServ); } if ((ngi->flags & NF_KILLPROTECT) && !is_recognized) { if (!ngi->authcode && NSAllowKillImmed && (ngi->flags&NF_KILL_IMMED)) { collide(ni, 0); } else if (!ngi->authcode && (ngi->flags & NF_KILL_QUICK)) { notice_lang(s_NickServ, u, DISCONNECT_IN_20_SECONDS); add_ns_timeout(ni, TO_COLLIDE, 20); add_ns_timeout(ni, TO_SEND_433, 10); } else { notice_lang(s_NickServ, u, DISCONNECT_IN_1_MINUTE); add_ns_timeout(ni, TO_COLLIDE, 60); add_ns_timeout(ni, TO_SEND_433, 40); } } if (!noexpire && NSExpire && NSExpireWarning && !(ni->status & NS_NOEXPIRE) ) { int time_left = NSExpire - (time(NULL) - ni->last_seen); if (time_left <= NSExpireWarning) { notice_lang(s_NickServ, u, NICK_EXPIRES_SOON, maketime(get_ngi(ni),time_left,0), s_NickServ, s_NickServ); } } return 0; } /*************************************************************************/ /* Cancel validation flags for a nick (i.e. when the user with that nick * signs off or changes nicks). Also cancels any impending collide, sets * the nick's last seen time if the user is recognized for the nick and * unlocks the nick's info records. */ void cancel_user(User *u) { NickInfo *ni = u->ni; NickGroupInfo *ngi = (u->ngi==NICKGROUPINFO_INVALID ? NULL : u->ngi); if (ni) { int old_status = ni->status; int old_authstat = ni->authstat; if (nick_id_or_rec(ni)) { ni->last_seen = time(NULL); put_nickinfo(ni); } ni->user = NULL; ni->status &= ~NS_TEMPORARY; ni->authstat = 0; if (old_status & NS_GUESTED) introduce_enforcer(ni); call_callback_3(module, cb_cancel_user, u, old_status, old_authstat); rem_ns_timeout(ni, TO_COLLIDE, 1); ni->locked--; if (ngi) ngi->locked--; } } /*************************************************************************/ /* Marks a user as having identified for the given nick. */ void set_identified(User *u, NickInfo *ni, NickGroupInfo *ngi) { ni->authstat &= ~NA_IDENT_NOMAIL; ni->authstat |= NA_IDENTIFIED; ni->id_stamp = u->servicestamp; if (!nick_recognized(ni)) { update_userinfo(u); ni->authstat |= NA_RECOGNIZED; } put_nickinfo(ni); if (!has_identified_nick(u, ngi->id)) { ARRAY_EXTEND(u->id_nicks); u->id_nicks[u->id_nicks_count-1] = ngi->id; ARRAY_EXTEND(ngi->id_users); ngi->id_users[ngi->id_users_count-1] = u; } if (usermode_reg) { send_cmd(s_NickServ, "SVSMODE %s :+%s", u->nick, mode_flags_to_string(usermode_reg, MODE_USER)); } } /*************************************************************************/ /*************************************************************************/ /* Add a nick to the database. Returns a pointer to the new NickInfo * structure if the nick was successfully registered, NULL otherwise. * Assumes nick does not already exist. If `nickgroup_ret' is non-NULL, * a new NickGroupInfo structure is created, ni->nickgroup/ngi->*nick* * are set appropriately, and a pointer to the NickGroupInfo is returned * in that variable. Note that makenick() may return NULL if a nick group * is requested and new_nickgroupinfo() returns NULL. * * If `nick' is longer than NICKMAX-1 characters, it is silently truncated * in the new NickInfo and NickGroupInfo. */ NickInfo *makenick(const char *nick, NickGroupInfo **nickgroup_ret) { NickInfo *ni; NickGroupInfo *ngi; #ifdef CLEAN_COMPILE ngi = NULL; #endif if (nickgroup_ret) { ngi = new_nickgroupinfo(nick); if (!ngi) return NULL; } ni = new_nickinfo(); strscpy(ni->nick, nick, NICKMAX); if (nickgroup_ret) { ni->nickgroup = ngi->id; ARRAY_EXTEND(ngi->nicks); strscpy(ngi->nicks[0], nick, NICKMAX); *nickgroup_ret = add_nickgroupinfo(ngi); } return add_nickinfo(ni); } /*************************************************************************/ /* Remove a nick from the NickServ database. Return 1 on success, 0 * otherwise. */ int delnick(NickInfo *ni) { int i; NickGroupInfo *ngi; rem_ns_timeout(ni, -1, 1); if (ni->status & NS_KILL_HELD) release(ni, 0); if (ni->user) { if (usermode_reg) send_cmd(s_NickServ, "SVSMODE %s :-%s", ni->nick, mode_flags_to_string(usermode_reg, MODE_USER)); ni->user->ni = NULL; ni->user->ngi = NULL; } ngi = ni->nickgroup ? get_nickgroupinfo(ni->nickgroup) : NULL; if (ngi) { ARRAY_SEARCH_PLAIN(ngi->nicks, ni->nick, irc_stricmp, i); if (i >= ngi->nicks_count) { module_log("BUG: delete nick: no entry in ngi->nicks[] for" " nick %s", ni->nick); } else { ARRAY_REMOVE(ngi->nicks, i); /* Adjust ngi->mainnick as needed; if the main nick is being * deleted, switch it to the next one in the array, or the * previous if it's currently the last one. */ if (ngi->mainnick > i || ngi->mainnick >= ngi->nicks_count) ngi->mainnick--; } } call_callback_1(module, cb_delete, ni); if (ngi) { if (ngi->nicks_count == 0) { call_callback_2(module, cb_groupdelete, ngi, ni->nick); del_nickgroupinfo(ngi); } else { put_nickgroupinfo(ngi); } } del_nickinfo(ni); return 1; } /*************************************************************************/ /* Remove a nick group and all associated nicks from the NickServ database. * Return 1 on success, 0 otherwise. */ int delgroup(NickGroupInfo *ngi) { int i; ARRAY_FOREACH (i, ngi->nicks) { NickInfo *ni = get_nickinfo_noexpire(ngi->nicks[i]); if (!ni) { module_log("delgroup(%u): missing NickInfo for nick %s", ngi->id, ngi->nicks[i]); continue; } rem_ns_timeout(ni, -1, 1); if (ni->user) { if (usermode_reg) send_cmd(s_NickServ, "SVSMODE %s :-%s", ni->nick, mode_flags_to_string(usermode_reg, MODE_USER)); ni->user->ni = NULL; ni->user->ngi = NULL; } call_callback_1(module, cb_delete, ni); del_nickinfo(ni); } call_callback_2(module, cb_groupdelete, ngi, ngi_mainnick(ngi)); del_nickgroupinfo(ngi); return 1; } /*************************************************************************/ /* Drop a nickgroup, logging appropriate information about it first. `u' * is the user dropping the nickgroup; `dropemail' should have one of the * following values: * - NULL for a user dropping their own nick * - PTR_INVALID for a DROPNICK from a Services admin * - The pattern used for a DROPEMAIL from a Services admin * Returns the result of delgroup(ngi). */ int drop_nickgroup(NickGroupInfo *ngi, const User *u, const char *dropemail) { int i; module_log("%s!%s@%s dropped nickgroup %u%s%s%s%s%s%s%s:", u->nick, u->username, u->host, ngi->id, ngi->email ? " (E-mail " : "", ngi->email ? ngi->email : "", ngi->email ? ")" : "", dropemail ? " as Services admin" : "", (dropemail && dropemail!=PTR_INVALID) ? " (DROPEMAIL on " : "", (dropemail && dropemail!=PTR_INVALID) ? dropemail : "", (dropemail && dropemail!=PTR_INVALID) ? ")" : ""); ARRAY_FOREACH (i, ngi->nicks) { module_log(" -- %s!%s@%s dropped nick %s", u->nick, u->username, u->host, ngi->nicks[i]); } return delgroup(ngi); } /*************************************************************************/ /* Create a new SuspendInfo structure and associate it with the given nick * group. */ void suspend_nick(NickGroupInfo *ngi, const char *reason, const char *who, const time_t expires) { SuspendInfo *si = new_suspendinfo(who, sstrdup(reason), expires); ngi->suspendinfo = si; put_nickgroupinfo(ngi); } /*************************************************************************/ /* Delete the suspension data for the given nick group. We also alter the * last_seen value for all nicks in this group to ensure that they do not * expire within the next NSSuspendGrace seconds, giving the owner a chance * to reclaim them (but only if set_time is nonzero). */ void unsuspend_nick(NickGroupInfo *ngi, int set_time) { time_t now = time(NULL); if (!ngi->suspendinfo) { module_log("unsuspend: called on non-suspended nick group %u [%s]", ngi->id, ngi->nicks[0]); return; } free_suspendinfo(ngi->suspendinfo); ngi->suspendinfo = NULL; put_nickgroupinfo(ngi); if (set_time && NSExpire && NSSuspendGrace) { int i; if (ngi->authcode) { ngi->authset = now; module_log("unsuspend: altering authset time for %s (nickgroup" " %u) to %ld", ngi_mainnick(ngi), ngi->id, (long)ngi->authset); } ARRAY_FOREACH (i, ngi->nicks) { NickInfo *ni = get_nickinfo_noexpire(ngi->nicks[i]); if (!ni) { module_log("unsuspend: unable to retrieve NickInfo for %s" " (nick group %u)", ngi->nicks[i], ngi->id); continue; } if (now - ni->last_seen >= NSExpire - NSSuspendGrace) { ni->last_seen = now - NSExpire + NSSuspendGrace; put_nickinfo(ni); module_log("unsuspend: altering last_seen time for %s to %ld", ni->nick, (long)ni->last_seen); } } } } /*************************************************************************/ /*************************************************************************/ /* Check the password given by the user and handle errors if it's not * correct (or the encryption routine fails). `command' is the name of * the command calling this function, and `failure_msg' is the message to * send to the user if the encryption routine fails. Returns 1 if the * password matches, 0 otherwise. */ int nick_check_password(User *u, NickInfo *ni, const char *password, const char *command, int failure_msg) { int res; NickGroupInfo *ngi = get_ngi(ni); if (!ngi) { module_log("%s: no nickgroup for %s, aborting password check", command, ni->nick); notice_lang(s_NickServ, u, failure_msg); return 0; } res = check_password(password, ngi->pass); if (res == 0) { module_log("%s: bad password for %s from %s!%s@%s", command, ni->nick, u->nick, u->username, u->host); bad_password(s_NickServ, u, ni->nick); ni->bad_passwords++; if (BadPassWarning && ni->bad_passwords == BadPassWarning) { wallops(s_NickServ, "\2Warning:\2 Repeated bad password attempts" " for nick %s", ni->nick); } return 0; } else if (res == -1) { module_log("%s: check_password failed for %s", command, ni->nick); notice_lang(s_NickServ, u, failure_msg); return 0; } else { ni->bad_passwords = 0; return 1; } } /*************************************************************************/ /* Return the number of registered nicknames with the given E-mail address. * If a registered nickname matches the given E-mail address and has an * authcode set (i.e. the address has not been authenticated), return the * negative of the number of registered nicknames: for example, if there * are 5 nicks with the given address, -5 would be returned. * * Note that this function is O(n) in the total number of registered nick * groups, so do not use it lightly! */ int count_nicks_with_email(const char *email) { int count = 0, has_authcode = 0; NickGroupInfo *ngi; for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo()) { if (ngi->email && stricmp(ngi->email, email) == 0) { if (ngi->authcode) has_authcode = 1; count += ngi->nicks_count; } } return has_authcode ? -count : count; } /*************************************************************************/ /*************************************************************************/ int init_util(Module *my_module) { module = my_module; cb_cancel_user = register_callback(module, "cancel_user"); cb_check_expire = register_callback(module, "check_expire"); cb_check_recognized = register_callback(module, "check recognized"); cb_delete = register_callback(module, "nick delete"); cb_groupdelete = register_callback(module, "nickgroup delete"); if (cb_cancel_user < 0 || cb_check_expire < 0 || cb_check_recognized < 0 || cb_delete < 0 || cb_groupdelete < 0 ) { module_log("Unable to register callbacks (util.c)"); return 0; } return 1; } /*************************************************************************/ void exit_util() { unregister_callback(module, cb_groupdelete); unregister_callback(module, cb_delete); unregister_callback(module, cb_check_recognized); unregister_callback(module, cb_check_expire); unregister_callback(module, cb_cancel_user); } /*************************************************************************/ #endif /* !STANDALONE_NICKSERV */