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