fix stupid typo from --patch-61
[srvx.git] / src / nickserv.c
1 /* nickserv.c - Nick/authentication service
2  * Copyright 2000-2004 srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 #include "chanserv.h"
22 #include "conf.h"
23 #include "global.h"
24 #include "modcmd.h"
25 #include "opserv.h" /* for gag_create(), opserv_bad_channel() */
26 #include "saxdb.h"
27 #include "sendmail.h"
28 #include "timeq.h"
29
30 #ifdef HAVE_REGEX_H
31 #include <regex.h>
32 #endif
33
34 #define NICKSERV_CONF_NAME "services/nickserv"
35
36 #define KEY_DISABLE_NICKS "disable_nicks"
37 #define KEY_DEFAULT_HOSTMASK "default_hostmask"
38 #define KEY_NICKS_PER_HANDLE "nicks_per_handle"
39 #define KEY_NICKS_PER_ACCOUNT "nicks_per_account"
40 #define KEY_PASSWORD_MIN_LENGTH "password_min_length"
41 #define KEY_PASSWORD_MIN_DIGITS "password_min_digits"
42 #define KEY_PASSWORD_MIN_UPPER "password_min_upper"
43 #define KEY_PASSWORD_MIN_LOWER "password_min_lower"
44 #define KEY_VALID_HANDLE_REGEX "valid_handle_regex"
45 #define KEY_VALID_ACCOUNT_REGEX "valid_account_regex"
46 #define KEY_VALID_NICK_REGEX "valid_nick_regex"
47 #define KEY_DB_BACKUP_FREQ "db_backup_freq"
48 #define KEY_MODOPER_LEVEL "modoper_level"
49 #define KEY_SET_EPITHET_LEVEL "set_epithet_level"
50 #define KEY_FLAG_LEVELS "flag_levels"
51 #define KEY_HANDLE_EXPIRE_FREQ  "handle_expire_freq"
52 #define KEY_ACCOUNT_EXPIRE_FREQ "account_expire_freq"
53 #define KEY_HANDLE_EXPIRE_DELAY "handle_expire_delay"
54 #define KEY_ACCOUNT_EXPIRE_DELAY "account_expire_delay"
55 #define KEY_NOCHAN_HANDLE_EXPIRE_DELAY "nochan_handle_expire_delay"
56 #define KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY "nochan_account_expire_delay"
57 #define KEY_DICT_FILE "dict_file"
58 #define KEY_NICK "nick"
59 #define KEY_LANGUAGE "language"
60 #define KEY_AUTOGAG_ENABLED "autogag_enabled"
61 #define KEY_AUTOGAG_DURATION "autogag_duration"
62 #define KEY_AUTH_POLICER "auth_policer"
63 #define KEY_EMAIL_VISIBLE_LEVEL "email_visible_level"
64 #define KEY_EMAIL_ENABLED "email_enabled"
65 #define KEY_EMAIL_REQUIRED "email_required"
66 #define KEY_COOKIE_TIMEOUT "cookie_timeout"
67 #define KEY_ACCOUNTS_PER_EMAIL "accounts_per_email"
68 #define KEY_EMAIL_SEARCH_LEVEL "email_search_level"
69
70 #define KEY_ID "id"
71 #define KEY_PASSWD "passwd"
72 #define KEY_NICKS "nicks"
73 #define KEY_MASKS "masks"
74 #define KEY_OPSERV_LEVEL "opserv_level"
75 #define KEY_FLAGS "flags"
76 #define KEY_REGISTER_ON "register"
77 #define KEY_LAST_SEEN "lastseen"
78 #define KEY_INFO "info"
79 #define KEY_USERLIST_STYLE "user_style"
80 #define KEY_SCREEN_WIDTH "screen_width"
81 #define KEY_LAST_AUTHED_HOST "last_authed_host"
82 #define KEY_LAST_QUIT_HOST "last_quit_host"
83 #define KEY_EMAIL_ADDR "email_addr"
84 #define KEY_COOKIE "cookie"
85 #define KEY_COOKIE_DATA "data"
86 #define KEY_COOKIE_TYPE "type"
87 #define KEY_COOKIE_EXPIRES "expires"
88 #define KEY_ACTIVATION "activation"
89 #define KEY_PASSWORD_CHANGE "password change"
90 #define KEY_EMAIL_CHANGE "email change"
91 #define KEY_ALLOWAUTH "allowauth"
92 #define KEY_EPITHET "epithet"
93 #define KEY_TABLE_WIDTH "table_width"
94 #define KEY_ANNOUNCEMENTS "announcements"
95 #define KEY_MAXLOGINS "maxlogins"
96
97 #define NICKSERV_VALID_CHARS    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
98
99 #define NICKSERV_FUNC(NAME) MODCMD_FUNC(NAME)
100 #define OPTION_FUNC(NAME) int NAME(struct userNode *user, struct handle_info *hi, UNUSED_ARG(unsigned int override), unsigned int argc, char *argv[])
101 typedef OPTION_FUNC(option_func_t);
102
103 DEFINE_LIST(handle_info_list, struct handle_info*);
104
105 #define NICKSERV_MIN_PARMS(N) do { \
106   if (argc < N) { \
107     reply("MSG_MISSING_PARAMS", argv[0]); \
108     svccmd_send_help(user, nickserv, cmd); \
109     return 0; \
110   } } while (0)
111
112 struct userNode *nickserv;
113 struct userList curr_helpers;
114 const char *handle_flags = HANDLE_FLAGS;
115
116 static struct module *nickserv_module;
117 static struct service *nickserv_service;
118 static struct log_type *NS_LOG;
119 static dict_t nickserv_handle_dict; /* contains struct handle_info* */
120 static dict_t nickserv_id_dict; /* contains struct handle_info* */
121 static dict_t nickserv_nick_dict; /* contains struct nick_info* */
122 static dict_t nickserv_opt_dict; /* contains option_func_t* */
123 static dict_t nickserv_allow_auth_dict; /* contains struct handle_info* */
124 static dict_t nickserv_email_dict; /* contains struct handle_info_list*, indexed by email addr */
125 static char handle_inverse_flags[256];
126 static unsigned int flag_access_levels[32];
127 static const struct message_entry msgtab[] = {
128     { "NSMSG_HANDLE_EXISTS", "Account $b%s$b is already registered." },
129     { "NSMSG_PASSWORD_SHORT", "Your password must be at least %lu characters long." },
130     { "NSMSG_PASSWORD_ACCOUNT", "Your password may not be the same as your account name." },
131     { "NSMSG_PASSWORD_DICTIONARY", "Your password should not be the word \"password\", or any other dictionary word." },
132     { "NSMSG_PASSWORD_READABLE", "Your password must have at least %lu digit(s), %lu capital letter(s), and %lu lower-case letter(s)." },
133     { "NSMSG_PARTIAL_REGISTER", "Account has been registered to you; nick was already registered to someone else." },
134     { "NSMSG_OREGISTER_VICTIM", "%s has registered a new account for you (named %s)." },
135     { "NSMSG_OREGISTER_H_SUCCESS", "Account has been registered." },
136     { "NSMSG_REGISTER_H_SUCCESS", "Account has been registered to you." },
137     { "NSMSG_REGISTER_HN_SUCCESS", "Account and nick have been registered to you." },
138     { "NSMSG_REQUIRE_OPER", "You must be an $bIRC Operator$b to register the first account." },
139     { "NSMSG_ROOT_HANDLE", "Account %s has been granted $broot-level privileges$b." },
140     { "NSMSG_USE_COOKIE_REGISTER", "To activate your account, you must check your email for the \"cookie\" that has been mailed to it.  When you have it, use the $bcookie$b command to complete registration." },
141     { "NSMSG_USE_COOKIE_RESETPASS", "A cookie has been mailed to your account's email address.  You must check your email and use the $bcookie$b command to confirm the password change." },
142     { "NSMSG_USE_COOKIE_EMAIL_1", "A cookie has been mailed to the new address you requested.  To finish setting your email address, please check your email for the cookie and use the $bcookie$b command to verify." },
143     { "NSMSG_USE_COOKIE_EMAIL_2", "A cookie has been generated, and half mailed to each your old and new addresses.  To finish changing your email address, please check your email for the cookie and use the $bcookie$b command to verify." },
144     { "NSMSG_USE_COOKIE_AUTH", "A cookie has been generated and sent to your email address.  Once you have checked your email and received the cookie, auth using the $bcookie$b command." },
145     { "NSMSG_COOKIE_LIVE", "Account $b%s$b already has a cookie active.  Please either finish using that cookie, wait for it to expire, or auth to the account and use the $bdelcookie$b command." },
146     { "NSMSG_EMAIL_UNACTIVATED", "That email address already has an unused cookie outstanding.  Please use the cookie or wait for it to expire." },
147     { "NSMSG_NO_COOKIE", "Your account does not have any cookie issued right now." },
148     { "NSMSG_CANNOT_COOKIE", "You cannot use that kind of cookie when you are logged in." },
149     { "NSMSG_BAD_COOKIE", "That cookie is not the right one.  Please make sure you are copying it EXACTLY from the email; it is case-sensitive, so $bABC$b is different from $babc$b." },
150     { "NSMSG_HANDLE_ACTIVATED", "Your account is now activated (with the password you entered when you registered).  You are now authenticated to your account." },
151     { "NSMSG_PASSWORD_CHANGED", "You have successfully changed your password to what you requested with the $bresetpass$b command." },
152     { "NSMSG_EMAIL_PROHIBITED", "%s may not be used as an email address: %s" },
153     { "NSMSG_EMAIL_OVERUSED", "There are already the maximum number of accounts associated with that email address." },
154     { "NSMSG_EMAIL_SAME", "That is the email address already there; no need to change it." },
155     { "NSMSG_EMAIL_CHANGED", "You have successfully changed your email address." },
156     { "NSMSG_BAD_COOKIE_TYPE", "Your account had bad cookie type %d; sorry.  I am confused.  Please report this bug." },
157     { "NSMSG_MUST_TIME_OUT", "You must wait for cookies of that type to time out." },
158     { "NSMSG_ATE_COOKIE", "I ate the cookie for your account.  You may now have another." },
159     { "NSMSG_USE_RENAME", "You are already authenticated to account $b%s$b -- contact the support staff to rename your account." },
160     { "NSMSG_ALREADY_REGISTERING", "You have already used $bREGISTER$b once this session; you may not use it again." },
161     { "NSMSG_REGISTER_BAD_NICKMASK", "Could not recognize $b%s$b as either a current nick or a hostmask." },
162     { "NSMSG_NICK_NOT_REGISTERED", "Nick $b%s$b has not been registered to any account." },
163     { "NSMSG_HANDLE_NOT_FOUND", "Could not find your account -- did you register yet?" },
164     { "NSMSG_ALREADY_AUTHED", "You are already authed to account $b%s$b; you must reconnect to auth to a different account." },
165     { "NSMSG_USE_AUTHCOOKIE", "Your hostmask is not valid for account $b%1$s$b.  Please use the $bauthcookie$b command to grant yourself access.  (/msg $S authcookie %1$s)" },
166     { "NSMSG_HOSTMASK_INVALID", "Your hostmask is not valid for account $b%s$b." },
167     { "NSMSG_USER_IS_SERVICE", "$b%s$b is a network service; you can only use that command on real users." },
168     { "NSMSG_USER_PREV_AUTH", "$b%s$b is already authenticated." },
169     { "NSMSG_USER_PREV_STAMP", "$b%s$b has authenticated to an account once and cannot authenticate again." },
170     { "NSMSG_BAD_MAX_LOGINS", "MaxLogins must be at most %d." },
171     { "NSMSG_LANGUAGE_NOT_FOUND", "Language $b%s$b is not supported; $b%s$b was the closest available match." },
172     { "NSMSG_MAX_LOGINS", "Your account already has its limit of %d user(s) logged in." },
173     { "NSMSG_STAMPED_REGISTER", "You have already authenticated to an account once this session; you may not register a new account." },
174     { "NSMSG_STAMPED_AUTH", "You have already authenticated to an account once this session; you may not authenticate to another." },
175     { "NSMSG_STAMPED_RESETPASS", "You have already authenticated to an account once this session; you may not reset your password to authenticate again." },
176     { "NSMSG_STAMPED_AUTHCOOKIE",  "You have already authenticated to an account once this session; you may not use a cookie to authenticate to another account." },
177     { "NSMSG_HANDLEINFO_ON", "Account information for $b%s$b:" },
178     { "NSMSG_HANDLEINFO_ID", "  Account ID: %lu" },
179     { "NSMSG_HANDLEINFO_REGGED", "  Registered on: %s" },
180     { "NSMSG_HANDLEINFO_LASTSEEN", "  Last seen: %s" },
181     { "NSMSG_HANDLEINFO_LASTSEEN_NOW", "  Last seen: Right now!" },
182     { "NSMSG_HANDLEINFO_VACATION", "  On vacation." },
183     { "NSMSG_HANDLEINFO_EMAIL_ADDR", "  Email address: %s" },
184     { "NSMSG_HANDLEINFO_COOKIE_ACTIVATION", "  Cookie: There is currently an activation cookie issued for this account" },
185     { "NSMSG_HANDLEINFO_COOKIE_PASSWORD", "  Cookie: There is currently a password change cookie issued for this account" },
186     { "NSMSG_HANDLEINFO_COOKIE_EMAIL", "  Cookie: There is currently an email change cookie issued for this account" },
187     { "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH", "  Cookie: There is currently an allowauth cookie issued for this account" },
188     { "NSMSG_HANDLEINFO_COOKIE_UNKNOWN", "  Cookie: There is currently an unknown cookie issued for this account" },
189     { "NSMSG_HANDLEINFO_INFOLINE", "  Infoline: %s" },
190     { "NSMSG_HANDLEINFO_FLAGS", "  Flags: %s" },
191     { "NSMSG_HANDLEINFO_EPITHET", "  Epithet: %s" },
192     { "NSMSG_HANDLEINFO_LAST_HOST", "  Last quit hostmask: %s" },
193     { "NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN", "  Last quit hostmask: Unknown" },
194     { "NSMSG_HANDLEINFO_NICKS", "  Nickname(s): %s" },
195     { "NSMSG_HANDLEINFO_MASKS", "  Hostmask(s): %s" },
196     { "NSMSG_HANDLEINFO_CHANNELS", "  Channel(s): %s" },
197     { "NSMSG_HANDLEINFO_CURRENT", "  Current nickname(s): %s" },
198     { "NSMSG_HANDLEINFO_DNR", "  Do-not-register (by %s): %s" },
199     { "NSMSG_USERINFO_AUTHED_AS", "$b%s$b is authenticated to account $b%s$b." },
200     { "NSMSG_USERINFO_NOT_AUTHED", "$b%s$b is not authenticated to any account." },
201     { "NSMSG_NICKINFO_OWNER", "Nick $b%s$b is owned by account $b%s$b." },
202     { "NSMSG_PASSWORD_INVALID", "Incorrect password; please try again." },
203     { "NSMSG_PLEASE_SET_EMAIL", "We now require email addresses for users.  Please use the $bset email$b command to set your email address!" },
204     { "NSMSG_WEAK_PASSWORD", "WARNING: You are using a password that is considered weak (easy to guess).  It is STRONGLY recommended you change it (now, if not sooner) by typing \"/msg $S@$s PASS oldpass newpass\" (with your current password and a new password)." },
205     { "NSMSG_HANDLE_SUSPENDED", "Your $b$N$b account has been suspended; you may not use it." },
206     { "NSMSG_AUTH_SUCCESS", "I recognize you." },
207     { "NSMSG_ALLOWAUTH_STAFF", "$b%s$b is a helper or oper; please use $bstaff$b after the account name to allowauth." },
208     { "NSMSG_AUTH_ALLOWED", "User $b%s$b may now authenticate to account $b%s$b." },
209     { "NSMSG_AUTH_ALLOWED_MSG", "You may now authenticate to account $b%s$b by typing $b/msg $N@$s auth %s password$b (using your password).  If you will be using this computer regularly, please type $b/msg $N addmask$b (AFTER you auth) to permanently add your hostmask." },
210     { "NSMSG_AUTH_ALLOWED_EMAIL", "You may also (after you auth) type $b/msg $N set email user@your.isp$b to set an email address.  This will let you use the $bauthcookie$b command to be authenticated in the future." },
211     { "NSMSG_AUTH_NORMAL_ONLY", "User $b%s$b may now only authenticate to accounts with matching hostmasks." },
212     { "NSMSG_AUTH_UNSPECIAL", "User $b%s$b did not have any special auth allowance." },
213     { "NSMSG_MUST_AUTH", "You must be authenticated first." },
214     { "NSMSG_TOO_MANY_NICKS", "You have already registered the maximum permitted number of nicks." },
215     { "NSMSG_NICK_EXISTS", "Nick $b%s$b already registered." },
216     { "NSMSG_REGNICK_SUCCESS", "Nick $b%s$b has been registered to you." },
217     { "NSMSG_OREGNICK_SUCCESS", "Nick $b%s$b has been registered to account $b%s$b." },
218     { "NSMSG_PASS_SUCCESS", "Password changed." },
219     { "NSMSG_MASK_INVALID", "$b%s$b is an invalid hostmask." },
220     { "NSMSG_ADDMASK_ALREADY", "$b%s$b is already a hostmask in your account." },
221     { "NSMSG_ADDMASK_SUCCESS", "Hostmask %s added." },
222     { "NSMSG_DELMASK_NOTLAST", "You may not delete your last hostmask." },
223     { "NSMSG_DELMASK_SUCCESS", "Hostmask %s deleted." },
224     { "NSMSG_DELMASK_NOT_FOUND", "Unable to find mask to be deleted." },
225     { "NSMSG_OPSERV_LEVEL_BAD", "You may not promote another oper above your level." },
226     { "NSMSG_USE_CMD_PASS", "Please use the PASS command to change your password." },
227     { "NSMSG_UNKNOWN_NICK", "I know nothing about nick $b%s$b." },
228     { "NSMSG_NOT_YOUR_NICK", "The nick $b%s$b is not registered to you." },
229     { "NSMSG_NICK_USER_YOU", "I will not let you kill yourself." },
230     { "NSMSG_UNREGNICK_SUCCESS", "Nick $b%s$b has been unregistered." },
231     { "NSMSG_UNREGISTER_SUCCESS", "Account $b%s$b has been unregistered." },
232     { "NSMSG_UNREGISTER_NICKS_SUCCESS", "Account $b%s$b and all its nicks have been unregistered." },
233     { "NSMSG_HANDLE_STATS", "There are %d nicks registered to your account." },
234     { "NSMSG_HANDLE_NONE", "You are not authenticated against any account." },
235     { "NSMSG_GLOBAL_STATS", "There are %d accounts and %d nicks registered globally." },
236     { "NSMSG_GLOBAL_STATS_NONICK", "There are %d accounts registered." },
237     { "NSMSG_CANNOT_GHOST_SELF", "You may not ghost-kill yourself." },
238     { "NSMSG_CANNOT_GHOST_USER", "$b%s$b is not authed to your account; you may not ghost-kill them." },
239     { "NSMSG_GHOST_KILLED", "$b%s$b has been killed as a ghost." },
240     { "NSMSG_ON_VACATION", "You are now on vacation.  Your account will be preserved until you authenticate again." },
241     { "NSMSG_NO_ACCESS", "Access denied." },
242     { "NSMSG_INVALID_FLAG", "$b%c$b is not a valid $N account flag." },
243     { "NSMSG_SET_FLAG", "Applied flags $b%s$b to %s's $N account." },
244     { "NSMSG_FLAG_PRIVILEGED", "You have insufficient access to set flag %c." },
245     { "NSMSG_DB_UNREADABLE", "Unable to read database file %s; check the log for more information." },
246     { "NSMSG_DB_MERGED", "$N merged DB from %s (in "FMT_TIME_T".%03lu seconds)." },
247     { "NSMSG_HANDLE_CHANGED", "$b%s$b's account name has been changed to $b%s$b." },
248     { "NSMSG_BAD_HANDLE", "Account $b%s$b not registered because it is in use by a network service, is too long, or contains invalid characters." },
249     { "NSMSG_BAD_NICK", "Nickname $b%s$b not registered because it is in use by a network service, is too long, or contains invalid characters." },
250     { "NSMSG_BAD_EMAIL_ADDR", "Please use a well-formed email address." },
251     { "NSMSG_FAIL_RENAME", "Account $b%s$b not renamed to $b%s$b because it is in use by a network services, or contains invalid characters." },
252     { "NSMSG_ACCOUNT_SEARCH_RESULTS", "The following accounts were found:" },
253     { "NSMSG_SEARCH_MATCH", "Match: %s" },
254     { "NSMSG_INVALID_ACTION", "%s is an invalid search action." },
255     { "NSMSG_CANNOT_MERGE_SELF", "You cannot merge account $b%s$b with itself." },
256     { "NSMSG_HANDLES_MERGED", "Merged account $b%s$b into $b%s$b." },
257     { "NSMSG_RECLAIM_WARN", "%s is a registered nick - you must auth to account %s or change your nick." },
258     { "NSMSG_RECLAIM_KILL", "Unauthenticated user of nick." },
259     { "NSMSG_RECLAIMED_NONE", "You cannot manually reclaim a nick." },
260     { "NSMSG_RECLAIMED_WARN", "Sent a request for %s to change their nick." },
261     { "NSMSG_RECLAIMED_SVSNICK", "Forcibly changed %s's nick." },
262     { "NSMSG_RECLAIMED_KILL",  "Disconnected %s from the network." },
263     { "NSMSG_CLONE_AUTH", "Warning: %s (%s@%s) authed to your account." },
264     { "NSMSG_SETTING_LIST", "$b$N account settings:$b" },
265     { "NSMSG_INVALID_OPTION", "$b%s$b is an invalid account setting." },
266     { "NSMSG_INVALID_ANNOUNCE", "$b%s$b is an invalid announcements value." },
267     { "NSMSG_SET_INFO", "$bINFO:         $b%s" },
268     { "NSMSG_SET_WIDTH", "$bWIDTH:        $b%d" },
269     { "NSMSG_SET_TABLEWIDTH", "$bTABLEWIDTH:   $b%d" },
270     { "NSMSG_SET_COLOR", "$bCOLOR:        $b%s" },
271     { "NSMSG_SET_PRIVMSG", "$bPRIVMSG:      $b%s" },
272     { "NSMSG_SET_STYLE", "$bSTYLE:        $b%s" },
273     { "NSMSG_SET_ANNOUNCEMENTS", "$bANNOUNCEMENTS: $b%s" },
274     { "NSMSG_SET_PASSWORD", "$bPASSWORD:     $b%s" },
275     { "NSMSG_SET_FLAGS", "$bFLAGS:        $b%s" },
276     { "NSMSG_SET_EMAIL", "$bEMAIL:        $b%s" },
277     { "NSMSG_SET_MAXLOGINS", "$bMAXLOGINS:    $b%d" },
278     { "NSMSG_SET_LANGUAGE", "$bLANGUAGE:     $b%s" },
279     { "NSMSG_SET_LEVEL", "$bLEVEL:        $b%d" },
280     { "NSMSG_SET_EPITHET", "$bEPITHET:      $b%s" },
281     { "NSEMAIL_ACTIVATION_SUBJECT", "Account verification for %s" },
282     { "NSEMAIL_ACTIVATION_BODY", "This email has been sent to verify that this email address belongs to the person who tried to register an account on %1$s.  Your cookie is:\n    %2$s\nTo verify your email address and complete the account registration, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$s\nThis command is only used once to complete your account registration, and never again. Once you have run this command, you will need to authenticate everytime you reconnect to the network. To do this, you will have to type this command every time you reconnect:\n    /msg %3$s@%4$s AUTH %5$s your-password\n Please remember to fill in 'your-password' with the actual password you gave to us when you registered.\n\nIf you did NOT request this account, you do not need to do anything.  Please contact the %1$s staff if you have questions, and be sure to check our website." },
283     { "NSEMAIL_PASSWORD_CHANGE_SUBJECT", "Password change verification on %s" },
284     { "NSEMAIL_PASSWORD_CHANGE_BODY", "This email has been sent to verify that you wish to change the password on your account %5$s.  Your cookie is %2$s.\nTo complete the password change, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$s\nIf you did NOT request your password to be changed, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
285     { "NSEMAIL_EMAIL_CHANGE_SUBJECT", "Email address change verification for %s" },
286     { "NSEMAIL_EMAIL_CHANGE_BODY_NEW", "This email has been sent to verify that your email address belongs to the same person as account %5$s on %1$s.  The SECOND HALF of your cookie is %2$.*6$s.\nTo verify your address as associated with this account, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s ?????%2$.*6$s\n(Replace the ????? with the FIRST HALF of the cookie, as sent to your OLD email address.)\nIf you did NOT request this email address to be associated with this account, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
287     { "NSEMAIL_EMAIL_CHANGE_BODY_OLD", "This email has been sent to verify that you want to change your email for account %5$s on %1$s from this address to %7$s.  The FIRST HALF of your cookie is %2$.*6$s\nTo verify your new address as associated with this account, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$.*6$s?????\n(Replace the ????? with the SECOND HALF of the cookie, as sent to your NEW email address.)\nIf you did NOT request this change of email address, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
288     { "NSEMAIL_EMAIL_VERIFY_SUBJECT", "Email address verification for %s" },
289     { "NSEMAIL_EMAIL_VERIFY_BODY", "This email has been sent to verify that this address belongs to the same person as %5$s on %1$s.  Your cookie is %2$s.\nTo verify your address as associated with this account, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$s\nIf you did NOT request this email address to be associated with this account, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
290     { "NSEMAIL_ALLOWAUTH_SUBJECT", "Authentication allowed for %s" },
291     { "NSEMAIL_ALLOWAUTH_BODY", "This email has been sent to let you authenticate (auth) to account %5$s on %1$s.  Your cookie is %2$s.\nTo auth to that account, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$s\nIf you did NOT request this authorization, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
292     { "CHECKPASS_YES", "Yes." },
293     { "CHECKPASS_NO", "No." },
294     { NULL, NULL }
295 };
296
297 enum reclaim_action {
298     RECLAIM_NONE,
299     RECLAIM_WARN,
300     RECLAIM_SVSNICK,
301     RECLAIM_KILL
302 };
303 static void nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action);
304 static void nickserv_reclaim_p(void *data);
305
306 static struct {
307     unsigned int disable_nicks : 1;
308     unsigned int valid_handle_regex_set : 1;
309     unsigned int valid_nick_regex_set : 1;
310     unsigned int autogag_enabled : 1;
311     unsigned int email_enabled : 1;
312     unsigned int email_required : 1;
313     unsigned int default_hostmask : 1;
314     unsigned int warn_nick_owned : 1;
315     unsigned int warn_clone_auth : 1;
316     unsigned long nicks_per_handle;
317     unsigned long password_min_length;
318     unsigned long password_min_digits;
319     unsigned long password_min_upper;
320     unsigned long password_min_lower;
321     unsigned long db_backup_frequency;
322     unsigned long handle_expire_frequency;
323     unsigned long autogag_duration;
324     unsigned long email_visible_level;
325     unsigned long cookie_timeout;
326     unsigned long handle_expire_delay;
327     unsigned long nochan_handle_expire_delay;
328     unsigned long modoper_level;
329     unsigned long set_epithet_level;
330     unsigned long handles_per_email;
331     unsigned long email_search_level;
332     const char *network_name;
333     const char *titlehost_suffix;
334     regex_t valid_handle_regex;
335     regex_t valid_nick_regex;
336     dict_t weak_password_dict;
337     struct policer_params *auth_policer_params;
338     enum reclaim_action reclaim_action;
339     enum reclaim_action auto_reclaim_action;
340     unsigned long auto_reclaim_delay;
341     unsigned char default_maxlogins;
342     unsigned char hard_maxlogins;
343 } nickserv_conf;
344
345 /* We have 2^32 unique account IDs to use. */
346 unsigned long int highest_id = 0;
347
348 static char *
349 canonicalize_hostmask(char *mask)
350 {
351     char *out = mask, *temp;
352     if ((temp = strchr(mask, '!'))) {
353         temp++;
354         while (*temp) *out++ = *temp++;
355         *out++ = 0;
356     }
357     return mask;
358 }
359
360 static struct handle_info *
361 register_handle(const char *handle, const char *passwd, UNUSED_ARG(unsigned long id))
362 {
363     struct handle_info *hi;
364
365 #ifdef WITH_PROTOCOL_BAHAMUT
366     char id_base64[IDLEN + 1];
367     do
368     {
369         /* Assign a unique account ID to the account; note that 0 is
370            an invalid account ID. 1 is therefore the first account ID. */
371         if (!id) {
372             id = 1 + highest_id++;
373         } else {
374             /* Note: highest_id is and must always be the highest ID. */
375             if(id > highest_id) {
376                 highest_id = id;
377             }
378         }
379         inttobase64(id_base64, id, IDLEN);
380
381         /* Make sure an account with the same ID doesn't exist. If a
382            duplicate is found, log some details and assign a new one.
383            This should be impossible, but it never hurts to expect it. */
384         if ((hi = dict_find(nickserv_id_dict, id_base64, NULL))) {
385             log_module(NS_LOG, LOG_WARNING, "Duplicated account ID %lu (%s) found belonging to %s while inserting %s.", id, id_base64, hi->handle, handle);
386             id = 0;
387         }
388     } while(!id);
389 #endif
390
391     hi = calloc(1, sizeof(*hi));
392     hi->userlist_style = HI_DEFAULT_STYLE;
393     hi->announcements = '?';
394     hi->handle = strdup(handle);
395     safestrncpy(hi->passwd, passwd, sizeof(hi->passwd));
396     hi->infoline = NULL;
397     dict_insert(nickserv_handle_dict, hi->handle, hi);
398
399 #ifdef WITH_PROTOCOL_BAHAMUT
400     hi->id = id;
401     dict_insert(nickserv_id_dict, strdup(id_base64), hi);
402 #endif
403
404     return hi;
405 }
406
407 static void
408 register_nick(const char *nick, struct handle_info *owner)
409 {
410     struct nick_info *ni;
411     ni = malloc(sizeof(struct nick_info));
412     safestrncpy(ni->nick, nick, sizeof(ni->nick));
413     ni->owner = owner;
414     ni->next = owner->nicks;
415     owner->nicks = ni;
416     dict_insert(nickserv_nick_dict, ni->nick, ni);
417 }
418
419 static void
420 free_nick_info(void *vni)
421 {
422     struct nick_info *ni = vni;
423     free(ni);
424 }
425
426 static void
427 delete_nick(struct nick_info *ni)
428 {
429     struct nick_info *last, *next;
430     struct userNode *user;
431     /* Check to see if we should mark a user as unregistered. */
432     if ((user = GetUserH(ni->nick)) && IsReggedNick(user)) {
433         user->modes &= ~FLAGS_REGNICK;
434         irc_regnick(user);
435     }
436     /* Remove ni from the nick_info linked list. */
437     if (ni == ni->owner->nicks) {
438         ni->owner->nicks = ni->next;
439     } else {
440         last = ni->owner->nicks;
441         next = last->next;
442         while (next != ni) {
443             last = next;
444             next = last->next;
445         }
446         last->next = next->next;
447     }
448     dict_remove(nickserv_nick_dict, ni->nick);
449 }
450
451 static unreg_func_t *unreg_func_list;
452 static unsigned int unreg_func_size = 0, unreg_func_used = 0;
453
454 void
455 reg_unreg_func(unreg_func_t func)
456 {
457     if (unreg_func_used == unreg_func_size) {
458         if (unreg_func_size) {
459             unreg_func_size <<= 1;
460             unreg_func_list = realloc(unreg_func_list, unreg_func_size*sizeof(unreg_func_t));
461         } else {
462             unreg_func_size = 8;
463             unreg_func_list = malloc(unreg_func_size*sizeof(unreg_func_t));
464         }
465     }
466     unreg_func_list[unreg_func_used++] = func;
467 }
468
469 static void
470 nickserv_free_cookie(void *data)
471 {
472     struct handle_cookie *cookie = data;
473     if (cookie->hi) cookie->hi->cookie = NULL;
474     if (cookie->data) free(cookie->data);
475     free(cookie);
476 }
477
478 static void
479 free_handle_info(void *vhi)
480 {
481     struct handle_info *hi = vhi;
482
483 #ifdef WITH_PROTOCOL_BAHAMUT
484     char id[IDLEN + 1];
485
486     inttobase64(id, hi->id, IDLEN);
487     dict_remove(nickserv_id_dict, id);
488 #endif
489
490     free_string_list(hi->masks);
491     assert(!hi->users);
492
493     while (hi->nicks)
494         delete_nick(hi->nicks);
495     free(hi->infoline);
496     free(hi->epithet);
497     if (hi->cookie) {
498         timeq_del(hi->cookie->expires, nickserv_free_cookie, hi->cookie, 0);
499         nickserv_free_cookie(hi->cookie);
500     }
501     if (hi->email_addr) {
502         struct handle_info_list *hil = dict_find(nickserv_email_dict, hi->email_addr, NULL);
503         handle_info_list_remove(hil, hi);
504         if (!hil->used)
505             dict_remove(nickserv_email_dict, hi->email_addr);
506     }
507     free(hi);
508 }
509
510 static void set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp);
511
512 static void
513 nickserv_unregister_handle(struct handle_info *hi, struct userNode *notify)
514 {
515     unsigned int n;
516
517     for (n=0; n<unreg_func_used; n++)
518         unreg_func_list[n](notify, hi);
519     while (hi->users)
520         set_user_handle_info(hi->users, NULL, 0);
521     if (notify) {
522         if (nickserv_conf.disable_nicks)
523             send_message(notify, nickserv, "NSMSG_UNREGISTER_SUCCESS", hi->handle);
524         else
525             send_message(notify, nickserv, "NSMSG_UNREGISTER_NICKS_SUCCESS", hi->handle);
526     }
527     dict_remove(nickserv_handle_dict, hi->handle);
528 }
529
530 struct handle_info*
531 get_handle_info(const char *handle)
532 {
533     return dict_find(nickserv_handle_dict, handle, 0);
534 }
535
536 struct nick_info*
537 get_nick_info(const char *nick)
538 {
539     return nickserv_conf.disable_nicks ? 0 : dict_find(nickserv_nick_dict, nick, 0);
540 }
541
542 struct modeNode *
543 find_handle_in_channel(struct chanNode *channel, struct handle_info *handle, struct userNode *except)
544 {
545     unsigned int nn;
546     struct modeNode *mn;
547
548     for (nn=0; nn<channel->members.used; ++nn) {
549         mn = channel->members.list[nn];
550         if ((mn->user != except) && (mn->user->handle_info == handle))
551             return mn;
552     }
553     return NULL;
554 }
555
556 int
557 oper_has_access(struct userNode *user, struct userNode *bot, unsigned int min_level, unsigned int quiet) {
558     if (!user->handle_info) {
559         if (!quiet)
560             send_message(user, bot, "MSG_AUTHENTICATE");
561         return 0;
562     }
563
564     if (!IsOper(user) && (!IsHelping(user) || min_level)) {
565         if (!quiet)
566             send_message(user, bot, "NSMSG_NO_ACCESS");
567         return 0;
568     }
569
570     if (HANDLE_FLAGGED(user->handle_info, OPER_SUSPENDED)) {
571         if (!quiet)
572             send_message(user, bot, "MSG_OPER_SUSPENDED");
573         return 0;
574     }
575
576     if (user->handle_info->opserv_level < min_level) {
577         if (!quiet)
578             send_message(user, bot, "NSMSG_NO_ACCESS");
579         return 0;
580     }
581
582     return 1;
583 }
584
585 static int
586 is_valid_handle(const char *handle)
587 {
588     struct userNode *user;
589     /* cant register a juped nick/service nick as handle, to prevent confusion */
590     user = GetUserH(handle);
591     if (user && IsLocal(user))
592         return 0;
593     /* check against maximum length */
594     if (strlen(handle) > NICKSERV_HANDLE_LEN)
595         return 0;
596     /* for consistency, only allow account names that could be nicks */
597     if (!is_valid_nick(handle))
598         return 0;
599     /* disallow account names that look like bad words */
600     if (opserv_bad_channel(handle))
601         return 0;
602     /* test either regex or containing all valid chars */
603     if (nickserv_conf.valid_handle_regex_set) {
604         int err = regexec(&nickserv_conf.valid_handle_regex, handle, 0, 0, 0);
605         if (err) {
606             char buff[256];
607             buff[regerror(err, &nickserv_conf.valid_handle_regex, buff, sizeof(buff))] = 0;
608             log_module(NS_LOG, LOG_INFO, "regexec error: %s (%d)", buff, err);
609         }
610         return !err;
611     } else {
612         return !handle[strspn(handle, NICKSERV_VALID_CHARS)];
613     }
614 }
615
616 static int
617 is_registerable_nick(const char *nick)
618 {
619     /* make sure it could be used as an account name */
620     if (!is_valid_handle(nick))
621         return 0;
622     /* check length */
623     if (strlen(nick) > NICKLEN)
624         return 0;
625     /* test either regex or as valid handle */
626     if (nickserv_conf.valid_nick_regex_set) {
627         int err = regexec(&nickserv_conf.valid_nick_regex, nick, 0, 0, 0);
628         if (err) {
629             char buff[256];
630             buff[regerror(err, &nickserv_conf.valid_nick_regex, buff, sizeof(buff))] = 0;
631             log_module(NS_LOG, LOG_INFO, "regexec error: %s (%d)", buff, err);
632         }
633         return !err;
634     }
635     return 1;
636 }
637
638 static int
639 is_valid_email_addr(const char *email)
640 {
641     return strchr(email, '@') != NULL;
642 }
643
644 static const char *
645 visible_email_addr(struct userNode *user, struct handle_info *hi)
646 {
647     if (hi->email_addr) {
648         if (oper_has_access(user, nickserv, nickserv_conf.email_visible_level, 1)) {
649             return hi->email_addr;
650         } else {
651             return "Set.";
652         }
653     } else {
654         return "Not set.";
655     }
656 }
657
658 struct handle_info *
659 smart_get_handle_info(struct userNode *service, struct userNode *user, const char *name)
660 {
661     struct handle_info *hi;
662     struct userNode *target;
663
664     switch (*name) {
665     case '*':
666         if (!(hi = get_handle_info(++name))) {
667             send_message(user, service, "MSG_HANDLE_UNKNOWN", name);
668             return 0;
669         }
670         return hi;
671     default:
672         if (!(target = GetUserH(name))) {
673             send_message(user, service, "MSG_NICK_UNKNOWN", name);
674             return 0;
675         }
676         if (IsLocal(target)) {
677             if (IsService(target))
678                 send_message(user, service, "NSMSG_USER_IS_SERVICE", target->nick);
679             else
680                 send_message(user, service, "MSG_USER_AUTHENTICATE", target->nick);
681             return 0;
682         }
683         if (!(hi = target->handle_info)) {
684             send_message(user, service, "MSG_USER_AUTHENTICATE", target->nick);
685             return 0;
686         }
687         return hi;
688     }
689 }
690
691 int
692 oper_outranks(struct userNode *user, struct handle_info *hi) {
693     if (user->handle_info->opserv_level > hi->opserv_level)
694         return 1;
695     if (user->handle_info->opserv_level == hi->opserv_level) {
696         if ((user->handle_info->opserv_level == 1000)
697             || (user->handle_info == hi)
698             || ((user->handle_info->opserv_level == 0)
699                 && !(HANDLE_FLAGGED(hi, SUPPORT_HELPER) || HANDLE_FLAGGED(hi, NETWORK_HELPER))
700                 && HANDLE_FLAGGED(user->handle_info, HELPING))) {
701             return 1;
702         }
703     }
704     send_message(user, nickserv, "MSG_USER_OUTRANKED", hi->handle);
705     return 0;
706 }
707
708 static struct handle_info *
709 get_victim_oper(struct userNode *user, const char *target)
710 {
711     struct handle_info *hi;
712     if (!(hi = smart_get_handle_info(nickserv, user, target)))
713         return 0;
714     if (HANDLE_FLAGGED(user->handle_info, OPER_SUSPENDED)) {
715         send_message(user, nickserv, "MSG_OPER_SUSPENDED");
716         return 0;
717     }
718     return oper_outranks(user, hi) ? hi : NULL;
719 }
720
721 static int
722 valid_user_for(struct userNode *user, struct handle_info *hi)
723 {
724     unsigned int ii;
725
726     /* If no hostmasks on the account, allow it. */
727     if (!hi->masks->used)
728         return 1;
729     /* If any hostmask matches, allow it. */
730     for (ii=0; ii<hi->masks->used; ii++)
731         if (user_matches_glob(user, hi->masks->list[ii], 0))
732             return 1;
733     /* If they are allowauthed to this account, allow it (removing the aa). */
734     if (dict_find(nickserv_allow_auth_dict, user->nick, NULL) == hi) {
735         dict_remove(nickserv_allow_auth_dict, user->nick);
736         return 2;
737     }
738     /* The user is not allowed to use this account. */
739     return 0;
740 }
741
742 static int
743 is_secure_password(const char *handle, const char *pass, struct userNode *user)
744 {
745     unsigned int i, len;
746     unsigned int cnt_digits = 0, cnt_upper = 0, cnt_lower = 0;
747     len = strlen(pass);
748     if (len < nickserv_conf.password_min_length) {
749         if (user)
750             send_message(user, nickserv, "NSMSG_PASSWORD_SHORT", nickserv_conf.password_min_length);
751         return 0;
752     }
753     if (!irccasecmp(pass, handle)) {
754         if (user)
755             send_message(user, nickserv, "NSMSG_PASSWORD_ACCOUNT");
756         return 0;
757     }
758     dict_find(nickserv_conf.weak_password_dict, pass, &i);
759     if (i) {
760         if (user)
761             send_message(user, nickserv, "NSMSG_PASSWORD_DICTIONARY");
762         return 0;
763     }
764     for (i=0; i<len; i++) {
765         if (isdigit(pass[i]))
766             cnt_digits++;
767         if (isupper(pass[i]))
768             cnt_upper++;
769         if (islower(pass[i]))
770             cnt_lower++;
771     }
772     if ((cnt_lower < nickserv_conf.password_min_lower)
773         || (cnt_upper < nickserv_conf.password_min_upper)
774         || (cnt_digits < nickserv_conf.password_min_digits)) {
775         if (user)
776             send_message(user, nickserv, "NSMSG_PASSWORD_READABLE", nickserv_conf.password_min_digits, nickserv_conf.password_min_upper, nickserv_conf.password_min_lower);
777         return 0;
778     }
779     return 1;
780 }
781
782 static auth_func_t *auth_func_list;
783 static unsigned int auth_func_size = 0, auth_func_used = 0;
784
785 void
786 reg_auth_func(auth_func_t func)
787 {
788     if (auth_func_used == auth_func_size) {
789         if (auth_func_size) {
790             auth_func_size <<= 1;
791             auth_func_list = realloc(auth_func_list, auth_func_size*sizeof(auth_func_t));
792         } else {
793             auth_func_size = 8;
794             auth_func_list = malloc(auth_func_size*sizeof(auth_func_t));
795         }
796     }
797     auth_func_list[auth_func_used++] = func;
798 }
799
800 static handle_rename_func_t *rf_list;
801 static unsigned int rf_list_size, rf_list_used;
802
803 void
804 reg_handle_rename_func(handle_rename_func_t func)
805 {
806     if (rf_list_used == rf_list_size) {
807         if (rf_list_size) {
808             rf_list_size <<= 1;
809             rf_list = realloc(rf_list, rf_list_size*sizeof(rf_list[0]));
810         } else {
811             rf_list_size = 8;
812             rf_list = malloc(rf_list_size*sizeof(rf_list[0]));
813         }
814     }
815     rf_list[rf_list_used++] = func;
816 }
817
818 static void
819 set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
820 {
821     unsigned int n;
822     struct handle_info *old_info;
823
824     /* This can happen if somebody uses COOKIE while authed, or if
825      * they re-auth to their current handle (which is silly, but users
826      * are like that). */
827     if (user->handle_info == hi)
828         return;
829
830     if (user->handle_info) {
831         struct userNode *other;
832
833         if (IsHelper(user))
834             userList_remove(&curr_helpers, user);
835
836         /* remove from next_authed linked list */
837         if (user->handle_info->users == user) {
838             user->handle_info->users = user->next_authed;
839         } else {
840             for (other = user->handle_info->users;
841                  other->next_authed != user;
842                  other = other->next_authed) ;
843             other->next_authed = user->next_authed;
844         }
845         /* if nobody left on old handle, and they're not an oper, remove !god */
846         if (!user->handle_info->users && !user->handle_info->opserv_level)
847             HANDLE_CLEAR_FLAG(user->handle_info, HELPING);
848         /* record them as being last seen at this time */
849         user->handle_info->lastseen = now;
850         /* and record their hostmask */
851         snprintf(user->handle_info->last_quit_host, sizeof(user->handle_info->last_quit_host), "%s@%s", user->ident, user->hostname);
852     }
853     old_info = user->handle_info;
854     user->handle_info = hi;
855     if (hi && !hi->users && !hi->opserv_level)
856         HANDLE_CLEAR_FLAG(hi, HELPING);
857     for (n=0; n<auth_func_used; n++)
858         auth_func_list[n](user, old_info);
859     if (hi) {
860         struct nick_info *ni;
861
862         HANDLE_CLEAR_FLAG(hi, FROZEN);
863         if (nickserv_conf.warn_clone_auth) {
864             struct userNode *other;
865             for (other = hi->users; other; other = other->next_authed)
866                 send_message(other, nickserv, "NSMSG_CLONE_AUTH", user->nick, user->ident, user->hostname);
867         }
868         user->next_authed = hi->users;
869         hi->users = user;
870         hi->lastseen = now;
871         if (IsHelper(user))
872             userList_append(&curr_helpers, user);
873
874         if (stamp) {
875 #ifdef WITH_PROTOCOL_BAHAMUT
876             /* Stamp users with their account ID. */
877             char id[IDLEN + 1];
878             inttobase64(id, hi->id, IDLEN);
879 #elif WITH_PROTOCOL_P10
880             /* Stamp users with their account name. */
881             char *id = hi->handle;
882 #else
883             const char *id = "???";
884 #endif
885             if (!nickserv_conf.disable_nicks) {
886                 struct nick_info *ni;
887                 for (ni = hi->nicks; ni; ni = ni->next) {
888                     if (!irccasecmp(user->nick, ni->nick)) {
889                         user->modes |= FLAGS_REGNICK;
890                         break;
891                     }
892                 }
893             }
894             StampUser(user, id);
895         }
896
897         if ((ni = get_nick_info(user->nick)) && (ni->owner == hi))
898             timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
899     } else {
900         /* We cannot clear the user's account ID, unfortunately. */
901         user->next_authed = NULL;
902     }
903 }
904
905 static struct handle_info*
906 nickserv_register(struct userNode *user, struct userNode *settee, const char *handle, const char *passwd, int no_auth)
907 {
908     struct handle_info *hi;
909     struct nick_info *ni;
910     char crypted[MD5_CRYPT_LENGTH];
911
912     if ((hi = dict_find(nickserv_handle_dict, handle, NULL))) {
913         send_message(user, nickserv, "NSMSG_HANDLE_EXISTS", handle);
914         return 0;
915     }
916
917     if (!is_secure_password(handle, passwd, user))
918         return 0;
919
920     cryptpass(passwd, crypted);
921     hi = register_handle(handle, crypted, 0);
922     hi->masks = alloc_string_list(1);
923     hi->users = NULL;
924     hi->language = lang_C;
925     hi->registered = now;
926     hi->lastseen = now;
927     hi->flags = HI_DEFAULT_FLAGS;
928     if (settee && !no_auth)
929         set_user_handle_info(settee, hi, 1);
930
931     if (user != settee)
932         send_message(user, nickserv, "NSMSG_OREGISTER_H_SUCCESS");
933     else if (nickserv_conf.disable_nicks)
934         send_message(user, nickserv, "NSMSG_REGISTER_H_SUCCESS");
935     else if ((ni = dict_find(nickserv_nick_dict, user->nick, NULL)))
936         send_message(user, nickserv, "NSMSG_PARTIAL_REGISTER");
937     else {
938         register_nick(user->nick, hi);
939         send_message(user, nickserv, "NSMSG_REGISTER_HN_SUCCESS");
940     }
941     if (settee && (user != settee))
942         send_message(settee, nickserv, "NSMSG_OREGISTER_VICTIM", user->nick, hi->handle);
943     return hi;
944 }
945
946 static void
947 nickserv_bake_cookie(struct handle_cookie *cookie)
948 {
949     cookie->hi->cookie = cookie;
950     timeq_add(cookie->expires, nickserv_free_cookie, cookie);
951 }
952
953 static void
954 nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_type type, const char *cookie_data)
955 {
956     struct handle_cookie *cookie;
957     char subject[128], body[4096], *misc;
958     const char *netname, *fmt;
959     int first_time = 0;
960
961     if (hi->cookie) {
962         send_message(user, nickserv, "NSMSG_COOKIE_LIVE", hi->handle);
963         return;
964     }
965
966     cookie = calloc(1, sizeof(*cookie));
967     cookie->hi = hi;
968     cookie->type = type;
969     cookie->data = cookie_data ? strdup(cookie_data) : NULL;
970     cookie->expires = now + nickserv_conf.cookie_timeout;
971     inttobase64(cookie->cookie, rand(), 5);
972     inttobase64(cookie->cookie+5, rand(), 5);
973
974     netname = nickserv_conf.network_name;
975     subject[0] = 0;
976
977     switch (cookie->type) {
978     case ACTIVATION:
979         hi->passwd[0] = 0; /* invalidate password */
980         send_message(user, nickserv, "NSMSG_USE_COOKIE_REGISTER");
981         fmt = handle_find_message(hi, "NSEMAIL_ACTIVATION_SUBJECT");
982         snprintf(subject, sizeof(subject), fmt, netname);
983         fmt = handle_find_message(hi, "NSEMAIL_ACTIVATION_BODY");
984         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
985         first_time = 1;
986         break;
987     case PASSWORD_CHANGE:
988         send_message(user, nickserv, "NSMSG_USE_COOKIE_RESETPASS");
989         fmt = handle_find_message(hi, "NSEMAIL_PASSWORD_CHANGE_SUBJECT");
990         snprintf(subject, sizeof(subject), fmt, netname);
991         fmt = handle_find_message(hi, "NSEMAIL_PASSWORD_CHANGE_BODY");
992         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
993         break;
994     case EMAIL_CHANGE:
995         misc = hi->email_addr;
996         hi->email_addr = cookie->data;
997         if (misc) {
998             send_message(user, nickserv, "NSMSG_USE_COOKIE_EMAIL_2");
999             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_SUBJECT");
1000             snprintf(subject, sizeof(subject), fmt, netname);
1001             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_BODY_NEW");
1002             snprintf(body, sizeof(body), fmt, netname, cookie->cookie+COOKIELEN/2, nickserv->nick, self->name, hi->handle, COOKIELEN/2);
1003             sendmail(nickserv, hi, subject, body, 1);
1004             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_BODY_OLD");
1005             snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle, COOKIELEN/2, hi->email_addr);
1006         } else {
1007             send_message(user, nickserv, "NSMSG_USE_COOKIE_EMAIL_1");
1008             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_VERIFY_SUBJECT");
1009             snprintf(subject, sizeof(subject), fmt, netname);
1010             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_VERIFY_BODY");
1011             snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
1012             sendmail(nickserv, hi, subject, body, 1);
1013             subject[0] = 0;
1014         }
1015         hi->email_addr = misc;
1016         break;
1017     case ALLOWAUTH:
1018         fmt = handle_find_message(hi, "NSEMAIL_ALLOWAUTH_SUBJECT");
1019         snprintf(subject, sizeof(subject), fmt, netname);
1020         fmt = handle_find_message(hi, "NSEMAIL_ALLOWAUTH_BODY");
1021         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
1022         send_message(user, nickserv, "NSMSG_USE_COOKIE_AUTH");
1023         break;
1024     default:
1025         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d in nickserv_make_cookie.", cookie->type);
1026         break;
1027     }
1028     if (subject[0])
1029         sendmail(nickserv, hi, subject, body, first_time);
1030     nickserv_bake_cookie(cookie);
1031 }
1032
1033 static void
1034 nickserv_eat_cookie(struct handle_cookie *cookie)
1035 {
1036     cookie->hi->cookie = NULL;
1037     timeq_del(cookie->expires, nickserv_free_cookie, cookie, 0);
1038     nickserv_free_cookie(cookie);
1039 }
1040
1041 static void
1042 nickserv_free_email_addr(void *data)
1043 {
1044     handle_info_list_clean(data);
1045     free(data);
1046 }
1047
1048 static void
1049 nickserv_set_email_addr(struct handle_info *hi, const char *new_email_addr)
1050 {
1051     struct handle_info_list *hil;
1052     /* Remove from old handle_info_list ... */
1053     if (hi->email_addr && (hil = dict_find(nickserv_email_dict, hi->email_addr, 0))) {
1054         handle_info_list_remove(hil, hi);
1055         if (!hil->used) dict_remove(nickserv_email_dict, hil->tag);
1056         hi->email_addr = NULL;
1057     }
1058     /* Add to the new list.. */
1059     if (new_email_addr) {
1060         if (!(hil = dict_find(nickserv_email_dict, new_email_addr, 0))) {
1061             hil = calloc(1, sizeof(*hil));
1062             hil->tag = strdup(new_email_addr);
1063             handle_info_list_init(hil);
1064             dict_insert(nickserv_email_dict, hil->tag, hil);
1065         }
1066         handle_info_list_append(hil, hi);
1067         hi->email_addr = hil->tag;
1068     }
1069 }
1070
1071 static NICKSERV_FUNC(cmd_register)
1072 {
1073     struct handle_info *hi;
1074     const char *email_addr, *password;
1075     int no_auth;
1076
1077     if (!IsOper(user) && !dict_size(nickserv_handle_dict)) {
1078         /* Require the first handle registered to belong to someone +o. */
1079         reply("NSMSG_REQUIRE_OPER");
1080         return 0;
1081     }
1082
1083     if (user->handle_info) {
1084         reply("NSMSG_USE_RENAME", user->handle_info->handle);
1085         return 0;
1086     }
1087
1088     if (IsRegistering(user)) {
1089         reply("NSMSG_ALREADY_REGISTERING");
1090         return 0;
1091     }
1092
1093     if (IsStamped(user)) {
1094         /* Unauthenticated users might still have been stamped
1095            previously and could therefore have a hidden host;
1096            do not allow them to register a new account. */
1097         reply("NSMSG_STAMPED_REGISTER");
1098         return 0;
1099     }
1100
1101     NICKSERV_MIN_PARMS((unsigned)3 + nickserv_conf.email_required);
1102
1103     if (!is_valid_handle(argv[1])) {
1104         reply("NSMSG_BAD_HANDLE", argv[1]);
1105         return 0;
1106     }
1107
1108     if ((argc >= 4) && nickserv_conf.email_enabled) {
1109         struct handle_info_list *hil;
1110         const char *str;
1111
1112         /* Remember email address. */
1113         email_addr = argv[3];
1114
1115         /* Check that the email address looks valid.. */
1116         if (!is_valid_email_addr(email_addr)) {
1117             reply("NSMSG_BAD_EMAIL_ADDR");
1118             return 0;
1119         }
1120
1121         /* .. and that we are allowed to send to it. */
1122         if ((str = sendmail_prohibited_address(email_addr))) {
1123             reply("NSMSG_EMAIL_PROHIBITED", email_addr, str);
1124             return 0;
1125         }
1126
1127         /* If we do email verify, make sure we don't spam the address. */
1128         if ((hil = dict_find(nickserv_email_dict, email_addr, NULL))) {
1129             unsigned int nn;
1130             for (nn=0; nn<hil->used; nn++) {
1131                 if (hil->list[nn]->cookie) {
1132                     reply("NSMSG_EMAIL_UNACTIVATED");
1133                     return 0;
1134                 }
1135             }
1136             if (hil->used >= nickserv_conf.handles_per_email) {
1137                 reply("NSMSG_EMAIL_OVERUSED");
1138                 return 0;
1139             }
1140         }
1141
1142         no_auth = 1;
1143     } else {
1144         email_addr = 0;
1145         no_auth = 0;
1146     }
1147
1148     password = argv[2];
1149     argv[2] = "****";
1150     if (!(hi = nickserv_register(user, user, argv[1], password, no_auth)))
1151         return 0;
1152     /* Add any masks they should get. */
1153     if (nickserv_conf.default_hostmask) {
1154         string_list_append(hi->masks, strdup("*@*"));
1155     } else {
1156         string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1157         if (user->ip.s_addr && user->hostname[strspn(user->hostname, "0123456789.")])
1158             string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1159     }
1160
1161     /* If they're the first to register, give them level 1000. */
1162     if (dict_size(nickserv_handle_dict) == 1) {
1163         hi->opserv_level = 1000;
1164         reply("NSMSG_ROOT_HANDLE", argv[1]);
1165     }
1166
1167     /* Set their email address. */
1168     if (email_addr)
1169         nickserv_set_email_addr(hi, email_addr);
1170
1171     /* If they need to do email verification, tell them. */
1172     if (no_auth)
1173         nickserv_make_cookie(user, hi, ACTIVATION, hi->passwd);
1174
1175     /* Set registering flag.. */
1176     user->modes |= FLAGS_REGISTERING; 
1177
1178     return 1;
1179 }
1180
1181 static NICKSERV_FUNC(cmd_oregister)
1182 {
1183     char *mask;
1184     struct userNode *settee;
1185     struct handle_info *hi;
1186
1187     NICKSERV_MIN_PARMS(4);
1188
1189     if (!is_valid_handle(argv[1])) {
1190         reply("NSMSG_BAD_HANDLE", argv[1]);
1191         return 0;
1192     }
1193
1194     if (strchr(argv[3], '@')) {
1195         mask = canonicalize_hostmask(strdup(argv[3]));
1196         if (argc > 4) {
1197             settee = GetUserH(argv[4]);
1198             if (!settee) {
1199                 reply("MSG_NICK_UNKNOWN", argv[4]);
1200                 free(mask);
1201                 return 0;
1202             }
1203         } else {
1204             settee = NULL;
1205         }
1206     } else if ((settee = GetUserH(argv[3]))) {
1207         mask = generate_hostmask(settee, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
1208     } else {
1209         reply("NSMSG_REGISTER_BAD_NICKMASK", argv[3]);
1210         return 0;
1211     }
1212     if (settee && settee->handle_info) {
1213         reply("NSMSG_USER_PREV_AUTH", settee->nick);
1214         free(mask);
1215         return 0;
1216     }
1217     if (!(hi = nickserv_register(user, settee, argv[1], argv[2], 0))) {
1218         free(mask);
1219         return 0;
1220     }
1221     string_list_append(hi->masks, mask);
1222     return 1;
1223 }
1224
1225 static NICKSERV_FUNC(cmd_handleinfo)
1226 {
1227     char buff[400];
1228     unsigned int i, pos=0, herelen;
1229     struct userNode *target, *next_un;
1230     struct handle_info *hi;
1231     const char *nsmsg_none;
1232
1233     if (argc < 2) {
1234         if (!(hi = user->handle_info)) {
1235             reply("NSMSG_MUST_AUTH");
1236             return 0;
1237         }
1238     } else if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
1239         return 0;
1240     }
1241
1242     nsmsg_none = handle_find_message(hi, "MSG_NONE");
1243     reply("NSMSG_HANDLEINFO_ON", hi->handle);
1244 #ifdef WITH_PROTOCOL_BAHAMUT
1245     reply("NSMSG_HANDLEINFO_ID", hi->id);
1246 #endif
1247     reply("NSMSG_HANDLEINFO_REGGED", ctime(&hi->registered));
1248
1249     if (!hi->users) {
1250         intervalString(buff, now - hi->lastseen, user->handle_info);
1251         reply("NSMSG_HANDLEINFO_LASTSEEN", buff);
1252     } else {
1253         reply("NSMSG_HANDLEINFO_LASTSEEN_NOW");
1254     }
1255
1256     reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none));
1257     if (HANDLE_FLAGGED(hi, FROZEN))
1258         reply("NSMSG_HANDLEINFO_VACATION");
1259
1260     if (oper_has_access(user, cmd->parent->bot, 0, 1)) {
1261         struct do_not_register *dnr;
1262         if ((dnr = chanserv_is_dnr(NULL, hi)))
1263             reply("NSMSG_HANDLEINFO_DNR", dnr->setter, dnr->reason);
1264         if (!oper_outranks(user, hi))
1265             return 1;
1266     } else if (hi != user->handle_info)
1267         return 1;
1268
1269     if (nickserv_conf.email_enabled)
1270         reply("NSMSG_HANDLEINFO_EMAIL_ADDR", visible_email_addr(user, hi));
1271
1272     if (hi->cookie) {
1273         const char *type;
1274         switch (hi->cookie->type) {
1275         case ACTIVATION: type = "NSMSG_HANDLEINFO_COOKIE_ACTIVATION"; break;
1276         case PASSWORD_CHANGE: type = "NSMSG_HANDLEINFO_COOKIE_PASSWORD"; break;
1277         case EMAIL_CHANGE: type = "NSMSG_HANDLEINFO_COOKIE_EMAIL"; break;
1278         case ALLOWAUTH: type = "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH"; break;
1279         default: type = "NSMSG_HANDLEINFO_COOKIE_UNKNOWN"; break;
1280         }
1281         reply(type);
1282     }
1283
1284     if (hi->flags) {
1285         unsigned long flen = 1;
1286         char flags[34]; /* 32 bits possible plus '+' and '\0' */
1287         flags[0] = '+';
1288         for (i=0, flen=1; handle_flags[i]; i++)
1289             if (hi->flags & 1 << i)
1290                 flags[flen++] = handle_flags[i];
1291         flags[flen] = 0;
1292         reply("NSMSG_HANDLEINFO_FLAGS", flags);
1293     } else {
1294         reply("NSMSG_HANDLEINFO_FLAGS", nsmsg_none);
1295     }
1296
1297     if (HANDLE_FLAGGED(hi, SUPPORT_HELPER)
1298         || HANDLE_FLAGGED(hi, NETWORK_HELPER)
1299         || (hi->opserv_level > 0)) {
1300         reply("NSMSG_HANDLEINFO_EPITHET", (hi->epithet ? hi->epithet : nsmsg_none));
1301     }
1302
1303     if (hi->last_quit_host[0])
1304         reply("NSMSG_HANDLEINFO_LAST_HOST", hi->last_quit_host);
1305     else
1306         reply("NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN");
1307
1308     if (nickserv_conf.disable_nicks) {
1309         /* nicks disabled; don't show anything about registered nicks */
1310     } else if (hi->nicks) {
1311         struct nick_info *ni, *next_ni;
1312         for (ni = hi->nicks; ni; ni = next_ni) {
1313             herelen = strlen(ni->nick);
1314             if (pos + herelen + 1 > ArrayLength(buff)) {
1315                 next_ni = ni;
1316                 goto print_nicks_buff;
1317             } else {
1318                 next_ni = ni->next;
1319             }
1320             memcpy(buff+pos, ni->nick, herelen);
1321             pos += herelen; buff[pos++] = ' ';
1322             if (!next_ni) {
1323               print_nicks_buff:
1324                 buff[pos-1] = 0;
1325                 reply("NSMSG_HANDLEINFO_NICKS", buff);
1326                 pos = 0;
1327             }
1328         }
1329     } else {
1330         reply("NSMSG_HANDLEINFO_NICKS", nsmsg_none);
1331     }
1332
1333     if (hi->masks->used) {
1334         for (i=0; i < hi->masks->used; i++) {
1335             herelen = strlen(hi->masks->list[i]);
1336             if (pos + herelen + 1 > ArrayLength(buff)) {
1337                 i--;
1338                 goto print_mask_buff;
1339             }
1340             memcpy(buff+pos, hi->masks->list[i], herelen);
1341             pos += herelen; buff[pos++] = ' ';
1342             if (i+1 == hi->masks->used) {
1343               print_mask_buff:
1344                 buff[pos-1] = 0;
1345                 reply("NSMSG_HANDLEINFO_MASKS", buff);
1346                 pos = 0;
1347             }
1348         }
1349     } else {
1350         reply("NSMSG_HANDLEINFO_MASKS", nsmsg_none);
1351     }
1352
1353     if (hi->channels) {
1354         struct userData *channel, *next;
1355         char *name;
1356
1357         for (channel = hi->channels; channel; channel = next) {
1358             next = channel->u_next;
1359             name = channel->channel->channel->name;
1360             herelen = strlen(name);
1361             if (pos + herelen + 7 > ArrayLength(buff)) {
1362                 next = channel;
1363                 goto print_chans_buff;
1364             }
1365             if (IsUserSuspended(channel))
1366                 buff[pos++] = '-';
1367             pos += sprintf(buff+pos, "%d:%s ", channel->access, name);
1368             if (next == NULL) {
1369               print_chans_buff:
1370                 buff[pos-1] = 0;
1371                 reply("NSMSG_HANDLEINFO_CHANNELS", buff);
1372                 pos = 0;
1373             }
1374         }
1375     } else {
1376         reply("NSMSG_HANDLEINFO_CHANNELS", nsmsg_none);
1377     }
1378
1379     for (target = hi->users; target; target = next_un) {
1380         herelen = strlen(target->nick);
1381         if (pos + herelen + 1 > ArrayLength(buff)) {
1382             next_un = target;
1383             goto print_cnick_buff;
1384         } else {
1385             next_un = target->next_authed;
1386         }
1387         memcpy(buff+pos, target->nick, herelen);
1388         pos += herelen; buff[pos++] = ' ';
1389         if (!next_un) {
1390           print_cnick_buff:
1391             buff[pos-1] = 0;
1392             reply("NSMSG_HANDLEINFO_CURRENT", buff);
1393             pos = 0;
1394         }
1395     }
1396
1397     return 1;
1398 }
1399
1400 static NICKSERV_FUNC(cmd_userinfo)
1401 {
1402     struct userNode *target;
1403
1404     NICKSERV_MIN_PARMS(2);
1405     if (!(target = GetUserH(argv[1]))) {
1406         reply("MSG_NICK_UNKNOWN", argv[1]);
1407         return 0;
1408     }
1409     if (target->handle_info)
1410         reply("NSMSG_USERINFO_AUTHED_AS", target->nick, target->handle_info->handle);
1411     else
1412         reply("NSMSG_USERINFO_NOT_AUTHED", target->nick);
1413     return 1;
1414 }
1415
1416 static NICKSERV_FUNC(cmd_nickinfo)
1417 {
1418     struct nick_info *ni;
1419
1420     NICKSERV_MIN_PARMS(2);
1421     if (!(ni = get_nick_info(argv[1]))) {
1422         reply("MSG_NICK_UNKNOWN", argv[1]);
1423         return 0;
1424     }
1425     reply("NSMSG_NICKINFO_OWNER", ni->nick, ni->owner->handle);
1426     return 1;
1427 }
1428
1429 static NICKSERV_FUNC(cmd_rename_handle)
1430 {
1431     struct handle_info *hi;
1432     char msgbuf[MAXLEN], *old_handle;
1433     unsigned int nn;
1434
1435     NICKSERV_MIN_PARMS(3);
1436     if (!(hi = get_victim_oper(user, argv[1])))
1437         return 0;
1438     if (!is_valid_handle(argv[2])) {
1439         reply("NSMSG_FAIL_RENAME", argv[1], argv[2]);
1440         return 0;
1441     }
1442     if (get_handle_info(argv[2])) {
1443         reply("NSMSG_HANDLE_EXISTS", argv[2]);
1444         return 0;
1445     }
1446
1447     dict_remove2(nickserv_handle_dict, old_handle = hi->handle, 1);
1448     hi->handle = strdup(argv[2]);
1449     dict_insert(nickserv_handle_dict, hi->handle, hi);
1450     for (nn=0; nn<rf_list_used; nn++)
1451         rf_list[nn](hi, old_handle);
1452     snprintf(msgbuf, sizeof(msgbuf), "%s renamed account %s to %s.", user->handle_info->handle, old_handle, hi->handle);
1453     reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
1454     global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
1455     free(old_handle);
1456     return 1;
1457 }
1458
1459 static failpw_func_t *failpw_func_list;
1460 static unsigned int failpw_func_size = 0, failpw_func_used = 0;
1461
1462 void
1463 reg_failpw_func(failpw_func_t func)
1464 {
1465     if (failpw_func_used == failpw_func_size) {
1466         if (failpw_func_size) {
1467             failpw_func_size <<= 1;
1468             failpw_func_list = realloc(failpw_func_list, failpw_func_size*sizeof(failpw_func_t));
1469         } else {
1470             failpw_func_size = 8;
1471             failpw_func_list = malloc(failpw_func_size*sizeof(failpw_func_t));
1472         }
1473     }
1474     failpw_func_list[failpw_func_used++] = func;
1475 }
1476
1477 static NICKSERV_FUNC(cmd_auth)
1478 {
1479     int pw_arg, used, maxlogins;
1480     struct handle_info *hi;
1481     const char *passwd;
1482     struct userNode *other;
1483
1484     if (user->handle_info) {
1485         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1486         return 0;
1487     }
1488     if (IsStamped(user)) {
1489         /* Unauthenticated users might still have been stamped
1490            previously and could therefore have a hidden host;
1491            do not allow them to authenticate. */
1492         reply("NSMSG_STAMPED_AUTH");
1493         return 0;
1494     }
1495     if (argc == 3) {
1496         hi = dict_find(nickserv_handle_dict, argv[1], NULL);
1497         pw_arg = 2;
1498     } else if (argc == 2) {
1499         if (nickserv_conf.disable_nicks) {
1500             if (!(hi = get_handle_info(user->nick))) {
1501                 reply("NSMSG_HANDLE_NOT_FOUND");
1502                 return 0;
1503             }
1504         } else {
1505             /* try to look up their handle from their nick */
1506             struct nick_info *ni;
1507             ni = get_nick_info(user->nick);
1508             if (!ni) {
1509                 reply("NSMSG_NICK_NOT_REGISTERED", user->nick);
1510                 return 0;
1511             }
1512             hi = ni->owner;
1513         }
1514         pw_arg = 1;
1515     } else {
1516         reply("MSG_MISSING_PARAMS", argv[0]);
1517         svccmd_send_help(user, nickserv, cmd);
1518         return 0;
1519     }
1520     if (!hi) {
1521         reply("NSMSG_HANDLE_NOT_FOUND");
1522         return 0;
1523     }
1524     /* Responses from here on look up the language used by the handle they asked about. */
1525     passwd = argv[pw_arg];
1526     if (!valid_user_for(user, hi)) {
1527         if (hi->email_addr && nickserv_conf.email_enabled)
1528             send_message_type(4, user, cmd->parent->bot,
1529                               handle_find_message(hi, "NSMSG_USE_AUTHCOOKIE"),
1530                               hi->handle);
1531         else
1532             send_message_type(4, user, cmd->parent->bot,
1533                               handle_find_message(hi, "NSMSG_HOSTMASK_INVALID"),
1534                               hi->handle);
1535         argv[pw_arg] = "BADMASK";
1536         return 1;
1537     }
1538     if (!checkpass(passwd, hi->passwd)) {
1539         unsigned int n;
1540         send_message_type(4, user, cmd->parent->bot,
1541                           handle_find_message(hi, "NSMSG_PASSWORD_INVALID"));
1542         argv[pw_arg] = "BADPASS";
1543         for (n=0; n<failpw_func_used; n++) failpw_func_list[n](user, hi);
1544         if (nickserv_conf.autogag_enabled) {
1545             if (!user->auth_policer.params) {
1546                 user->auth_policer.last_req = now;
1547                 user->auth_policer.params = nickserv_conf.auth_policer_params;
1548             }
1549             if (!policer_conforms(&user->auth_policer, now, 1.0)) {
1550                 char *hostmask;
1551                 hostmask = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_BYIP|GENMASK_NO_HIDING);
1552                 log_module(NS_LOG, LOG_INFO, "%s auto-gagged for repeated password guessing.", hostmask);
1553                 gag_create(hostmask, nickserv->nick, "Repeated password guessing.", now+nickserv_conf.autogag_duration);
1554                 free(hostmask);
1555                 argv[pw_arg] = "GAGGED";
1556             }
1557         }
1558         return 1;
1559     }
1560     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1561         send_message_type(4, user, cmd->parent->bot,
1562                           handle_find_message(hi, "NSMSG_HANDLE_SUSPENDED"));
1563         argv[pw_arg] = "SUSPENDED";
1564         return 1;
1565     }
1566     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
1567     for (used = 0, other = hi->users; other; other = other->next_authed) {
1568         if (++used >= maxlogins) {
1569             send_message_type(4, user, cmd->parent->bot,
1570                               handle_find_message(hi, "NSMSG_MAX_LOGINS"),
1571                               maxlogins);
1572             argv[pw_arg] = "MAXLOGINS";
1573             return 1;
1574         }
1575     }
1576
1577     set_user_handle_info(user, hi, 1);
1578     if (nickserv_conf.email_required && !hi->email_addr)
1579         reply("NSMSG_PLEASE_SET_EMAIL");
1580     if (!is_secure_password(hi->handle, passwd, NULL))
1581         reply("NSMSG_WEAK_PASSWORD");
1582     if (hi->passwd[0] != '$')
1583         cryptpass(passwd, hi->passwd);
1584     reply("NSMSG_AUTH_SUCCESS");
1585     argv[pw_arg] = "****";
1586     return 1;
1587 }
1588
1589 static allowauth_func_t *allowauth_func_list;
1590 static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
1591
1592 void
1593 reg_allowauth_func(allowauth_func_t func)
1594 {
1595     if (allowauth_func_used == allowauth_func_size) {
1596         if (allowauth_func_size) {
1597             allowauth_func_size <<= 1;
1598             allowauth_func_list = realloc(allowauth_func_list, allowauth_func_size*sizeof(allowauth_func_t));
1599         } else {
1600             allowauth_func_size = 8;
1601             allowauth_func_list = malloc(allowauth_func_size*sizeof(allowauth_func_t));
1602         }
1603     }
1604     allowauth_func_list[allowauth_func_used++] = func;
1605 }
1606
1607 static NICKSERV_FUNC(cmd_allowauth)
1608 {
1609     struct userNode *target;
1610     struct handle_info *hi;
1611     unsigned int n;
1612
1613     NICKSERV_MIN_PARMS(2);
1614     if (!(target = GetUserH(argv[1]))) {
1615         reply("MSG_NICK_UNKNOWN", argv[1]);
1616         return 0;
1617     }
1618     if (target->handle_info) {
1619         reply("NSMSG_USER_PREV_AUTH", target->nick);
1620         return 0;
1621     }
1622     if (IsStamped(target)) {
1623         /* Unauthenticated users might still have been stamped
1624            previously and could therefore have a hidden host;
1625            do not allow them to authenticate to an account. */
1626         send_message(target, nickserv, "NSMSG_USER_PREV_STAMP", target->nick);
1627         return 0;
1628     }
1629     if (argc == 2)
1630         hi = NULL;
1631     else if (!(hi = get_handle_info(argv[2]))) {
1632         reply("MSG_HANDLE_UNKNOWN", argv[2]);
1633         return 0;
1634     }
1635     if (hi) {
1636         if (hi->opserv_level > user->handle_info->opserv_level) {
1637             reply("MSG_USER_OUTRANKED", hi->handle);
1638             return 0;
1639         }
1640         if (((hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER))
1641              || (hi->opserv_level > 0))
1642             && ((argc < 4) || irccasecmp(argv[3], "staff"))) {
1643             reply("NSMSG_ALLOWAUTH_STAFF", hi->handle);
1644             return 0;
1645         }
1646         dict_insert(nickserv_allow_auth_dict, target->nick, hi);
1647         reply("NSMSG_AUTH_ALLOWED", target->nick, hi->handle);
1648         send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_MSG", hi->handle, hi->handle);
1649         if (nickserv_conf.email_enabled)
1650             send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_EMAIL");
1651     } else {
1652         if (dict_remove(nickserv_allow_auth_dict, target->nick))
1653             reply("NSMSG_AUTH_NORMAL_ONLY", target->nick);
1654         else
1655             reply("NSMSG_AUTH_UNSPECIAL", target->nick);
1656     }
1657     for (n=0; n<allowauth_func_used; n++)
1658         allowauth_func_list[n](user, target, hi);
1659     return 1;
1660 }
1661
1662 static NICKSERV_FUNC(cmd_authcookie)
1663 {
1664     struct handle_info *hi;
1665
1666     NICKSERV_MIN_PARMS(2);
1667     if (user->handle_info) {
1668         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1669         return 0;
1670     }
1671     if (IsStamped(user)) {
1672         /* Unauthenticated users might still have been stamped
1673            previously and could therefore have a hidden host;
1674            do not allow them to authenticate to an account. */
1675         reply("NSMSG_STAMPED_AUTHCOOKIE");
1676         return 0;
1677     }
1678     if (!(hi = get_handle_info(argv[1]))) {
1679         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1680         return 0;
1681     }
1682     if (!hi->email_addr) {
1683         reply("MSG_SET_EMAIL_ADDR");
1684         return 0;
1685     }
1686     nickserv_make_cookie(user, hi, ALLOWAUTH, NULL);
1687     return 1;
1688 }
1689
1690 static NICKSERV_FUNC(cmd_delcookie)
1691 {
1692     struct handle_info *hi;
1693
1694     hi = user->handle_info;
1695     if (!hi->cookie) {
1696         reply("NSMSG_NO_COOKIE");
1697         return 0;
1698     }
1699     switch (hi->cookie->type) {
1700     case ACTIVATION:
1701     case EMAIL_CHANGE:
1702         reply("NSMSG_MUST_TIME_OUT");
1703         break;
1704     default:
1705         nickserv_eat_cookie(hi->cookie);
1706         reply("NSMSG_ATE_COOKIE");
1707         break;
1708     }
1709     return 1;
1710 }
1711
1712 static NICKSERV_FUNC(cmd_resetpass)
1713 {
1714     struct handle_info *hi;
1715     char crypted[MD5_CRYPT_LENGTH];
1716
1717     NICKSERV_MIN_PARMS(3);
1718     if (user->handle_info) {
1719         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1720         return 0;
1721     }
1722     if (IsStamped(user)) {
1723         /* Unauthenticated users might still have been stamped
1724            previously and could therefore have a hidden host;
1725            do not allow them to activate an account. */
1726         reply("NSMSG_STAMPED_RESETPASS");
1727         return 0;
1728     }
1729     if (!(hi = get_handle_info(argv[1]))) {
1730         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1731         return 0;
1732     }
1733     if (!hi->email_addr) {
1734         reply("MSG_SET_EMAIL_ADDR");
1735         return 0;
1736     }
1737     cryptpass(argv[2], crypted);
1738     argv[2] = "****";
1739     nickserv_make_cookie(user, hi, PASSWORD_CHANGE, crypted);
1740     return 1;
1741 }
1742
1743 static NICKSERV_FUNC(cmd_cookie)
1744 {
1745     struct handle_info *hi;
1746     const char *cookie;
1747
1748     if ((argc == 2) && (hi = user->handle_info) && hi->cookie && (hi->cookie->type == EMAIL_CHANGE)) {
1749         cookie = argv[1];
1750     } else {
1751         NICKSERV_MIN_PARMS(3);
1752         if (!(hi = get_handle_info(argv[1]))) {
1753             reply("MSG_HANDLE_UNKNOWN", argv[1]);
1754             return 0;
1755         }
1756         cookie = argv[2];
1757     }
1758
1759     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1760         reply("NSMSG_HANDLE_SUSPENDED");
1761         return 0;
1762     }
1763
1764     if (!hi->cookie) {
1765         reply("NSMSG_NO_COOKIE");
1766         return 0;
1767     }
1768
1769     /* Check validity of operation before comparing cookie to
1770      * prohibit guessing by authed users. */
1771     if (user->handle_info
1772         && (hi->cookie->type != EMAIL_CHANGE)
1773         && (hi->cookie->type != PASSWORD_CHANGE)) {
1774         reply("NSMSG_CANNOT_COOKIE");
1775         return 0;
1776     }
1777
1778     if (strcmp(cookie, hi->cookie->cookie)) {
1779         reply("NSMSG_BAD_COOKIE");
1780         return 0;
1781     }
1782
1783     switch (hi->cookie->type) {
1784     case ACTIVATION:
1785         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1786         set_user_handle_info(user, hi, 1);
1787         reply("NSMSG_HANDLE_ACTIVATED");
1788         break;
1789     case PASSWORD_CHANGE:
1790         set_user_handle_info(user, hi, 1);
1791         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1792         reply("NSMSG_PASSWORD_CHANGED");
1793         break;
1794     case EMAIL_CHANGE:
1795         nickserv_set_email_addr(hi, hi->cookie->data);
1796         reply("NSMSG_EMAIL_CHANGED");
1797         break;
1798     case ALLOWAUTH:
1799         set_user_handle_info(user, hi, 1);
1800         reply("NSMSG_AUTH_SUCCESS");
1801         break;
1802     default:
1803         reply("NSMSG_BAD_COOKIE_TYPE", hi->cookie->type);
1804         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d for account %s.", hi->cookie->type, hi->handle);
1805         break;
1806     }
1807
1808     nickserv_eat_cookie(hi->cookie);
1809
1810     return 1;
1811 }
1812
1813 static NICKSERV_FUNC(cmd_oregnick) {
1814     const char *nick;
1815     struct handle_info *target;
1816     struct nick_info *ni;
1817
1818     NICKSERV_MIN_PARMS(3);
1819     if (!(target = modcmd_get_handle_info(user, argv[1])))
1820         return 0;
1821     nick = argv[2];
1822     if (!is_registerable_nick(nick)) {
1823         reply("NSMSG_BAD_NICK", nick);
1824         return 0;
1825     }
1826     ni = dict_find(nickserv_nick_dict, nick, NULL);
1827     if (ni) {
1828         reply("NSMSG_NICK_EXISTS", nick);
1829         return 0;
1830     }
1831     register_nick(nick, target);
1832     reply("NSMSG_OREGNICK_SUCCESS", nick, target->handle);
1833     return 1;
1834 }
1835
1836 static NICKSERV_FUNC(cmd_regnick) {
1837     unsigned n;
1838     struct nick_info *ni;
1839
1840     if (!is_registerable_nick(user->nick)) {
1841         reply("NSMSG_BAD_NICK", user->nick);
1842         return 0;
1843     }
1844     /* count their nicks, see if it's too many */
1845     for (n=0,ni=user->handle_info->nicks; ni; n++,ni=ni->next) ;
1846     if (n >= nickserv_conf.nicks_per_handle) {
1847         reply("NSMSG_TOO_MANY_NICKS");
1848         return 0;
1849     }
1850     ni = dict_find(nickserv_nick_dict, user->nick, NULL);
1851     if (ni) {
1852         reply("NSMSG_NICK_EXISTS", user->nick);
1853         return 0;
1854     }
1855     register_nick(user->nick, user->handle_info);
1856     reply("NSMSG_REGNICK_SUCCESS", user->nick);
1857     return 1;
1858 }
1859
1860 static NICKSERV_FUNC(cmd_pass)
1861 {
1862     struct handle_info *hi;
1863     const char *old_pass, *new_pass;
1864
1865     NICKSERV_MIN_PARMS(3);
1866     hi = user->handle_info;
1867     old_pass = argv[1];
1868     new_pass = argv[2];
1869     argv[2] = "****";
1870     if (!is_secure_password(hi->handle, new_pass, user)) return 0;
1871     if (!checkpass(old_pass, hi->passwd)) {
1872         argv[1] = "BADPASS";
1873         reply("NSMSG_PASSWORD_INVALID");
1874         return 0;
1875     }
1876     cryptpass(new_pass, hi->passwd);
1877     argv[1] = "****";
1878     reply("NSMSG_PASS_SUCCESS");
1879     return 1;
1880 }
1881
1882 static int
1883 nickserv_addmask(struct userNode *user, struct handle_info *hi, const char *mask)
1884 {
1885     unsigned int i;
1886     char *new_mask = canonicalize_hostmask(strdup(mask));
1887     for (i=0; i<hi->masks->used; i++) {
1888         if (!irccasecmp(new_mask, hi->masks->list[i])) {
1889             send_message(user, nickserv, "NSMSG_ADDMASK_ALREADY", new_mask);
1890             free(new_mask);
1891             return 0;
1892         }
1893     }
1894     string_list_append(hi->masks, new_mask);
1895     send_message(user, nickserv, "NSMSG_ADDMASK_SUCCESS", new_mask);
1896     return 1;
1897 }
1898
1899 static NICKSERV_FUNC(cmd_addmask)
1900 {
1901     if (argc < 2) {
1902         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
1903         int res = nickserv_addmask(user, user->handle_info, mask);
1904         free(mask);
1905         return res;
1906     } else {
1907         if (!is_gline(argv[1])) {
1908             reply("NSMSG_MASK_INVALID", argv[1]);
1909             return 0;
1910         }
1911         return nickserv_addmask(user, user->handle_info, argv[1]);
1912     }
1913 }
1914
1915 static NICKSERV_FUNC(cmd_oaddmask)
1916 {
1917     struct handle_info *hi;
1918
1919     NICKSERV_MIN_PARMS(3);
1920     if (!(hi = get_victim_oper(user, argv[1])))
1921         return 0;
1922     return nickserv_addmask(user, hi, argv[2]);
1923 }
1924
1925 static int
1926 nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask)
1927 {
1928     unsigned int i;
1929     for (i=0; i<hi->masks->used; i++) {
1930         if (!strcmp(del_mask, hi->masks->list[i])) {
1931             char *old_mask = hi->masks->list[i];
1932             if (hi->masks->used == 1) {
1933                 send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
1934                 return 0;
1935             }
1936             hi->masks->list[i] = hi->masks->list[--hi->masks->used];
1937             send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
1938             free(old_mask);
1939             return 1;
1940         }
1941     }
1942     send_message(user, nickserv, "NSMSG_DELMASK_NOT_FOUND");
1943     return 0;
1944 }
1945
1946 static NICKSERV_FUNC(cmd_delmask)
1947 {
1948     NICKSERV_MIN_PARMS(2);
1949     return nickserv_delmask(user, user->handle_info, argv[1]);
1950 }
1951
1952 static NICKSERV_FUNC(cmd_odelmask)
1953 {
1954     struct handle_info *hi;
1955     NICKSERV_MIN_PARMS(3);
1956     if (!(hi = get_victim_oper(user, argv[1])))
1957         return 0;
1958     return nickserv_delmask(user, hi, argv[2]);
1959 }
1960
1961 int
1962 nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *padded, unsigned long *premoved) {
1963     unsigned int nn, add = 1, pos;
1964     unsigned long added, removed, flag;
1965
1966     for (added=removed=nn=0; str[nn]; nn++) {
1967         switch (str[nn]) {
1968         case '+': add = 1; break;
1969         case '-': add = 0; break;
1970         default:
1971             if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
1972                 send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
1973                 return 0;
1974             }
1975             if (user && (user->handle_info->opserv_level < flag_access_levels[pos-1])) {
1976                 /* cheesy avoidance of looking up the flag name.. */
1977                 send_message(user, bot, "NSMSG_FLAG_PRIVILEGED", str[nn]);
1978                 return 0;
1979             }
1980             flag = 1 << (pos - 1);
1981             if (add)
1982                 added |= flag, removed &= ~flag;
1983             else
1984                 removed |= flag, added &= ~flag;
1985             break;
1986         }
1987     }
1988     *padded = added;
1989     *premoved = removed;
1990     return 1;
1991 }
1992
1993 static int
1994 nickserv_apply_flags(struct userNode *user, struct handle_info *hi, const char *flags)
1995 {
1996     unsigned long before, after, added, removed;
1997     struct userNode *uNode;
1998
1999     before = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2000     if (!nickserv_modify_handle_flags(user, nickserv, flags, &added, &removed))
2001         return 0;
2002     hi->flags = (hi->flags | added) & ~removed;
2003     after = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2004
2005     /* Strip helping flag if they're only a support helper and not
2006      * currently in #support. */
2007     if (HANDLE_FLAGGED(hi, HELPING) && (after == HI_FLAG_SUPPORT_HELPER)) {
2008         struct channelList *schannels;
2009         unsigned int ii;
2010         schannels = chanserv_support_channels();
2011         for (uNode = hi->users; uNode; uNode = uNode->next_authed) {
2012             for (ii = 0; ii < schannels->used; ++ii)
2013                 if (GetUserMode(schannels->list[ii], uNode))
2014                     break;
2015             if (ii < schannels->used)
2016                 break;
2017         }
2018         if (!uNode)
2019             HANDLE_CLEAR_FLAG(hi, HELPING);
2020     }
2021
2022     if (after && !before) {
2023         /* Add user to current helper list. */
2024         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2025             userList_append(&curr_helpers, uNode);
2026     } else if (!after && before) {
2027         /* Remove user from current helper list. */
2028         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2029             userList_remove(&curr_helpers, uNode);
2030     }
2031
2032     return 1;
2033 }
2034
2035 static void
2036 set_list(struct userNode *user, struct handle_info *hi, int override)
2037 {
2038     option_func_t *opt;
2039     unsigned int i;
2040     char *set_display[] = {
2041         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
2042         "EMAIL", "ANNOUNCEMENTS", "MAXLOGINS", "LANGUAGE"
2043     };
2044
2045     send_message(user, nickserv, "NSMSG_SETTING_LIST");
2046
2047     /* Do this so options are presented in a consistent order. */
2048     for (i = 0; i < ArrayLength(set_display); ++i)
2049         if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
2050             opt(user, hi, override, 0, NULL);
2051 }
2052
2053 static NICKSERV_FUNC(cmd_set)
2054 {
2055     struct handle_info *hi;
2056     option_func_t *opt;
2057
2058     hi = user->handle_info;
2059     if (argc < 2) {
2060         set_list(user, hi, 0);
2061         return 1;
2062     }
2063     if (!(opt = dict_find(nickserv_opt_dict, argv[1], NULL))) {
2064         reply("NSMSG_INVALID_OPTION", argv[1]);
2065         return 0;
2066     }
2067     return opt(user, hi, 0, argc-1, argv+1);
2068 }
2069
2070 static NICKSERV_FUNC(cmd_oset)
2071 {
2072     struct handle_info *hi;
2073     option_func_t *opt;
2074
2075     NICKSERV_MIN_PARMS(2);
2076
2077     if (!(hi = get_victim_oper(user, argv[1])))
2078         return 0;
2079
2080     if (argc < 3) {
2081         set_list(user, hi, 0);
2082         return 1;
2083     }
2084
2085     if (!(opt = dict_find(nickserv_opt_dict, argv[2], NULL))) {
2086         reply("NSMSG_INVALID_OPTION", argv[2]);
2087         return 0;
2088     }
2089
2090     return opt(user, hi, 1, argc-2, argv+2);
2091 }
2092
2093 static OPTION_FUNC(opt_info)
2094 {
2095     const char *info;
2096     if (argc > 1) {
2097         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2098             free(hi->infoline);
2099             hi->infoline = NULL;
2100         } else {
2101             hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
2102         }
2103     }
2104
2105     info = hi->infoline ? hi->infoline : user_find_message(user, "MSG_NONE");
2106     send_message(user, nickserv, "NSMSG_SET_INFO", info);
2107     return 1;
2108 }
2109
2110 static OPTION_FUNC(opt_width)
2111 {
2112     if (argc > 1)
2113         hi->screen_width = strtoul(argv[1], NULL, 0);
2114
2115     if ((hi->screen_width > 0) && (hi->screen_width < MIN_LINE_SIZE))
2116         hi->screen_width = MIN_LINE_SIZE;
2117     else if (hi->screen_width > MAX_LINE_SIZE)
2118         hi->screen_width = MAX_LINE_SIZE;
2119
2120     send_message(user, nickserv, "NSMSG_SET_WIDTH", hi->screen_width);
2121     return 1;
2122 }
2123
2124 static OPTION_FUNC(opt_tablewidth)
2125 {
2126     if (argc > 1)
2127         hi->table_width = strtoul(argv[1], NULL, 0);
2128
2129     if ((hi->table_width > 0) && (hi->table_width < MIN_LINE_SIZE))
2130         hi->table_width = MIN_LINE_SIZE;
2131     else if (hi->screen_width > MAX_LINE_SIZE)
2132         hi->table_width = MAX_LINE_SIZE;
2133
2134     send_message(user, nickserv, "NSMSG_SET_TABLEWIDTH", hi->table_width);
2135     return 1;
2136 }
2137
2138 static OPTION_FUNC(opt_color)
2139 {
2140     if (argc > 1) {
2141         if (enabled_string(argv[1]))
2142             HANDLE_SET_FLAG(hi, MIRC_COLOR);
2143         else if (disabled_string(argv[1]))
2144             HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
2145         else {
2146             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2147             return 0;
2148         }
2149     }
2150
2151     send_message(user, nickserv, "NSMSG_SET_COLOR", user_find_message(user, HANDLE_FLAGGED(hi, MIRC_COLOR) ? "MSG_ON" : "MSG_OFF"));
2152     return 1;
2153 }
2154
2155 static OPTION_FUNC(opt_privmsg)
2156 {
2157     if (argc > 1) {
2158         if (enabled_string(argv[1]))
2159             HANDLE_SET_FLAG(hi, USE_PRIVMSG);
2160         else if (disabled_string(argv[1]))
2161             HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
2162         else {
2163             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2164             return 0;
2165         }
2166     }
2167
2168     send_message(user, nickserv, "NSMSG_SET_PRIVMSG", user_find_message(user, HANDLE_FLAGGED(hi, USE_PRIVMSG) ? "MSG_ON" : "MSG_OFF"));
2169     return 1;
2170 }
2171
2172 static OPTION_FUNC(opt_style)
2173 {
2174     char *style;
2175
2176     if (argc > 1) {
2177         if (!irccasecmp(argv[1], "Zoot"))
2178             hi->userlist_style = HI_STYLE_ZOOT;
2179         else if (!irccasecmp(argv[1], "def"))
2180             hi->userlist_style = HI_STYLE_DEF;
2181     }
2182
2183     switch (hi->userlist_style) {
2184     case HI_STYLE_DEF:
2185         style = "def";
2186         break;
2187     case HI_STYLE_ZOOT:
2188     default:
2189         style = "Zoot";
2190     }
2191
2192     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
2193     return 1;
2194 }
2195
2196 static OPTION_FUNC(opt_announcements)
2197 {
2198     const char *choice;
2199
2200     if (argc > 1) {
2201         if (enabled_string(argv[1]))
2202             hi->announcements = 'y';
2203         else if (disabled_string(argv[1]))
2204             hi->announcements = 'n';
2205         else if (!strcmp(argv[1], "?") || !irccasecmp(argv[1], "default"))
2206             hi->announcements = '?';
2207         else {
2208             send_message(user, nickserv, "NSMSG_INVALID_ANNOUNCE", argv[1]);
2209             return 0;
2210         }
2211     }
2212
2213     switch (hi->announcements) {
2214     case 'y': choice = user_find_message(user, "MSG_ON"); break;
2215     case 'n': choice = user_find_message(user, "MSG_OFF"); break;
2216     case '?': choice = "default"; break;
2217     default: choice = "unknown"; break;
2218     }
2219     send_message(user, nickserv, "NSMSG_SET_ANNOUNCEMENTS", choice);
2220     return 1;
2221 }
2222
2223 static OPTION_FUNC(opt_password)
2224 {
2225     if (!override) {
2226         send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
2227         return 0;
2228     }
2229
2230     if (argc > 1)
2231         cryptpass(argv[1], hi->passwd);
2232
2233     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
2234     return 1;
2235 }
2236
2237 static OPTION_FUNC(opt_flags)
2238 {
2239     char flags[33];
2240     unsigned int ii, flen;
2241
2242     if (!override) {
2243         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2244         return 0;
2245     }
2246
2247     if (argc > 1)
2248         nickserv_apply_flags(user, hi, argv[1]);
2249
2250     for (ii = flen = 0; handle_flags[ii]; ii++)
2251         if (hi->flags & (1 << ii))
2252             flags[flen++] = handle_flags[ii];
2253     flags[flen] = '\0';
2254     if (hi->flags)
2255         send_message(user, nickserv, "NSMSG_SET_FLAGS", flags);
2256     else
2257         send_message(user, nickserv, "NSMSG_SET_FLAGS", user_find_message(user, "MSG_NONE"));
2258     return 1;
2259 }
2260
2261 static OPTION_FUNC(opt_email)
2262 {
2263     if (argc > 1) {
2264         const char *str;
2265         if (!is_valid_email_addr(argv[1])) {
2266             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
2267             return 0;
2268         }
2269         if ((str = sendmail_prohibited_address(argv[1]))) {
2270             send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", argv[1], str);
2271             return 0;
2272         }
2273         if (hi->email_addr && !irccasecmp(hi->email_addr, argv[1]))
2274             send_message(user, nickserv, "NSMSG_EMAIL_SAME");
2275         else if (!override)
2276                 nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1]);
2277         else {
2278             nickserv_set_email_addr(hi, argv[1]);
2279             if (hi->cookie)
2280                 nickserv_eat_cookie(hi->cookie);
2281             send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2282         }
2283     } else
2284         send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2285     return 1;
2286 }
2287
2288 static OPTION_FUNC(opt_maxlogins)
2289 {
2290     unsigned char maxlogins;
2291     if (argc > 1) {
2292         maxlogins = strtoul(argv[1], NULL, 0);
2293         if ((maxlogins > nickserv_conf.hard_maxlogins) && !override) {
2294             send_message(user, nickserv, "NSMSG_BAD_MAX_LOGINS", nickserv_conf.hard_maxlogins);
2295             return 0;
2296         }
2297         hi->maxlogins = maxlogins;
2298     }
2299     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
2300     send_message(user, nickserv, "NSMSG_SET_MAXLOGINS", maxlogins);
2301     return 1;
2302 }
2303
2304 static OPTION_FUNC(opt_language)
2305 {
2306     struct language *lang;
2307     if (argc > 1) {
2308         lang = language_find(argv[1]);
2309         if (irccasecmp(lang->name, argv[1]))
2310             send_message(user, nickserv, "NSMSG_LANGUAGE_NOT_FOUND", argv[1], lang->name);
2311         hi->language = lang;
2312     }
2313     send_message(user, nickserv, "NSMSG_SET_LANGUAGE", hi->language->name);
2314     return 1;
2315 }
2316
2317 int
2318 oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
2319     if (!oper_has_access(user, bot, nickserv_conf.modoper_level, 0))
2320         return 0;
2321     if ((user->handle_info->opserv_level < target->opserv_level)
2322         || ((user->handle_info->opserv_level == target->opserv_level)
2323             && (user->handle_info->opserv_level < 1000))) {
2324         send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
2325         return 0;
2326     }
2327     if ((user->handle_info->opserv_level < new_level)
2328         || ((user->handle_info->opserv_level == new_level)
2329             && (user->handle_info->opserv_level < 1000))) {
2330         send_message(user, bot, "NSMSG_OPSERV_LEVEL_BAD");
2331         return 0;
2332     }
2333     if (user->handle_info == target) {
2334         send_message(user, bot, "MSG_STUPID_ACCESS_CHANGE");
2335         return 0;
2336     }
2337     if (target->opserv_level == new_level)
2338         return 0;
2339     log_module(NS_LOG, LOG_INFO, "Account %s setting oper level for account %s to %d (from %d).",
2340         user->handle_info->handle, target->handle, new_level, target->opserv_level);
2341     target->opserv_level = new_level;
2342     return 1;
2343 }
2344
2345 static OPTION_FUNC(opt_level)
2346 {
2347     int res;
2348
2349     if (!override) {
2350         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2351         return 0;
2352     }
2353
2354     res = (argc > 1) ? oper_try_set_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
2355     send_message(user, nickserv, "NSMSG_SET_LEVEL", hi->opserv_level);
2356     return res;
2357 }
2358
2359 static OPTION_FUNC(opt_epithet)
2360 {
2361     if (!override) {
2362         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2363         return 0;
2364     }
2365
2366     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_epithet_level, 0)) {
2367         char *epithet = unsplit_string(argv+1, argc-1, NULL);
2368         if (hi->epithet)
2369             free(hi->epithet);
2370         if ((epithet[0] == '*') && !epithet[1])
2371             hi->epithet = NULL;
2372         else
2373             hi->epithet = strdup(epithet);
2374     }
2375
2376     if (hi->epithet)
2377         send_message(user, nickserv, "NSMSG_SET_EPITHET", hi->epithet);
2378     else
2379         send_message(user, nickserv, "NSMSG_SET_EPITHET", user_find_message(user, "MSG_NONE"));
2380     return 1;
2381 }
2382
2383 static NICKSERV_FUNC(cmd_reclaim)
2384 {
2385     struct handle_info *hi;
2386     struct nick_info *ni;
2387     struct userNode *victim;
2388
2389     NICKSERV_MIN_PARMS(2);
2390     hi = user->handle_info;
2391     ni = dict_find(nickserv_nick_dict, argv[1], 0);
2392     if (!ni) {
2393         reply("NSMSG_UNKNOWN_NICK", argv[1]);
2394         return 0;
2395     }
2396     if (ni->owner != user->handle_info) {
2397         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
2398         return 0;
2399     }
2400     victim = GetUserH(ni->nick);
2401     if (!victim) {
2402         reply("MSG_NICK_UNKNOWN", ni->nick);
2403         return 0;
2404     }
2405     if (victim == user) {
2406         reply("NSMSG_NICK_USER_YOU");
2407         return 0;
2408     }
2409     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
2410     switch (nickserv_conf.reclaim_action) {
2411     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
2412     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
2413     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
2414     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
2415     }
2416     return 1;
2417 }
2418
2419 static NICKSERV_FUNC(cmd_unregnick)
2420 {
2421     const char *nick;
2422     struct handle_info *hi;
2423     struct nick_info *ni;
2424
2425     hi = user->handle_info;
2426     nick = (argc < 2) ? user->nick : (const char*)argv[1];
2427     ni = dict_find(nickserv_nick_dict, nick, NULL);
2428     if (!ni) {
2429         reply("NSMSG_UNKNOWN_NICK", nick);
2430         return 0;
2431     }
2432     if (hi != ni->owner) {
2433         reply("NSMSG_NOT_YOUR_NICK", nick);
2434         return 0;
2435     }
2436     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2437     delete_nick(ni);
2438     return 1;
2439 }
2440
2441 static NICKSERV_FUNC(cmd_ounregnick)
2442 {
2443     struct nick_info *ni;
2444
2445     NICKSERV_MIN_PARMS(2);
2446     if (!(ni = get_nick_info(argv[1]))) {
2447         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
2448         return 0;
2449     }
2450     if (ni->owner->opserv_level >= user->handle_info->opserv_level) {
2451         reply("MSG_USER_OUTRANKED", ni->nick);
2452         return 0;
2453     }
2454     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2455     delete_nick(ni);
2456     return 1;
2457 }
2458
2459 static NICKSERV_FUNC(cmd_unregister)
2460 {
2461     struct handle_info *hi;
2462     char *passwd;
2463
2464     NICKSERV_MIN_PARMS(2);
2465     hi = user->handle_info;
2466     passwd = argv[1];
2467     argv[1] = "****";
2468     if (checkpass(passwd, hi->passwd)) {
2469         nickserv_unregister_handle(hi, user);
2470         return 1;
2471     } else {
2472         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
2473         reply("NSMSG_PASSWORD_INVALID");
2474         return 0;
2475     }
2476 }
2477
2478 static NICKSERV_FUNC(cmd_ounregister)
2479 {
2480     struct handle_info *hi;
2481
2482     NICKSERV_MIN_PARMS(2);
2483     if (!(hi = get_victim_oper(user, argv[1])))
2484         return 0;
2485     nickserv_unregister_handle(hi, user);
2486     return 0;
2487 }
2488
2489 static NICKSERV_FUNC(cmd_status)
2490 {
2491     if (nickserv_conf.disable_nicks) {
2492         reply("NSMSG_GLOBAL_STATS_NONICK",
2493                         dict_size(nickserv_handle_dict));
2494     } else {
2495         if (user->handle_info) {
2496             int cnt=0;
2497             struct nick_info *ni;
2498             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
2499             reply("NSMSG_HANDLE_STATS", cnt);
2500         } else {
2501             reply("NSMSG_HANDLE_NONE");
2502         }
2503         reply("NSMSG_GLOBAL_STATS",
2504               dict_size(nickserv_handle_dict),
2505               dict_size(nickserv_nick_dict));
2506     }
2507     return 1;
2508 }
2509
2510 static NICKSERV_FUNC(cmd_ghost)
2511 {
2512     struct userNode *target;
2513     char reason[MAXLEN];
2514
2515     NICKSERV_MIN_PARMS(2);
2516     if (!(target = GetUserH(argv[1]))) {
2517         reply("MSG_NICK_UNKNOWN", argv[1]);
2518         return 0;
2519     }
2520     if (target == user) {
2521         reply("NSMSG_CANNOT_GHOST_SELF");
2522         return 0;
2523     }
2524     if (!target->handle_info || (target->handle_info != user->handle_info)) {
2525         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
2526         return 0;
2527     }
2528     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
2529     DelUser(target, nickserv, 1, reason);
2530     reply("NSMSG_GHOST_KILLED", argv[1]);
2531     return 1;
2532 }
2533
2534 static NICKSERV_FUNC(cmd_vacation)
2535 {
2536     HANDLE_SET_FLAG(user->handle_info, FROZEN);
2537     reply("NSMSG_ON_VACATION");
2538     return 1;
2539 }
2540
2541 static int
2542 nickserv_saxdb_write(struct saxdb_context *ctx) {
2543     dict_iterator_t it;
2544     struct handle_info *hi;
2545     char flags[33];
2546
2547     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2548         hi = iter_data(it);
2549 #ifdef WITH_PROTOCOL_BAHAMUT
2550         assert(hi->id);
2551 #endif
2552         saxdb_start_record(ctx, iter_key(it), 0);
2553         if (hi->announcements != '?') {
2554             flags[0] = hi->announcements;
2555             flags[1] = 0;
2556             saxdb_write_string(ctx, KEY_ANNOUNCEMENTS, flags);
2557         }
2558         if (hi->cookie) {
2559             struct handle_cookie *cookie = hi->cookie;
2560             char *type;
2561
2562             switch (cookie->type) {
2563             case ACTIVATION: type = KEY_ACTIVATION; break;
2564             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
2565             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
2566             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
2567             default: type = NULL; break;
2568             }
2569             if (type) {
2570                 saxdb_start_record(ctx, KEY_COOKIE, 0);
2571                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
2572                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
2573                 if (cookie->data)
2574                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
2575                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
2576                 saxdb_end_record(ctx);
2577             }
2578         }
2579         if (hi->email_addr)
2580             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
2581         if (hi->epithet)
2582             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
2583         if (hi->flags) {
2584             int ii, flen;
2585
2586             for (ii=flen=0; handle_flags[ii]; ++ii)
2587                 if (hi->flags & (1 << ii))
2588                     flags[flen++] = handle_flags[ii];
2589             flags[flen] = 0;
2590             saxdb_write_string(ctx, KEY_FLAGS, flags);
2591         }
2592 #ifdef WITH_PROTOCOL_BAHAMUT
2593         saxdb_write_int(ctx, KEY_ID, hi->id);
2594 #endif
2595         if (hi->infoline)
2596             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
2597         if (hi->last_quit_host[0])
2598             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
2599         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
2600         if (hi->masks->used)
2601             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
2602         if (hi->maxlogins)
2603             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
2604         if (hi->nicks) {
2605             struct string_list *slist;
2606             struct nick_info *ni;
2607
2608             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
2609             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
2610             saxdb_write_string_list(ctx, KEY_NICKS, slist);
2611             free(slist->list);
2612             free(slist);
2613         }
2614         if (hi->opserv_level)
2615             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
2616         if (hi->language != lang_C)
2617             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
2618         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
2619         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
2620         if (hi->screen_width)
2621             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
2622         if (hi->table_width)
2623             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
2624         flags[0] = hi->userlist_style;
2625         flags[1] = 0;
2626         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
2627         saxdb_end_record(ctx);
2628     }
2629     return 0;
2630 }
2631
2632 static handle_merge_func_t *handle_merge_func_list;
2633 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
2634
2635 void
2636 reg_handle_merge_func(handle_merge_func_t func)
2637 {
2638     if (handle_merge_func_used == handle_merge_func_size) {
2639         if (handle_merge_func_size) {
2640             handle_merge_func_size <<= 1;
2641             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
2642         } else {
2643             handle_merge_func_size = 8;
2644             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
2645         }
2646     }
2647     handle_merge_func_list[handle_merge_func_used++] = func;
2648 }
2649
2650 static NICKSERV_FUNC(cmd_merge)
2651 {
2652     struct handle_info *hi_from, *hi_to;
2653     struct userNode *last_user;
2654     struct userData *cList, *cListNext;
2655     unsigned int ii, jj, n;
2656     char buffer[MAXLEN];
2657
2658     NICKSERV_MIN_PARMS(3);
2659
2660     if (!(hi_from = get_victim_oper(user, argv[1])))
2661         return 0;
2662     if (!(hi_to = get_victim_oper(user, argv[2])))
2663         return 0;
2664     if (hi_to == hi_from) {
2665         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
2666         return 0;
2667     }
2668
2669     for (n=0; n<handle_merge_func_used; n++)
2670         handle_merge_func_list[n](user, hi_to, hi_from);
2671
2672     /* Append "from" handle's nicks to "to" handle's nick list. */
2673     if (hi_to->nicks) {
2674         struct nick_info *last_ni;
2675         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
2676         last_ni->next = hi_from->nicks;
2677     }
2678     while (hi_from->nicks) {
2679         hi_from->nicks->owner = hi_to;
2680         hi_from->nicks = hi_from->nicks->next;
2681     }
2682
2683     /* Merge the hostmasks. */
2684     for (ii=0; ii<hi_from->masks->used; ii++) {
2685         char *mask = hi_from->masks->list[ii];
2686         for (jj=0; jj<hi_to->masks->used; jj++)
2687             if (match_ircglobs(hi_to->masks->list[jj], mask))
2688                 break;
2689         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
2690             string_list_append(hi_to->masks, strdup(mask));
2691     }
2692
2693     /* Merge the lists of authed users. */
2694     if (hi_to->users) {
2695         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
2696         last_user->next_authed = hi_from->users;
2697     } else {
2698         hi_to->users = hi_from->users;
2699     }
2700     /* Repoint the old "from" handle's users. */
2701     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
2702         last_user->handle_info = hi_to;
2703     }
2704     hi_from->users = NULL;
2705
2706     /* Merge channel userlists. */
2707     for (cList=hi_from->channels; cList; cList=cListNext) {
2708         struct userData *cList2;
2709         cListNext = cList->u_next;
2710         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
2711             if (cList->channel == cList2->channel)
2712                 break;
2713         if (cList2 && (cList2->access >= cList->access)) {
2714             log_module(NS_LOG, LOG_INFO, "Merge: %s had only %d access in %s (versus %d for %s)", hi_from->handle, cList->access, cList->channel->channel->name, cList2->access, hi_to->handle);
2715             /* keep cList2 in hi_to; remove cList from hi_from */
2716             del_channel_user(cList, 1);
2717         } else {
2718             if (cList2) {
2719                 log_module(NS_LOG, LOG_INFO, "Merge: %s had only %d access in %s (versus %d for %s)", hi_to->handle, cList2->access, cList->channel->channel->name, cList->access, hi_from->handle);
2720                 /* remove the lower-ranking cList2 from hi_to */
2721                 del_channel_user(cList2, 1);
2722             } else {
2723                 log_module(NS_LOG, LOG_INFO, "Merge: %s had no access in %s", hi_to->handle, cList->channel->channel->name);
2724             }
2725             /* cList needs to be moved from hi_from to hi_to */
2726             cList->handle = hi_to;
2727             /* Remove from linked list for hi_from */
2728             assert(!cList->u_prev);
2729             hi_from->channels = cList->u_next;
2730             if (cList->u_next)
2731                 cList->u_next->u_prev = cList->u_prev;
2732             /* Add to linked list for hi_to */
2733             cList->u_prev = NULL;
2734             cList->u_next = hi_to->channels;
2735             if (hi_to->channels)
2736                 hi_to->channels->u_prev = cList;
2737             hi_to->channels = cList;
2738         }
2739     }
2740
2741     /* Do they get an OpServ level promotion? */
2742     if (hi_from->opserv_level > hi_to->opserv_level)
2743         hi_to->opserv_level = hi_from->opserv_level;
2744
2745     /* What about last seen time? */
2746     if (hi_from->lastseen > hi_to->lastseen)
2747         hi_to->lastseen = hi_from->lastseen;
2748
2749     /* Notify of success. */
2750     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
2751     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
2752     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
2753
2754     /* Unregister the "from" handle. */
2755     nickserv_unregister_handle(hi_from, NULL);
2756
2757     return 1;
2758 }
2759
2760 struct nickserv_discrim {
2761     unsigned int limit, min_level, max_level;
2762     unsigned long flags_on, flags_off;
2763     time_t min_registered, max_registered;
2764     time_t lastseen;
2765     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
2766     const char *nickmask;
2767     const char *hostmask;
2768     const char *handlemask;
2769     const char *emailmask;
2770 };
2771
2772 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
2773
2774 struct discrim_apply_info {
2775     struct nickserv_discrim *discrim;
2776     discrim_search_func func;
2777     struct userNode *source;
2778     unsigned int matched;
2779 };
2780
2781 static struct nickserv_discrim *
2782 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
2783 {
2784     unsigned int i;
2785     struct nickserv_discrim *discrim;
2786
2787     discrim = malloc(sizeof(*discrim));
2788     memset(discrim, 0, sizeof(*discrim));
2789     discrim->min_level = 0;
2790     discrim->max_level = ~0;
2791     discrim->limit = 50;
2792     discrim->min_registered = 0;
2793     discrim->max_registered = INT_MAX;
2794     discrim->lastseen = now;
2795
2796     for (i=0; i<argc; i++) {
2797         if (i == argc - 1) {
2798             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2799             goto fail;
2800         }
2801         if (!irccasecmp(argv[i], "limit")) {
2802             discrim->limit = strtoul(argv[++i], NULL, 0);
2803         } else if (!irccasecmp(argv[i], "flags")) {
2804             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
2805         } else if (!irccasecmp(argv[i], "registered")) {
2806             const char *cmp = argv[++i];
2807             if (cmp[0] == '<') {
2808                 if (cmp[1] == '=') {
2809                     discrim->min_registered = now - ParseInterval(cmp+2);
2810                 } else {
2811                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
2812                 }
2813             } else if (cmp[0] == '=') {
2814                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
2815             } else if (cmp[0] == '>') {
2816                 if (cmp[1] == '=') {
2817                     discrim->max_registered = now - ParseInterval(cmp+2);
2818                 } else {
2819                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
2820                 }
2821             } else {
2822                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
2823             }
2824         } else if (!irccasecmp(argv[i], "seen")) {
2825             discrim->lastseen = now - ParseInterval(argv[++i]);
2826         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
2827             discrim->nickmask = argv[++i];
2828         } else if (!irccasecmp(argv[i], "hostmask")) {
2829             i++;
2830             if (!irccasecmp(argv[i], "exact")) {
2831                 if (i == argc - 1) {
2832                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2833                     goto fail;
2834                 }
2835                 discrim->hostmask_type = EXACT;
2836             } else if (!irccasecmp(argv[i], "subset")) {
2837                 if (i == argc - 1) {
2838                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2839                     goto fail;
2840                 }
2841                 discrim->hostmask_type = SUBSET;
2842             } else if (!irccasecmp(argv[i], "superset")) {
2843                 if (i == argc - 1) {
2844                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2845                     goto fail;
2846                 }
2847                 discrim->hostmask_type = SUPERSET;
2848             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
2849                if (i == argc - 1) {
2850                    send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2851                    goto fail;
2852                }
2853                discrim->hostmask_type = LASTQUIT;
2854             } else {
2855                 i--;
2856                 discrim->hostmask_type = SUPERSET;
2857             }
2858             discrim->hostmask = argv[++i];
2859         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
2860             if (!irccasecmp(argv[++i], "*")) {
2861                 discrim->handlemask = 0;
2862             } else {
2863                 discrim->handlemask = argv[i];
2864             }
2865         } else if (!irccasecmp(argv[i], "email")) {
2866             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
2867                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
2868                 goto fail;
2869             } else if (!irccasecmp(argv[++i], "*")) {
2870                 discrim->emailmask = 0;
2871             } else {
2872                 discrim->emailmask = argv[i];
2873             }
2874         } else if (!irccasecmp(argv[i], "access")) {
2875             const char *cmp = argv[++i];
2876             if (cmp[0] == '<') {
2877                 if (discrim->min_level == 0) discrim->min_level = 1;
2878                 if (cmp[1] == '=') {
2879                     discrim->max_level = strtoul(cmp+2, NULL, 0);
2880                 } else {
2881                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
2882                 }
2883             } else if (cmp[0] == '=') {
2884                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
2885             } else if (cmp[0] == '>') {
2886                 if (cmp[1] == '=') {
2887                     discrim->min_level = strtoul(cmp+2, NULL, 0);
2888                 } else {
2889                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
2890                 }
2891             } else {
2892                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
2893             }
2894         } else {
2895             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
2896             goto fail;
2897         }
2898     }
2899     return discrim;
2900   fail:
2901     free(discrim);
2902     return NULL;
2903 }
2904
2905 static int
2906 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
2907 {
2908     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
2909         || (discrim->flags_off & hi->flags)
2910         || (discrim->min_registered > hi->registered)
2911         || (discrim->max_registered < hi->registered)
2912         || (discrim->lastseen < (hi->users?now:hi->lastseen))
2913         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
2914         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
2915         || (discrim->min_level > hi->opserv_level)
2916         || (discrim->max_level < hi->opserv_level)) {
2917         return 0;
2918     }
2919     if (discrim->hostmask) {
2920         unsigned int i;
2921         for (i=0; i<hi->masks->used; i++) {
2922             const char *mask = hi->masks->list[i];
2923             if ((discrim->hostmask_type == SUBSET)
2924                 && (match_ircglobs(discrim->hostmask, mask))) break;
2925             else if ((discrim->hostmask_type == EXACT)
2926                      && !irccasecmp(discrim->hostmask, mask)) break;
2927             else if ((discrim->hostmask_type == SUPERSET)
2928                      && (match_ircglobs(mask, discrim->hostmask))) break;
2929             else if ((discrim->hostmask_type == LASTQUIT)
2930                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
2931         }
2932         if (i==hi->masks->used) return 0;
2933     }
2934     if (discrim->nickmask) {
2935         struct nick_info *nick = hi->nicks;
2936         while (nick) {
2937             if (match_ircglob(nick->nick, discrim->nickmask)) break;
2938             nick = nick->next;
2939         }
2940         if (!nick) return 0;
2941     }
2942     return 1;
2943 }
2944
2945 static unsigned int
2946 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
2947 {
2948     dict_iterator_t it, next;
2949     unsigned int matched;
2950
2951     for (it = dict_first(nickserv_handle_dict), matched = 0;
2952          it && (matched < discrim->limit);
2953          it = next) {
2954         next = iter_next(it);
2955         if (nickserv_discrim_match(discrim, iter_data(it))) {
2956             dsf(source, iter_data(it));
2957             matched++;
2958         }
2959     }
2960     return matched;
2961 }
2962
2963 static void
2964 search_print_func(struct userNode *source, struct handle_info *match)
2965 {
2966     send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
2967 }
2968
2969 static void
2970 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
2971 {
2972 }
2973
2974 static void
2975 search_unregister_func (struct userNode *source, struct handle_info *match)
2976 {
2977     if (oper_has_access(source, nickserv, match->opserv_level, 0))
2978         nickserv_unregister_handle(match, source);
2979 }
2980
2981 static int
2982 nickserv_sort_accounts_by_access(const void *a, const void *b)
2983 {
2984     const struct handle_info *hi_a = *(const struct handle_info**)a;
2985     const struct handle_info *hi_b = *(const struct handle_info**)b;
2986     if (hi_a->opserv_level != hi_b->opserv_level)
2987         return hi_b->opserv_level - hi_a->opserv_level;
2988     return irccasecmp(hi_a->handle, hi_b->handle);
2989 }
2990
2991 void
2992 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
2993 {
2994     struct handle_info_list hil;
2995     struct helpfile_table tbl;
2996     unsigned int ii;
2997     dict_iterator_t it;
2998     const char **ary;
2999
3000     memset(&hil, 0, sizeof(hil));
3001     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3002         struct handle_info *hi = iter_data(it);
3003         if (hi->opserv_level)
3004             handle_info_list_append(&hil, hi);
3005     }
3006     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
3007     tbl.length = hil.used + 1;
3008     tbl.width = 2;
3009     tbl.flags = TABLE_NO_FREE;
3010     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
3011     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
3012     ary[0] = "Account";
3013     ary[1] = "Level";
3014     for (ii = 0; ii < hil.used; ) {
3015         ary = malloc(tbl.width * sizeof(ary[0]));
3016         ary[0] = hil.list[ii]->handle;
3017         ary[1] = strtab(hil.list[ii]->opserv_level);
3018         tbl.contents[++ii] = ary;
3019     }
3020     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3021     reply("MSG_MATCH_COUNT", hil.used);
3022     for (ii = 0; ii < hil.used; )
3023         free(tbl.contents[++ii]);
3024     free(tbl.contents);
3025     free(hil.list);
3026 }
3027
3028 static NICKSERV_FUNC(cmd_search)
3029 {
3030     struct nickserv_discrim *discrim;
3031     discrim_search_func action;
3032     struct svccmd *subcmd;
3033     unsigned int matches;
3034     char buf[MAXLEN];
3035
3036     NICKSERV_MIN_PARMS(3);
3037     sprintf(buf, "search %s", argv[1]);
3038     subcmd = dict_find(nickserv_service->commands, buf, NULL);
3039     if (!irccasecmp(argv[1], "print"))
3040         action = search_print_func;
3041     else if (!irccasecmp(argv[1], "count"))
3042         action = search_count_func;
3043     else if (!irccasecmp(argv[1], "unregister"))
3044         action = search_unregister_func;
3045     else {
3046         reply("NSMSG_INVALID_ACTION", argv[1]);
3047         return 0;
3048     }
3049
3050     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
3051         return 0;
3052
3053     discrim = nickserv_discrim_create(user, argc-2, argv+2);
3054     if (!discrim)
3055         return 0;
3056
3057     if (action == search_print_func)
3058         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
3059     else if (action == search_count_func)
3060         discrim->limit = INT_MAX;
3061
3062     matches = nickserv_discrim_search(discrim, action, user);
3063
3064     if (matches)
3065         reply("MSG_MATCH_COUNT", matches);
3066     else
3067         reply("MSG_NO_MATCHES");
3068
3069     free(discrim);
3070     return 0;
3071 }
3072
3073 static MODCMD_FUNC(cmd_checkpass)
3074 {
3075     struct handle_info *hi;
3076
3077     NICKSERV_MIN_PARMS(3);
3078     if (!(hi = get_handle_info(argv[1]))) {
3079         reply("MSG_HANDLE_UNKNOWN", argv[1]);
3080         return 0;
3081     }
3082     if (checkpass(argv[2], hi->passwd))
3083         reply("CHECKPASS_YES");
3084     else
3085         reply("CHECKPASS_NO");
3086     argv[2] = "****";
3087     return 1;
3088 }
3089
3090 static void
3091 nickserv_db_read_handle(const char *handle, dict_t obj)
3092 {
3093     const char *str;
3094     struct string_list *masks, *slist;
3095     struct handle_info *hi;
3096     struct userNode *authed_users;
3097     unsigned long int id;
3098     unsigned int ii;
3099     dict_t subdb;
3100
3101     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
3102     id = str ? strtoul(str, NULL, 0) : 0;
3103     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
3104     if (!str) {
3105         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
3106         return;
3107     }
3108     if ((hi = get_handle_info(handle))) {
3109         authed_users = hi->users;
3110         hi->users = NULL;
3111         dict_remove(nickserv_handle_dict, hi->handle);
3112     } else {
3113         authed_users = NULL;
3114     }
3115     hi = register_handle(handle, str, id);
3116     if (authed_users) {
3117         hi->users = authed_users;
3118         while (authed_users) {
3119             authed_users->handle_info = hi;
3120             authed_users = authed_users->next_authed;
3121         }
3122     }
3123     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
3124     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
3125     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
3126     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
3127     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
3128     hi->language = language_find(str ? str : "C");
3129     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
3130     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
3131     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
3132     if (str)
3133         hi->infoline = strdup(str);
3134     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
3135     hi->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
3136     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
3137     hi->lastseen = str ? (time_t)strtoul(str, NULL, 0) : hi->registered;
3138     /* We want to read the nicks even if disable_nicks is set.  This is so
3139      * that we don't lose the nick data entirely. */
3140     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
3141     if (slist) {
3142         for (ii=0; ii<slist->used; ii++)
3143             register_nick(slist->list[ii], hi);
3144     }
3145     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
3146     if (str) {
3147         for (ii=0; str[ii]; ii++)
3148             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
3149     }
3150     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
3151     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
3152     str = database_get_data(obj, KEY_ANNOUNCEMENTS, RECDB_QSTRING);
3153     hi->announcements = str ? str[0] : '?';
3154     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
3155     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
3156     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
3157     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
3158     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
3159     if (!str)
3160         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
3161     if (str)
3162         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
3163     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
3164     if (str)
3165         nickserv_set_email_addr(hi, str);
3166     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
3167     if (str)
3168         hi->epithet = strdup(str);
3169     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
3170     if (subdb) {
3171         const char *data, *type, *expires, *cookie_str;
3172         struct handle_cookie *cookie;
3173
3174         cookie = calloc(1, sizeof(*cookie));
3175         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
3176         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
3177         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
3178         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
3179         if (!type || !expires || !cookie_str) {
3180             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
3181             goto cookie_out;
3182         }
3183         if (!irccasecmp(type, KEY_ACTIVATION))
3184             cookie->type = ACTIVATION;
3185         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
3186             cookie->type = PASSWORD_CHANGE;
3187         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
3188             cookie->type = EMAIL_CHANGE;
3189         else if (!irccasecmp(type, KEY_ALLOWAUTH))
3190             cookie->type = ALLOWAUTH;
3191         else {
3192             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
3193             goto cookie_out;
3194         }
3195         cookie->expires = strtoul(expires, NULL, 0);
3196         if (cookie->expires < now)
3197             goto cookie_out;
3198         if (data)
3199             cookie->data = strdup(data);
3200         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
3201         cookie->hi = hi;
3202       cookie_out:
3203         if (cookie->hi)
3204             nickserv_bake_cookie(cookie);
3205         else
3206             nickserv_free_cookie(cookie);
3207     }
3208 }
3209
3210 static int
3211 nickserv_saxdb_read(dict_t db) {
3212     dict_iterator_t it;
3213     struct record_data *rd;
3214
3215     for (it=dict_first(db); it; it=iter_next(it)) {
3216         rd = iter_data(it);
3217         nickserv_db_read_handle(iter_key(it), rd->d.object);
3218     }
3219     return 0;
3220 }
3221
3222 static NICKSERV_FUNC(cmd_mergedb)
3223 {
3224     struct timeval start, stop;
3225     dict_t db;
3226
3227     NICKSERV_MIN_PARMS(2);
3228     gettimeofday(&start, NULL);
3229     if (!(db = parse_database(argv[1]))) {
3230         reply("NSMSG_DB_UNREADABLE", argv[1]);
3231         return 0;
3232     }
3233     nickserv_saxdb_read(db);
3234     free_database(db);
3235     gettimeofday(&stop, NULL);
3236     stop.tv_sec -= start.tv_sec;
3237     stop.tv_usec -= start.tv_usec;
3238     if (stop.tv_usec < 0) {
3239         stop.tv_sec -= 1;
3240         stop.tv_usec += 1000000;
3241     }
3242     reply("NSMSG_DB_MERGED", argv[1], stop.tv_sec, stop.tv_usec/1000);
3243     return 1;
3244 }
3245
3246 static void
3247 expire_handles(UNUSED_ARG(void *data))
3248 {
3249     dict_iterator_t it, next;
3250     time_t expiry;
3251     struct handle_info *hi;
3252
3253     for (it=dict_first(nickserv_handle_dict); it; it=next) {
3254         next = iter_next(it);
3255         hi = iter_data(it);
3256         if ((hi->opserv_level > 0)
3257             || hi->users
3258             || HANDLE_FLAGGED(hi, FROZEN)
3259             || HANDLE_FLAGGED(hi, NODELETE)) {
3260             continue;
3261         }
3262         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
3263         if ((now - hi->lastseen) > expiry) {
3264             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
3265             nickserv_unregister_handle(hi, NULL);
3266         }
3267     }
3268
3269     if (nickserv_conf.handle_expire_frequency)
3270         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3271 }
3272
3273 static void
3274 nickserv_load_dict(const char *fname)
3275 {
3276     FILE *file;
3277     char line[128];
3278     if (!(file = fopen(fname, "r"))) {
3279         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
3280         return;
3281     }
3282     while (!feof(file)) {
3283         fgets(line, sizeof(line), file);
3284         if (!line[0])
3285             continue;
3286         if (line[strlen(line)-1] == '\n')
3287             line[strlen(line)-1] = 0;
3288         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
3289     }
3290     fclose(file);
3291     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
3292 }
3293
3294 static enum reclaim_action
3295 reclaim_action_from_string(const char *str) {
3296     if (!str)
3297         return RECLAIM_NONE;
3298     else if (!irccasecmp(str, "warn"))
3299         return RECLAIM_WARN;
3300     else if (!irccasecmp(str, "svsnick"))
3301         return RECLAIM_SVSNICK;
3302     else if (!irccasecmp(str, "kill"))
3303         return RECLAIM_KILL;
3304     else
3305         return RECLAIM_NONE;
3306 }
3307
3308 static void
3309 nickserv_conf_read(void)
3310 {
3311     dict_t conf_node, child;
3312     const char *str;
3313     dict_iterator_t it;
3314
3315     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
3316         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
3317         return;
3318     }
3319     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
3320     if (!str)
3321         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
3322     if (nickserv_conf.valid_handle_regex_set) regfree(&nickserv_conf.valid_handle_regex);
3323     if (str) {
3324         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3325         nickserv_conf.valid_handle_regex_set = !err;
3326         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
3327     } else {
3328         nickserv_conf.valid_handle_regex_set = 0;
3329     }
3330     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
3331     if (nickserv_conf.valid_nick_regex_set) regfree(&nickserv_conf.valid_nick_regex);
3332     if (str) {
3333         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3334         nickserv_conf.valid_nick_regex_set = !err;
3335         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
3336     } else {
3337         nickserv_conf.valid_nick_regex_set = 0;
3338     }
3339     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
3340     if (!str)
3341         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
3342     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
3343     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
3344     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
3345     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
3346     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
3347     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
3348     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
3349     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
3350     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
3351     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
3352     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
3353     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
3354     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
3355     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
3356     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3357     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
3358     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
3359     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
3360     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
3361     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
3362     if (!str)
3363         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
3364     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
3365     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3366     if (!str)
3367         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3368     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
3369     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3370     if (!str)
3371         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3372     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
3373     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
3374     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
3375     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
3376     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
3377     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
3378     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
3379     if (!nickserv_conf.disable_nicks) {
3380         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
3381         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3382         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
3383         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
3384         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
3385         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3386         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
3387         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
3388     }
3389     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
3390     for (it=dict_first(child); it; it=iter_next(it)) {
3391         const char *key = iter_key(it), *value;
3392         unsigned char flag;
3393         int pos;
3394
3395         if (!strncasecmp(key, "uc_", 3))
3396             flag = toupper(key[3]);
3397         else if (!strncasecmp(key, "lc_", 3))
3398             flag = tolower(key[3]);
3399         else
3400             flag = key[0];
3401
3402         if ((pos = handle_inverse_flags[flag])) {
3403             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
3404             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
3405         }
3406     }
3407     if (nickserv_conf.weak_password_dict)
3408         dict_delete(nickserv_conf.weak_password_dict);
3409     nickserv_conf.weak_password_dict = dict_new();
3410     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
3411     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
3412     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
3413     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
3414     if (str)
3415         nickserv_load_dict(str);
3416     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
3417     if (nickserv && str)
3418         NickChange(nickserv, str, 0);
3419     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
3420     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
3421     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
3422     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
3423     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
3424     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
3425     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
3426     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
3427     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
3428     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
3429     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
3430     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
3431     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
3432     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
3433     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
3434     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
3435     str = conf_get_data("server/network", RECDB_QSTRING);
3436     nickserv_conf.network_name = str ? str : "some IRC network";
3437     if (!nickserv_conf.auth_policer_params) {
3438         nickserv_conf.auth_policer_params = policer_params_new();
3439         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
3440         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
3441     }
3442     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
3443     for (it=dict_first(child); it; it=iter_next(it))
3444         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
3445 }
3446
3447 static void
3448 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
3449     const char *msg;
3450     char newnick[NICKLEN+1];
3451
3452     assert(user);
3453     assert(ni);
3454     switch (action) {
3455     case RECLAIM_NONE:
3456         /* do nothing */
3457         break;
3458     case RECLAIM_WARN:
3459         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3460         break;
3461     case RECLAIM_SVSNICK:
3462         do {
3463             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
3464         } while (GetUserH(newnick));
3465         irc_svsnick(nickserv, user, newnick);
3466         break;
3467     case RECLAIM_KILL:
3468         msg = user_find_message(user, "NSMSG_RECLAIM_KILL");
3469         irc_kill(nickserv, user, msg);
3470         break;
3471     }
3472 }
3473
3474 static void
3475 nickserv_reclaim_p(void *data) {
3476     struct userNode *user = data;
3477     struct nick_info *ni = get_nick_info(user->nick);
3478     if (ni)
3479         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3480 }
3481
3482 static int
3483 check_user_nick(struct userNode *user) {
3484     struct nick_info *ni;
3485     user->modes &= ~FLAGS_REGNICK;
3486     if (!(ni = get_nick_info(user->nick)))
3487         return 0;
3488     if (user->handle_info == ni->owner) {
3489         user->modes |= FLAGS_REGNICK;
3490         irc_regnick(user);
3491         return 0;
3492     }
3493     if (nickserv_conf.warn_nick_owned)
3494         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3495     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
3496         return 0;
3497     if (nickserv_conf.auto_reclaim_delay)
3498         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
3499     else
3500         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3501     return 0;
3502 }
3503
3504 int
3505 handle_new_user(struct userNode *user)
3506 {
3507     return check_user_nick(user);
3508 }
3509
3510 void
3511 handle_account(struct userNode *user, const char *stamp)
3512 {
3513     struct handle_info *hi;
3514
3515 #ifdef WITH_PROTOCOL_P10
3516     hi = dict_find(nickserv_handle_dict, stamp, NULL);
3517 #else
3518     hi = dict_find(nickserv_id_dict, stamp, NULL);
3519 #endif
3520
3521     if (hi) {
3522         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
3523             return;
3524         }
3525         set_user_handle_info(user, hi, 0);
3526     } else {
3527         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s.", user->nick, stamp);
3528     }
3529 }
3530
3531 void
3532 handle_nick_change(struct userNode *user, const char *old_nick)
3533 {
3534     struct handle_info *hi;
3535
3536     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
3537         dict_remove(nickserv_allow_auth_dict, old_nick);
3538         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
3539     }
3540     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
3541     check_user_nick(user);
3542 }
3543
3544 void
3545 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
3546 {
3547     dict_remove(nickserv_allow_auth_dict, user->nick);
3548     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
3549     set_user_handle_info(user, NULL, 0);
3550 }
3551
3552 static struct modcmd *
3553 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
3554 {
3555     if (min_level > 0) {
3556         char buf[16];
3557         sprintf(buf, "%u", min_level);
3558         if (must_be_qualified) {
3559             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
3560         } else {
3561             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
3562         }
3563     } else if (min_level == 0) {
3564         if (must_be_qualified) {
3565             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
3566         } else {
3567             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
3568         }
3569     } else {
3570         if (must_be_qualified) {
3571             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
3572         } else {
3573             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
3574         }
3575     }
3576 }
3577
3578 static void
3579 nickserv_db_cleanup(void)
3580 {
3581     unreg_del_user_func(nickserv_remove_user);
3582     userList_clean(&curr_helpers);
3583     policer_params_delete(nickserv_conf.auth_policer_params);
3584     dict_delete(nickserv_handle_dict);
3585     dict_delete(nickserv_nick_dict);
3586     dict_delete(nickserv_opt_dict);
3587     dict_delete(nickserv_allow_auth_dict);
3588     dict_delete(nickserv_email_dict);
3589     dict_delete(nickserv_id_dict);
3590     dict_delete(nickserv_conf.weak_password_dict);
3591     free(auth_func_list);
3592     free(unreg_func_list);
3593     free(rf_list);
3594     free(allowauth_func_list);
3595     free(handle_merge_func_list);
3596     free(failpw_func_list);
3597     if (nickserv_conf.valid_handle_regex_set)
3598         regfree(&nickserv_conf.valid_handle_regex);
3599     if (nickserv_conf.valid_nick_regex_set)
3600         regfree(&nickserv_conf.valid_nick_regex);
3601 }
3602
3603 void
3604 init_nickserv(const char *nick)
3605 {
3606     unsigned int i;
3607     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
3608     reg_new_user_func(handle_new_user);
3609     reg_nick_change_func(handle_nick_change);
3610     reg_del_user_func(nickserv_remove_user);
3611     reg_account_func(handle_account);
3612
3613     /* set up handle_inverse_flags */
3614     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
3615     for (i=0; handle_flags[i]; i++) {
3616         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
3617         flag_access_levels[i] = 0;
3618     }
3619
3620     conf_register_reload(nickserv_conf_read);
3621     nickserv_opt_dict = dict_new();
3622     nickserv_email_dict = dict_new();
3623     dict_set_free_keys(nickserv_email_dict, free);
3624     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
3625
3626     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
3627     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
3628     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
3629     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
3630     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
3631     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
3632     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
3633     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
3634     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
3635     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
3636     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
3637     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
3638     nickserv_define_func("SET", cmd_set, -1, 1, 0);
3639     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
3640     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
3641     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
3642     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
3643     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
3644     nickserv_define_func("MERGE", cmd_merge, 0, 1, 0);
3645     if (!nickserv_conf.disable_nicks) {
3646         /* nick management commands */
3647         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
3648         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
3649         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
3650         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
3651         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
3652         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
3653     }
3654     if (nickserv_conf.email_enabled) {
3655         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
3656         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
3657         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
3658         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
3659         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
3660     }
3661     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
3662     /* miscellaneous commands */
3663     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
3664     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
3665     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
3666     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
3667     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
3668     /* other options */
3669     dict_insert(nickserv_opt_dict, "INFO", opt_info);
3670     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
3671     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
3672     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
3673     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
3674     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
3675     dict_insert(nickserv_opt_dict, "PASS", opt_password);
3676     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
3677     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
3678     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
3679     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
3680     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
3681     dict_insert(nickserv_opt_dict, "ANNOUNCEMENTS", opt_announcements);
3682     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
3683     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
3684
3685     nickserv_handle_dict = dict_new();
3686     dict_set_free_keys(nickserv_handle_dict, free);
3687     dict_set_free_data(nickserv_handle_dict, free_handle_info);
3688
3689     nickserv_id_dict = dict_new();
3690     dict_set_free_keys(nickserv_id_dict, free);
3691
3692     nickserv_nick_dict = dict_new();
3693     dict_set_free_data(nickserv_nick_dict, free_nick_info);
3694
3695     nickserv_allow_auth_dict = dict_new();
3696
3697     userList_init(&curr_helpers);
3698
3699     if (nick) {
3700         nickserv = AddService(nick, "Nick Services", NULL);
3701         nickserv_service = service_register(nickserv);
3702     }
3703     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
3704     reg_exit_func(nickserv_db_cleanup);
3705     if(nickserv_conf.handle_expire_frequency)
3706         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3707     message_register_table(msgtab);
3708 }