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