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