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