Avoid crashing when a user is killed by an on-auth callback.
[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 if (user->handle_info->users != NULL) {
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         } else {
926             /* No users authed to the account - can happen if they get
927              * killed for authing. */
928         }
929         /* if nobody left on old handle, and they're not an oper, remove !god */
930         if (!user->handle_info->users && !user->handle_info->opserv_level)
931             HANDLE_CLEAR_FLAG(user->handle_info, HELPING);
932         /* record them as being last seen at this time */
933         user->handle_info->lastseen = now;
934         /* and record their hostmask */
935         snprintf(user->handle_info->last_quit_host, sizeof(user->handle_info->last_quit_host), "%s@%s", user->ident, user->hostname);
936     }
937     old_info = user->handle_info;
938     user->handle_info = hi;
939     if (hi && !hi->users && !hi->opserv_level)
940         HANDLE_CLEAR_FLAG(hi, HELPING);
941     for (n=0; n<auth_func_used; n++) {
942         auth_func_list[n](user, old_info);
943         if (user->dead)
944             return;
945     }
946     if (hi) {
947         struct nick_info *ni;
948
949         HANDLE_CLEAR_FLAG(hi, FROZEN);
950         if (nickserv_conf.warn_clone_auth) {
951             struct userNode *other;
952             for (other = hi->users; other; other = other->next_authed)
953                 send_message(other, nickserv, "NSMSG_CLONE_AUTH", user->nick, user->ident, user->hostname);
954         }
955         user->next_authed = hi->users;
956         hi->users = user;
957         hi->lastseen = now;
958         if (IsHelper(user) && !userList_contains(&curr_helpers, user))
959             userList_append(&curr_helpers, user);
960
961         if (hi->fakehost || old_info)
962             apply_fakehost(hi);
963
964         if (stamp) {
965             if (!nickserv_conf.disable_nicks) {
966                 struct nick_info *ni2;
967                 for (ni2 = hi->nicks; ni2; ni2 = ni2->next) {
968                     if (!irccasecmp(user->nick, ni2->nick)) {
969                         user->modes |= FLAGS_REGNICK;
970                         break;
971                     }
972                 }
973             }
974             StampUser(user, hi->handle, hi->registered, hi->id);
975         }
976
977         if ((ni = get_nick_info(user->nick)) && (ni->owner == hi))
978             timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
979     } else {
980         /* We cannot clear the user's account ID, unfortunately. */
981         user->next_authed = NULL;
982     }
983 }
984
985 static struct handle_info*
986 nickserv_register(struct userNode *user, struct userNode *settee, const char *handle, const char *passwd, int no_auth)
987 {
988     struct handle_info *hi;
989     struct nick_info *ni;
990     char crypted[MD5_CRYPT_LENGTH];
991
992     if ((hi = dict_find(nickserv_handle_dict, handle, NULL))) {
993         send_message(user, nickserv, "NSMSG_HANDLE_EXISTS", handle);
994         return 0;
995     }
996
997     if (!is_secure_password(handle, passwd, user))
998         return 0;
999
1000     cryptpass(passwd, crypted);
1001     hi = register_handle(handle, crypted, 0);
1002     hi->masks = alloc_string_list(1);
1003     hi->users = NULL;
1004     hi->language = lang_C;
1005     hi->registered = now;
1006     hi->lastseen = now;
1007     hi->flags = HI_DEFAULT_FLAGS;
1008     if (settee && !no_auth)
1009         set_user_handle_info(settee, hi, 1);
1010
1011     if (user != settee)
1012         send_message(user, nickserv, "NSMSG_OREGISTER_H_SUCCESS");
1013     else if (nickserv_conf.disable_nicks)
1014         send_message(user, nickserv, "NSMSG_REGISTER_H_SUCCESS");
1015     else if ((ni = dict_find(nickserv_nick_dict, user->nick, NULL)))
1016         send_message(user, nickserv, "NSMSG_PARTIAL_REGISTER");
1017     else {
1018         register_nick(user->nick, hi);
1019         send_message(user, nickserv, "NSMSG_REGISTER_HN_SUCCESS");
1020     }
1021     if (settee && (user != settee))
1022         send_message(settee, nickserv, "NSMSG_OREGISTER_VICTIM", user->nick, hi->handle);
1023     return hi;
1024 }
1025
1026 static void
1027 nickserv_bake_cookie(struct handle_cookie *cookie)
1028 {
1029     cookie->hi->cookie = cookie;
1030     timeq_add(cookie->expires, nickserv_free_cookie, cookie);
1031 }
1032
1033 static void
1034 nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_type type, const char *cookie_data)
1035 {
1036     struct handle_cookie *cookie;
1037     char subject[128], body[4096], *misc;
1038     const char *netname, *fmt;
1039     int first_time = 0;
1040
1041     if (hi->cookie) {
1042         send_message(user, nickserv, "NSMSG_COOKIE_LIVE", hi->handle);
1043         return;
1044     }
1045
1046     cookie = calloc(1, sizeof(*cookie));
1047     cookie->hi = hi;
1048     cookie->type = type;
1049     cookie->data = cookie_data ? strdup(cookie_data) : NULL;
1050     cookie->expires = now + nickserv_conf.cookie_timeout;
1051     inttobase64(cookie->cookie, rand(), 5);
1052     inttobase64(cookie->cookie+5, rand(), 5);
1053
1054     netname = nickserv_conf.network_name;
1055     subject[0] = 0;
1056
1057     switch (cookie->type) {
1058     case ACTIVATION:
1059         hi->passwd[0] = 0; /* invalidate password */
1060         send_message(user, nickserv, "NSMSG_USE_COOKIE_REGISTER");
1061         fmt = handle_find_message(hi, "NSEMAIL_ACTIVATION_SUBJECT");
1062         snprintf(subject, sizeof(subject), fmt, netname);
1063         fmt = handle_find_message(hi, "NSEMAIL_ACTIVATION_BODY");
1064         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
1065         first_time = 1;
1066         break;
1067     case PASSWORD_CHANGE:
1068         send_message(user, nickserv, "NSMSG_USE_COOKIE_RESETPASS");
1069         fmt = handle_find_message(hi, "NSEMAIL_PASSWORD_CHANGE_SUBJECT");
1070         snprintf(subject, sizeof(subject), fmt, netname);
1071         fmt = handle_find_message(hi, "NSEMAIL_PASSWORD_CHANGE_BODY");
1072         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
1073         break;
1074     case EMAIL_CHANGE:
1075         misc = hi->email_addr;
1076         hi->email_addr = cookie->data;
1077         if (misc) {
1078             send_message(user, nickserv, "NSMSG_USE_COOKIE_EMAIL_2");
1079             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_SUBJECT");
1080             snprintf(subject, sizeof(subject), fmt, netname);
1081             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_BODY_NEW");
1082             snprintf(body, sizeof(body), fmt, netname, cookie->cookie+COOKIELEN/2, nickserv->nick, self->name, hi->handle, COOKIELEN/2);
1083             mail_send(nickserv, hi, subject, body, 1);
1084             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_BODY_OLD");
1085             snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle, COOKIELEN/2, hi->email_addr);
1086         } else {
1087             send_message(user, nickserv, "NSMSG_USE_COOKIE_EMAIL_1");
1088             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_VERIFY_SUBJECT");
1089             snprintf(subject, sizeof(subject), fmt, netname);
1090             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_VERIFY_BODY");
1091             snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
1092             mail_send(nickserv, hi, subject, body, 1);
1093             subject[0] = 0;
1094         }
1095         hi->email_addr = misc;
1096         break;
1097     case ALLOWAUTH:
1098         fmt = handle_find_message(hi, "NSEMAIL_ALLOWAUTH_SUBJECT");
1099         snprintf(subject, sizeof(subject), fmt, netname);
1100         fmt = handle_find_message(hi, "NSEMAIL_ALLOWAUTH_BODY");
1101         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
1102         send_message(user, nickserv, "NSMSG_USE_COOKIE_AUTH");
1103         break;
1104     default:
1105         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d in nickserv_make_cookie.", cookie->type);
1106         break;
1107     }
1108     if (subject[0])
1109         mail_send(nickserv, hi, subject, body, first_time);
1110     nickserv_bake_cookie(cookie);
1111 }
1112
1113 static void
1114 nickserv_eat_cookie(struct handle_cookie *cookie)
1115 {
1116     cookie->hi->cookie = NULL;
1117     timeq_del(cookie->expires, nickserv_free_cookie, cookie, 0);
1118     nickserv_free_cookie(cookie);
1119 }
1120
1121 static void
1122 nickserv_free_email_addr(void *data)
1123 {
1124     handle_info_list_clean(data);
1125     free(data);
1126 }
1127
1128 static void
1129 nickserv_set_email_addr(struct handle_info *hi, const char *new_email_addr)
1130 {
1131     struct handle_info_list *hil;
1132     /* Remove from old handle_info_list ... */
1133     if (hi->email_addr && (hil = dict_find(nickserv_email_dict, hi->email_addr, 0))) {
1134         handle_info_list_remove(hil, hi);
1135         if (!hil->used) dict_remove(nickserv_email_dict, hil->tag);
1136         hi->email_addr = NULL;
1137     }
1138     /* Add to the new list.. */
1139     if (new_email_addr) {
1140         if (!(hil = dict_find(nickserv_email_dict, new_email_addr, 0))) {
1141             hil = calloc(1, sizeof(*hil));
1142             hil->tag = strdup(new_email_addr);
1143             handle_info_list_init(hil);
1144             dict_insert(nickserv_email_dict, hil->tag, hil);
1145         }
1146         handle_info_list_append(hil, hi);
1147         hi->email_addr = hil->tag;
1148     }
1149 }
1150
1151 static NICKSERV_FUNC(cmd_register)
1152 {
1153     irc_in_addr_t ip;
1154     struct handle_info *hi;
1155     const char *email_addr, *password;
1156     int no_auth;
1157
1158     if (!IsOper(user) && !dict_size(nickserv_handle_dict)) {
1159         /* Require the first handle registered to belong to someone +o. */
1160         reply("NSMSG_REQUIRE_OPER");
1161         return 0;
1162     }
1163
1164     if (user->handle_info) {
1165         reply("NSMSG_USE_RENAME", user->handle_info->handle);
1166         return 0;
1167     }
1168
1169     if (IsRegistering(user)) {
1170         reply("NSMSG_ALREADY_REGISTERING");
1171         return 0;
1172     }
1173
1174     if (IsStamped(user)) {
1175         /* Unauthenticated users might still have been stamped
1176            previously and could therefore have a hidden host;
1177            do not allow them to register a new account. */
1178         reply("NSMSG_STAMPED_REGISTER");
1179         return 0;
1180     }
1181
1182     NICKSERV_MIN_PARMS((unsigned)3 + nickserv_conf.email_required);
1183
1184     if (!is_valid_handle(argv[1])) {
1185         reply("NSMSG_BAD_HANDLE", argv[1]);
1186         return 0;
1187     }
1188
1189     if ((argc >= 4) && nickserv_conf.email_enabled) {
1190         struct handle_info_list *hil;
1191         const char *str;
1192
1193         /* Remember email address. */
1194         email_addr = argv[3];
1195
1196         /* Check that the email address looks valid.. */
1197         if (!is_valid_email_addr(email_addr)) {
1198             reply("NSMSG_BAD_EMAIL_ADDR");
1199             return 0;
1200         }
1201
1202         /* .. and that we are allowed to send to it. */
1203         if ((str = mail_prohibited_address(email_addr))) {
1204             reply("NSMSG_EMAIL_PROHIBITED", email_addr, str);
1205             return 0;
1206         }
1207
1208         /* If we do email verify, make sure we don't spam the address. */
1209         if ((hil = dict_find(nickserv_email_dict, email_addr, NULL))) {
1210             unsigned int nn;
1211             for (nn=0; nn<hil->used; nn++) {
1212                 if (hil->list[nn]->cookie) {
1213                     reply("NSMSG_EMAIL_UNACTIVATED");
1214                     return 0;
1215                 }
1216             }
1217             if (hil->used >= nickserv_conf.handles_per_email) {
1218                 reply("NSMSG_EMAIL_OVERUSED");
1219                 return 0;
1220             }
1221         }
1222
1223         no_auth = 1;
1224     } else {
1225         email_addr = 0;
1226         no_auth = 0;
1227     }
1228
1229     password = argv[2];
1230     argv[2] = "****";
1231     if (!(hi = nickserv_register(user, user, argv[1], password, no_auth)))
1232         return 0;
1233     /* Add any masks they should get. */
1234     if (nickserv_conf.default_hostmask) {
1235         string_list_append(hi->masks, strdup("*@*"));
1236     } else {
1237         string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1238         if (irc_in_addr_is_valid(user->ip) && !irc_pton(&ip, NULL, user->hostname))
1239             string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1240     }
1241
1242     /* If they're the first to register, give them level 1000. */
1243     if (dict_size(nickserv_handle_dict) == 1) {
1244         hi->opserv_level = 1000;
1245         reply("NSMSG_ROOT_HANDLE", argv[1]);
1246     }
1247
1248     /* Set their email address. */
1249     if (email_addr)
1250         nickserv_set_email_addr(hi, email_addr);
1251
1252     /* If they need to do email verification, tell them. */
1253     if (no_auth)
1254         nickserv_make_cookie(user, hi, ACTIVATION, hi->passwd);
1255
1256     /* Set registering flag.. */
1257     user->modes |= FLAGS_REGISTERING;
1258
1259     return 1;
1260 }
1261
1262 static NICKSERV_FUNC(cmd_oregister)
1263 {
1264     char *mask;
1265     struct userNode *settee;
1266     struct handle_info *hi;
1267
1268     NICKSERV_MIN_PARMS(3);
1269
1270     if (!is_valid_handle(argv[1])) {
1271         reply("NSMSG_BAD_HANDLE", argv[1]);
1272         return 0;
1273     }
1274
1275     if (argc < 4) {
1276         mask = NULL;
1277         settee = NULL;
1278     } else if (strchr(argv[3], '@')) {
1279         mask = canonicalize_hostmask(strdup(argv[3]));
1280         if (argc > 4) {
1281             settee = GetUserH(argv[4]);
1282             if (!settee) {
1283                 reply("MSG_NICK_UNKNOWN", argv[4]);
1284                 free(mask);
1285                 return 0;
1286             }
1287         } else {
1288             settee = NULL;
1289         }
1290     } else if ((settee = GetUserH(argv[3]))) {
1291         mask = generate_hostmask(settee, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
1292     } else {
1293         reply("NSMSG_REGISTER_BAD_NICKMASK", argv[3]);
1294         return 0;
1295     }
1296     if (settee && settee->handle_info) {
1297         reply("NSMSG_USER_PREV_AUTH", settee->nick);
1298         free(mask);
1299         return 0;
1300     }
1301     if (!(hi = nickserv_register(user, settee, argv[1], argv[2], 0))) {
1302         free(mask);
1303         return 0;
1304     }
1305     if (mask)
1306         string_list_append(hi->masks, mask);
1307     return 1;
1308 }
1309
1310 static NICKSERV_FUNC(cmd_handleinfo)
1311 {
1312     char buff[400];
1313     unsigned int i, pos=0, herelen;
1314     struct userNode *target, *next_un;
1315     struct handle_info *hi;
1316     const char *nsmsg_none;
1317     time_t feh;
1318
1319     if (argc < 2) {
1320         if (!(hi = user->handle_info)) {
1321             reply("NSMSG_MUST_AUTH");
1322             return 0;
1323         }
1324     } else if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
1325         return 0;
1326     }
1327
1328     nsmsg_none = handle_find_message(hi, "MSG_NONE");
1329     reply("NSMSG_HANDLEINFO_ON", hi->handle);
1330     feh = hi->registered;
1331     reply("NSMSG_HANDLEINFO_REGGED", ctime(&feh));
1332
1333     if (!hi->users) {
1334         intervalString(buff, now - hi->lastseen, user->handle_info);
1335         reply("NSMSG_HANDLEINFO_LASTSEEN", buff);
1336     } else {
1337         reply("NSMSG_HANDLEINFO_LASTSEEN_NOW");
1338     }
1339
1340     reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none));
1341     if (HANDLE_FLAGGED(hi, FROZEN))
1342         reply("NSMSG_HANDLEINFO_VACATION");
1343
1344     if (oper_has_access(user, cmd->parent->bot, 0, 1)) {
1345         struct do_not_register *dnr;
1346         if ((dnr = chanserv_is_dnr(NULL, hi)))
1347             reply("NSMSG_HANDLEINFO_DNR", dnr->setter, dnr->reason);
1348         if ((user->handle_info->opserv_level < 900) && !oper_outranks(user, hi))
1349             return 1;
1350     } else if (hi != user->handle_info)
1351         return 1;
1352
1353     if (IsOper(user))
1354         reply("NSMSG_HANDLEINFO_KARMA", hi->karma);
1355
1356     if (nickserv_conf.email_enabled)
1357         reply("NSMSG_HANDLEINFO_EMAIL_ADDR", visible_email_addr(user, hi));
1358
1359     if (hi->cookie) {
1360         const char *type;
1361         switch (hi->cookie->type) {
1362         case ACTIVATION: type = "NSMSG_HANDLEINFO_COOKIE_ACTIVATION"; break;
1363         case PASSWORD_CHANGE: type = "NSMSG_HANDLEINFO_COOKIE_PASSWORD"; break;
1364         case EMAIL_CHANGE: type = "NSMSG_HANDLEINFO_COOKIE_EMAIL"; break;
1365         case ALLOWAUTH: type = "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH"; break;
1366         default: type = "NSMSG_HANDLEINFO_COOKIE_UNKNOWN"; break;
1367         }
1368         reply(type);
1369     }
1370
1371     if (oper_has_access(user, cmd->parent->bot, 601, 1))
1372         reply("NSMSG_HANDLEINFO_ID", hi->id);
1373
1374     if (oper_has_access(user, cmd->parent->bot, 0, 1) || IsStaff(user)) {
1375         if (!hi->notes) {
1376             reply("NSMSG_HANDLEINFO_NO_NOTES");
1377         } else {
1378             struct handle_note *prev, *note;
1379
1380             WALK_NOTES(hi, prev, note) {
1381                 char set_time[INTERVALLEN];
1382                 intervalString(set_time, now - note->set, user->handle_info);
1383                 if (note->expires) {
1384                     char exp_time[INTERVALLEN];
1385                     intervalString(exp_time, note->expires - now, user->handle_info);
1386                     reply("NSMSG_HANDLEINFO_NOTE_EXPIRES", note->id, set_time, note->setter, exp_time, note->note);
1387                 } else {
1388                     reply("NSMSG_HANDLEINFO_NOTE", note->id, set_time, note->setter, note->note);
1389                 }
1390             }
1391         }
1392     }
1393
1394     if (hi->flags) {
1395         unsigned long flen = 1;
1396         char flags[34]; /* 32 bits possible plus '+' and '\0' */
1397         flags[0] = '+';
1398         for (i=0, flen=1; handle_flags[i]; i++)
1399             if (hi->flags & 1 << i)
1400                 flags[flen++] = handle_flags[i];
1401         flags[flen] = 0;
1402         reply("NSMSG_HANDLEINFO_FLAGS", flags);
1403     } else {
1404         reply("NSMSG_HANDLEINFO_FLAGS", nsmsg_none);
1405     }
1406
1407     if (HANDLE_FLAGGED(hi, SUPPORT_HELPER)
1408         || HANDLE_FLAGGED(hi, NETWORK_HELPER)
1409         || (hi->opserv_level > 0)) {
1410         reply("NSMSG_HANDLEINFO_EPITHET", (hi->epithet ? hi->epithet : nsmsg_none));
1411     }
1412
1413     if (hi->fakehost)
1414         reply("NSMSG_HANDLEINFO_FAKEHOST", (hi->fakehost ? hi->fakehost : handle_find_message(hi, "MSG_NONE")));
1415
1416     if (hi->last_quit_host[0])
1417         reply("NSMSG_HANDLEINFO_LAST_HOST", hi->last_quit_host);
1418     else
1419         reply("NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN");
1420
1421     if (nickserv_conf.disable_nicks) {
1422         /* nicks disabled; don't show anything about registered nicks */
1423     } else if (hi->nicks) {
1424         struct nick_info *ni, *next_ni;
1425         for (ni = hi->nicks; ni; ni = next_ni) {
1426             herelen = strlen(ni->nick);
1427             if (pos + herelen + 1 > ArrayLength(buff)) {
1428                 next_ni = ni;
1429                 goto print_nicks_buff;
1430             } else {
1431                 next_ni = ni->next;
1432             }
1433             memcpy(buff+pos, ni->nick, herelen);
1434             pos += herelen; buff[pos++] = ' ';
1435             if (!next_ni) {
1436               print_nicks_buff:
1437                 buff[pos-1] = 0;
1438                 reply("NSMSG_HANDLEINFO_NICKS", buff);
1439                 pos = 0;
1440             }
1441         }
1442     } else {
1443         reply("NSMSG_HANDLEINFO_NICKS", nsmsg_none);
1444     }
1445
1446     if (hi->masks->used) {
1447         for (i=0; i < hi->masks->used; i++) {
1448             herelen = strlen(hi->masks->list[i]);
1449             if (pos + herelen + 1 > ArrayLength(buff)) {
1450                 i--;
1451                 goto print_mask_buff;
1452             }
1453             memcpy(buff+pos, hi->masks->list[i], herelen);
1454             pos += herelen; buff[pos++] = ' ';
1455             if (i+1 == hi->masks->used) {
1456               print_mask_buff:
1457                 buff[pos-1] = 0;
1458                 reply("NSMSG_HANDLEINFO_MASKS", buff);
1459                 pos = 0;
1460             }
1461         }
1462     } else {
1463         reply("NSMSG_HANDLEINFO_MASKS", nsmsg_none);
1464     }
1465
1466     if (hi->channels) {
1467         struct userData *chan, *next;
1468         char *name;
1469
1470         for (chan = hi->channels; chan; chan = next) {
1471             next = chan->u_next;
1472             name = chan->channel->channel->name;
1473             herelen = strlen(name);
1474             if (pos + herelen + 7 > ArrayLength(buff)) {
1475                 next = chan;
1476                 goto print_chans_buff;
1477             }
1478             if (IsUserSuspended(chan))
1479                 buff[pos++] = '-';
1480             pos += sprintf(buff+pos, "%d:%s ", chan->access, name);
1481             if (next == NULL) {
1482               print_chans_buff:
1483                 buff[pos-1] = 0;
1484                 reply("NSMSG_HANDLEINFO_CHANNELS", buff);
1485                 pos = 0;
1486             }
1487         }
1488     } else {
1489         reply("NSMSG_HANDLEINFO_CHANNELS", nsmsg_none);
1490     }
1491
1492     for (target = hi->users; target; target = next_un) {
1493         herelen = strlen(target->nick);
1494         if (pos + herelen + 1 > ArrayLength(buff)) {
1495             next_un = target;
1496             goto print_cnick_buff;
1497         } else {
1498             next_un = target->next_authed;
1499         }
1500         memcpy(buff+pos, target->nick, herelen);
1501         pos += herelen; buff[pos++] = ' ';
1502         if (!next_un) {
1503             print_cnick_buff:
1504             buff[pos-1] = 0;
1505             reply("NSMSG_HANDLEINFO_CURRENT", buff);
1506             pos = 0;
1507         }
1508     }
1509
1510     return 1 | ((hi != user->handle_info) ? CMD_LOG_STAFF : 0);
1511 }
1512
1513 static NICKSERV_FUNC(cmd_userinfo)
1514 {
1515     struct userNode *target;
1516
1517     NICKSERV_MIN_PARMS(2);
1518     if (!(target = GetUserH(argv[1]))) {
1519         reply("MSG_NICK_UNKNOWN", argv[1]);
1520         return 0;
1521     }
1522     if (target->handle_info)
1523         reply("NSMSG_USERINFO_AUTHED_AS", target->nick, target->handle_info->handle);
1524     else
1525         reply("NSMSG_USERINFO_NOT_AUTHED", target->nick);
1526     return 1;
1527 }
1528
1529 static NICKSERV_FUNC(cmd_nickinfo)
1530 {
1531     struct nick_info *ni;
1532
1533     NICKSERV_MIN_PARMS(2);
1534     if (!(ni = get_nick_info(argv[1]))) {
1535         reply("MSG_NICK_UNKNOWN", argv[1]);
1536         return 0;
1537     }
1538     reply("NSMSG_NICKINFO_OWNER", ni->nick, ni->owner->handle);
1539     return 1;
1540 }
1541
1542 static NICKSERV_FUNC(cmd_notes)
1543 {
1544     struct handle_info *hi;
1545     struct handle_note *prev, *note;
1546     unsigned int hits;
1547
1548     NICKSERV_MIN_PARMS(2);
1549     if (!(hi = get_victim_oper(user, argv[1])))
1550         return 0;
1551     hits = 0;
1552     WALK_NOTES(hi, prev, note) {
1553         char set_time[INTERVALLEN];
1554         intervalString(set_time, now - note->set, user->handle_info);
1555         if (note->expires) {
1556             char exp_time[INTERVALLEN];
1557             intervalString(exp_time, note->expires - now, user->handle_info);
1558             reply("NSMSG_NOTE_EXPIRES", note->id, set_time, note->setter, exp_time, note->note);
1559         } else {
1560             reply("NSMSG_NOTE", note->id, set_time, note->setter, note->note);
1561         }
1562         ++hits;
1563     }
1564     reply("NSMSG_NOTE_COUNT", hits, argv[1]);
1565     return 1;
1566 }
1567
1568 static NICKSERV_FUNC(cmd_rename_handle)
1569 {
1570     struct handle_info *hi;
1571     char msgbuf[MAXLEN], *old_handle;
1572     unsigned int nn;
1573
1574     NICKSERV_MIN_PARMS(3);
1575     if (!(hi = get_victim_oper(user, argv[1])))
1576         return 0;
1577     if (!is_valid_handle(argv[2])) {
1578         reply("NSMSG_FAIL_RENAME", argv[1], argv[2]);
1579         return 0;
1580     }
1581     if (get_handle_info(argv[2])) {
1582         reply("NSMSG_HANDLE_EXISTS", argv[2]);
1583         return 0;
1584     }
1585
1586     dict_remove2(nickserv_handle_dict, old_handle = hi->handle, 1);
1587     hi->handle = strdup(argv[2]);
1588     dict_insert(nickserv_handle_dict, hi->handle, hi);
1589     for (nn=0; nn<rf_list_used; nn++)
1590         rf_list[nn](hi, old_handle);
1591     snprintf(msgbuf, sizeof(msgbuf), "%s renamed account %s to %s.", user->handle_info->handle, old_handle, hi->handle);
1592     reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
1593     global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
1594     free(old_handle);
1595     return 1;
1596 }
1597
1598 static failpw_func_t *failpw_func_list;
1599 static unsigned int failpw_func_size = 0, failpw_func_used = 0;
1600
1601 void
1602 reg_failpw_func(failpw_func_t func)
1603 {
1604     if (failpw_func_used == failpw_func_size) {
1605         if (failpw_func_size) {
1606             failpw_func_size <<= 1;
1607             failpw_func_list = realloc(failpw_func_list, failpw_func_size*sizeof(failpw_func_t));
1608         } else {
1609             failpw_func_size = 8;
1610             failpw_func_list = malloc(failpw_func_size*sizeof(failpw_func_t));
1611         }
1612     }
1613     failpw_func_list[failpw_func_used++] = func;
1614 }
1615
1616 static NICKSERV_FUNC(cmd_auth)
1617 {
1618     int pw_arg, used, maxlogins;
1619     struct handle_info *hi;
1620     const char *passwd;
1621     struct userNode *other;
1622
1623     if (user->handle_info) {
1624         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1625         return 0;
1626     }
1627     if (IsStamped(user)) {
1628         /* Unauthenticated users might still have been stamped
1629            previously and could therefore have a hidden host;
1630            do not allow them to authenticate. */
1631         reply("NSMSG_STAMPED_AUTH");
1632         return 0;
1633     }
1634     if (argc == 3) {
1635         hi = dict_find(nickserv_handle_dict, argv[1], NULL);
1636         pw_arg = 2;
1637     } else if (argc == 2) {
1638         if (nickserv_conf.disable_nicks) {
1639             if (!(hi = get_handle_info(user->nick))) {
1640                 reply("NSMSG_HANDLE_NOT_FOUND");
1641                 return 0;
1642             }
1643         } else {
1644             /* try to look up their handle from their nick */
1645             struct nick_info *ni;
1646             ni = get_nick_info(user->nick);
1647             if (!ni) {
1648                 reply("NSMSG_NICK_NOT_REGISTERED", user->nick);
1649                 return 0;
1650             }
1651             hi = ni->owner;
1652         }
1653         pw_arg = 1;
1654     } else {
1655         reply("MSG_MISSING_PARAMS", argv[0]);
1656         svccmd_send_help(user, nickserv, cmd);
1657         return 0;
1658     }
1659     if (!hi) {
1660         reply("NSMSG_HANDLE_NOT_FOUND");
1661         return 0;
1662     }
1663     /* Responses from here on look up the language used by the handle they asked about. */
1664     passwd = argv[pw_arg];
1665     if (!valid_user_for(user, hi)) {
1666         if (hi->email_addr && nickserv_conf.email_enabled)
1667             send_message_type(4, user, cmd->parent->bot,
1668                               handle_find_message(hi, "NSMSG_USE_AUTHCOOKIE"),
1669                               hi->handle);
1670         else
1671             send_message_type(4, user, cmd->parent->bot,
1672                               handle_find_message(hi, "NSMSG_HOSTMASK_INVALID"),
1673                               hi->handle);
1674         argv[pw_arg] = "BADMASK";
1675         return 1;
1676     }
1677     if (!checkpass(passwd, hi->passwd)) {
1678         unsigned int n;
1679         send_message_type(4, user, cmd->parent->bot,
1680                           handle_find_message(hi, "NSMSG_PASSWORD_INVALID"));
1681         argv[pw_arg] = "BADPASS";
1682         for (n=0; n<failpw_func_used; n++) failpw_func_list[n](user, hi);
1683         if (nickserv_conf.autogag_enabled) {
1684             if (!user->auth_policer.params) {
1685                 user->auth_policer.last_req = now;
1686                 user->auth_policer.params = nickserv_conf.auth_policer_params;
1687             }
1688             if (!policer_conforms(&user->auth_policer, now, 1.0)) {
1689                 char *hostmask;
1690                 hostmask = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_BYIP|GENMASK_NO_HIDING);
1691                 log_module(NS_LOG, LOG_INFO, "%s auto-gagged for repeated password guessing.", hostmask);
1692                 gag_create(hostmask, nickserv->nick, "Repeated password guessing.", now+nickserv_conf.autogag_duration);
1693                 free(hostmask);
1694                 argv[pw_arg] = "GAGGED";
1695             }
1696         }
1697         return 1;
1698     }
1699     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1700         send_message_type(4, user, cmd->parent->bot,
1701                           handle_find_message(hi, "NSMSG_HANDLE_SUSPENDED"));
1702         argv[pw_arg] = "SUSPENDED";
1703         return 1;
1704     }
1705     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
1706     for (used = 0, other = hi->users; other; other = other->next_authed) {
1707         if (++used >= maxlogins) {
1708             send_message_type(4, user, cmd->parent->bot,
1709                               handle_find_message(hi, "NSMSG_MAX_LOGINS"),
1710                               maxlogins);
1711             argv[pw_arg] = "MAXLOGINS";
1712             return 1;
1713         }
1714     }
1715
1716     set_user_handle_info(user, hi, 1);
1717     if (nickserv_conf.email_required && !hi->email_addr)
1718         reply("NSMSG_PLEASE_SET_EMAIL");
1719     if (!is_secure_password(hi->handle, passwd, NULL))
1720         reply("NSMSG_WEAK_PASSWORD");
1721     if (hi->passwd[0] != '$')
1722         cryptpass(passwd, hi->passwd);
1723     if (!hi->masks->used) {
1724         irc_in_addr_t ip;
1725         string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1726         if (irc_in_addr_is_valid(user->ip) && irc_pton(&ip, NULL, user->hostname))
1727             string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1728     }
1729     argv[pw_arg] = "****";
1730     reply("NSMSG_AUTH_SUCCESS");
1731     return 1;
1732 }
1733
1734 static allowauth_func_t *allowauth_func_list;
1735 static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
1736
1737 void
1738 reg_allowauth_func(allowauth_func_t func)
1739 {
1740     if (allowauth_func_used == allowauth_func_size) {
1741         if (allowauth_func_size) {
1742             allowauth_func_size <<= 1;
1743             allowauth_func_list = realloc(allowauth_func_list, allowauth_func_size*sizeof(allowauth_func_t));
1744         } else {
1745             allowauth_func_size = 8;
1746             allowauth_func_list = malloc(allowauth_func_size*sizeof(allowauth_func_t));
1747         }
1748     }
1749     allowauth_func_list[allowauth_func_used++] = func;
1750 }
1751
1752 static NICKSERV_FUNC(cmd_allowauth)
1753 {
1754     struct userNode *target;
1755     struct handle_info *hi;
1756     unsigned int n;
1757
1758     NICKSERV_MIN_PARMS(2);
1759     if (!(target = GetUserH(argv[1]))) {
1760         reply("MSG_NICK_UNKNOWN", argv[1]);
1761         return 0;
1762     }
1763     if (target->handle_info) {
1764         reply("NSMSG_USER_PREV_AUTH", target->nick);
1765         return 0;
1766     }
1767     if (IsStamped(target)) {
1768         /* Unauthenticated users might still have been stamped
1769            previously and could therefore have a hidden host;
1770            do not allow them to authenticate to an account. */
1771         reply("NSMSG_USER_PREV_STAMP", target->nick);
1772         return 0;
1773     }
1774     if (argc == 2)
1775         hi = NULL;
1776     else if (!(hi = get_handle_info(argv[2]))) {
1777         reply("MSG_HANDLE_UNKNOWN", argv[2]);
1778         return 0;
1779     }
1780     if (hi) {
1781         if (hi->opserv_level > user->handle_info->opserv_level) {
1782             reply("MSG_USER_OUTRANKED", hi->handle);
1783             return 0;
1784         }
1785         if (((hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER))
1786              || (hi->opserv_level > 0))
1787             && ((argc < 4) || irccasecmp(argv[3], "staff"))) {
1788             reply("NSMSG_ALLOWAUTH_STAFF", hi->handle);
1789             return 0;
1790         }
1791         dict_insert(nickserv_allow_auth_dict, target->nick, hi);
1792         reply("NSMSG_AUTH_ALLOWED", target->nick, hi->handle);
1793         send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_MSG", hi->handle, hi->handle);
1794         if (nickserv_conf.email_enabled)
1795             send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_EMAIL");
1796     } else {
1797         if (dict_remove(nickserv_allow_auth_dict, target->nick))
1798             reply("NSMSG_AUTH_NORMAL_ONLY", target->nick);
1799         else
1800             reply("NSMSG_AUTH_UNSPECIAL", target->nick);
1801     }
1802     for (n=0; n<allowauth_func_used; n++)
1803         allowauth_func_list[n](user, target, hi);
1804     return 1;
1805 }
1806
1807 static NICKSERV_FUNC(cmd_authcookie)
1808 {
1809     struct handle_info *hi;
1810
1811     NICKSERV_MIN_PARMS(2);
1812     if (user->handle_info) {
1813         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1814         return 0;
1815     }
1816     if (IsStamped(user)) {
1817         /* Unauthenticated users might still have been stamped
1818            previously and could therefore have a hidden host;
1819            do not allow them to authenticate to an account. */
1820         reply("NSMSG_STAMPED_AUTHCOOKIE");
1821         return 0;
1822     }
1823     if (!(hi = get_handle_info(argv[1]))) {
1824         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1825         return 0;
1826     }
1827     if (!hi->email_addr) {
1828         reply("MSG_SET_EMAIL_ADDR");
1829         return 0;
1830     }
1831     nickserv_make_cookie(user, hi, ALLOWAUTH, NULL);
1832     return 1;
1833 }
1834
1835 static NICKSERV_FUNC(cmd_delcookie)
1836 {
1837     struct handle_info *hi;
1838
1839     hi = user->handle_info;
1840     if (!hi->cookie) {
1841         reply("NSMSG_NO_COOKIE");
1842         return 0;
1843     }
1844     switch (hi->cookie->type) {
1845     case ACTIVATION:
1846     case EMAIL_CHANGE:
1847         reply("NSMSG_MUST_TIME_OUT");
1848         break;
1849     default:
1850         nickserv_eat_cookie(hi->cookie);
1851         reply("NSMSG_ATE_COOKIE");
1852         break;
1853     }
1854     return 1;
1855 }
1856
1857 static NICKSERV_FUNC(cmd_odelcookie)
1858 {
1859     struct handle_info *hi;
1860
1861     NICKSERV_MIN_PARMS(2);
1862
1863     if (!(hi = get_victim_oper(user, argv[1])))
1864         return 0;
1865
1866     if (!hi->cookie) {
1867         reply("NSMSG_NO_COOKIE_FOREIGN", hi->handle);
1868         return 0;
1869     }
1870
1871     nickserv_eat_cookie(hi->cookie);
1872     reply("NSMSG_ATE_COOKIE_FOREIGN", hi->handle);
1873     return 1;
1874 }
1875
1876
1877 static NICKSERV_FUNC(cmd_resetpass)
1878 {
1879     struct handle_info *hi;
1880     char crypted[MD5_CRYPT_LENGTH];
1881
1882     NICKSERV_MIN_PARMS(3);
1883     if (user->handle_info) {
1884         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1885         return 0;
1886     }
1887     if (IsStamped(user)) {
1888         /* Unauthenticated users might still have been stamped
1889            previously and could therefore have a hidden host;
1890            do not allow them to activate an account. */
1891         reply("NSMSG_STAMPED_RESETPASS");
1892         return 0;
1893     }
1894     if (!(hi = get_handle_info(argv[1]))) {
1895         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1896         return 0;
1897     }
1898     if (!hi->email_addr) {
1899         reply("MSG_SET_EMAIL_ADDR");
1900         return 0;
1901     }
1902     cryptpass(argv[2], crypted);
1903     argv[2] = "****";
1904     nickserv_make_cookie(user, hi, PASSWORD_CHANGE, crypted);
1905     return 1;
1906 }
1907
1908 static NICKSERV_FUNC(cmd_cookie)
1909 {
1910     struct handle_info *hi;
1911     const char *cookie;
1912
1913     if ((argc == 2) && (hi = user->handle_info) && hi->cookie && (hi->cookie->type == EMAIL_CHANGE)) {
1914         cookie = argv[1];
1915     } else {
1916         NICKSERV_MIN_PARMS(3);
1917         if (!(hi = get_handle_info(argv[1]))) {
1918             reply("MSG_HANDLE_UNKNOWN", argv[1]);
1919             return 0;
1920         }
1921         cookie = argv[2];
1922     }
1923
1924     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1925         reply("NSMSG_HANDLE_SUSPENDED");
1926         return 0;
1927     }
1928
1929     if (!hi->cookie) {
1930         reply("NSMSG_NO_COOKIE");
1931         return 0;
1932     }
1933
1934     /* Check validity of operation before comparing cookie to
1935      * prohibit guessing by authed users. */
1936     if (user->handle_info
1937         && (hi->cookie->type != EMAIL_CHANGE)
1938         && (hi->cookie->type != PASSWORD_CHANGE)) {
1939         reply("NSMSG_CANNOT_COOKIE");
1940         return 0;
1941     }
1942
1943     if (strcmp(cookie, hi->cookie->cookie)) {
1944         reply("NSMSG_BAD_COOKIE");
1945         return 0;
1946     }
1947
1948     switch (hi->cookie->type) {
1949     case ACTIVATION:
1950         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1951         set_user_handle_info(user, hi, 1);
1952         reply("NSMSG_HANDLE_ACTIVATED");
1953         break;
1954     case PASSWORD_CHANGE:
1955         set_user_handle_info(user, hi, 1);
1956         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1957         reply("NSMSG_PASSWORD_CHANGED");
1958         break;
1959     case EMAIL_CHANGE:
1960         nickserv_set_email_addr(hi, hi->cookie->data);
1961         reply("NSMSG_EMAIL_CHANGED");
1962         break;
1963     case ALLOWAUTH: {
1964         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
1965         set_user_handle_info(user, hi, 1);
1966         nickserv_addmask(user, hi, mask);
1967         reply("NSMSG_AUTH_SUCCESS");
1968         free(mask);
1969         break;
1970     }
1971     default:
1972         reply("NSMSG_BAD_COOKIE_TYPE", hi->cookie->type);
1973         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d for account %s.", hi->cookie->type, hi->handle);
1974         break;
1975     }
1976
1977     nickserv_eat_cookie(hi->cookie);
1978
1979     return 1;
1980 }
1981
1982 static NICKSERV_FUNC(cmd_oregnick) {
1983     const char *nick;
1984     struct handle_info *target;
1985     struct nick_info *ni;
1986
1987     NICKSERV_MIN_PARMS(3);
1988     if (!(target = modcmd_get_handle_info(user, argv[1])))
1989         return 0;
1990     nick = argv[2];
1991     if (!is_registerable_nick(nick)) {
1992         reply("NSMSG_BAD_NICK", nick);
1993         return 0;
1994     }
1995     ni = dict_find(nickserv_nick_dict, nick, NULL);
1996     if (ni) {
1997         reply("NSMSG_NICK_EXISTS", nick);
1998         return 0;
1999     }
2000     register_nick(nick, target);
2001     reply("NSMSG_OREGNICK_SUCCESS", nick, target->handle);
2002     return 1;
2003 }
2004
2005 static NICKSERV_FUNC(cmd_regnick) {
2006     unsigned n;
2007     struct nick_info *ni;
2008
2009     if (!is_registerable_nick(user->nick)) {
2010         reply("NSMSG_BAD_NICK", user->nick);
2011         return 0;
2012     }
2013     /* count their nicks, see if it's too many */
2014     for (n=0,ni=user->handle_info->nicks; ni; n++,ni=ni->next) ;
2015     if (n >= nickserv_conf.nicks_per_handle) {
2016         reply("NSMSG_TOO_MANY_NICKS");
2017         return 0;
2018     }
2019     ni = dict_find(nickserv_nick_dict, user->nick, NULL);
2020     if (ni) {
2021         reply("NSMSG_NICK_EXISTS", user->nick);
2022         return 0;
2023     }
2024     register_nick(user->nick, user->handle_info);
2025     reply("NSMSG_REGNICK_SUCCESS", user->nick);
2026     return 1;
2027 }
2028
2029 static NICKSERV_FUNC(cmd_pass)
2030 {
2031     struct handle_info *hi;
2032     const char *old_pass, *new_pass;
2033
2034     NICKSERV_MIN_PARMS(3);
2035     hi = user->handle_info;
2036     old_pass = argv[1];
2037     new_pass = argv[2];
2038     argv[2] = "****";
2039     if (!is_secure_password(hi->handle, new_pass, user)) return 0;
2040     if (!checkpass(old_pass, hi->passwd)) {
2041         argv[1] = "BADPASS";
2042         reply("NSMSG_PASSWORD_INVALID");
2043         return 0;
2044     }
2045     cryptpass(new_pass, hi->passwd);
2046     argv[1] = "****";
2047     reply("NSMSG_PASS_SUCCESS");
2048     return 1;
2049 }
2050
2051 static int
2052 nickserv_addmask(struct userNode *user, struct handle_info *hi, const char *mask)
2053 {
2054     unsigned int i;
2055     char *new_mask = canonicalize_hostmask(strdup(mask));
2056     for (i=0; i<hi->masks->used; i++) {
2057         if (!irccasecmp(new_mask, hi->masks->list[i])) {
2058             send_message(user, nickserv, "NSMSG_ADDMASK_ALREADY", new_mask);
2059             free(new_mask);
2060             return 0;
2061         }
2062     }
2063     string_list_append(hi->masks, new_mask);
2064     send_message(user, nickserv, "NSMSG_ADDMASK_SUCCESS", new_mask);
2065     return 1;
2066 }
2067
2068 static NICKSERV_FUNC(cmd_addmask)
2069 {
2070     if (argc < 2) {
2071         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
2072         int res = nickserv_addmask(user, user->handle_info, mask);
2073         free(mask);
2074         return res;
2075     } else {
2076         if (!is_gline(argv[1])) {
2077             reply("NSMSG_MASK_INVALID", argv[1]);
2078             return 0;
2079         }
2080         return nickserv_addmask(user, user->handle_info, argv[1]);
2081     }
2082 }
2083
2084 static NICKSERV_FUNC(cmd_oaddmask)
2085 {
2086     struct handle_info *hi;
2087
2088     NICKSERV_MIN_PARMS(3);
2089     if (!(hi = get_victim_oper(user, argv[1])))
2090         return 0;
2091     return nickserv_addmask(user, hi, argv[2]);
2092 }
2093
2094 static int
2095 nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask, int force)
2096 {
2097     unsigned int i;
2098     for (i=0; i<hi->masks->used; i++) {
2099         if (!strcmp(del_mask, hi->masks->list[i])) {
2100             char *old_mask = hi->masks->list[i];
2101             if (hi->masks->used == 1 && !force) {
2102                 send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
2103                 return 0;
2104             }
2105             hi->masks->list[i] = hi->masks->list[--hi->masks->used];
2106             send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
2107             free(old_mask);
2108             return 1;
2109         }
2110     }
2111     send_message(user, nickserv, "NSMSG_DELMASK_NOT_FOUND");
2112     return 0;
2113 }
2114
2115 static NICKSERV_FUNC(cmd_delmask)
2116 {
2117     NICKSERV_MIN_PARMS(2);
2118     return nickserv_delmask(user, user->handle_info, argv[1], 0);
2119 }
2120
2121 static NICKSERV_FUNC(cmd_odelmask)
2122 {
2123     struct handle_info *hi;
2124     NICKSERV_MIN_PARMS(3);
2125     if (!(hi = get_victim_oper(user, argv[1])))
2126         return 0;
2127     return nickserv_delmask(user, hi, argv[2], 1);
2128 }
2129
2130 int
2131 nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *padded, unsigned long *premoved) {
2132     unsigned int nn, add = 1, pos;
2133     unsigned long added, removed, flag;
2134
2135     for (added=removed=nn=0; str[nn]; nn++) {
2136         switch (str[nn]) {
2137         case '+': add = 1; break;
2138         case '-': add = 0; break;
2139         default:
2140             if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
2141                 send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
2142                 return 0;
2143             }
2144             if (user && (user->handle_info->opserv_level < flag_access_levels[pos-1])) {
2145                 /* cheesy avoidance of looking up the flag name.. */
2146                 send_message(user, bot, "NSMSG_FLAG_PRIVILEGED", str[nn]);
2147                 return 0;
2148             }
2149             flag = 1 << (pos - 1);
2150             if (add)
2151                 added |= flag, removed &= ~flag;
2152             else
2153                 removed |= flag, added &= ~flag;
2154             break;
2155         }
2156     }
2157     *padded = added;
2158     *premoved = removed;
2159     return 1;
2160 }
2161
2162 static int
2163 nickserv_apply_flags(struct userNode *user, struct handle_info *hi, const char *flags)
2164 {
2165     unsigned long before, after, added, removed;
2166     struct userNode *uNode;
2167
2168     before = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2169     if (!nickserv_modify_handle_flags(user, nickserv, flags, &added, &removed))
2170         return 0;
2171     hi->flags = (hi->flags | added) & ~removed;
2172     after = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2173
2174     /* Strip helping flag if they're only a support helper and not
2175      * currently in #support. */
2176     if (HANDLE_FLAGGED(hi, HELPING) && (after == HI_FLAG_SUPPORT_HELPER)) {
2177         struct channelList *schannels;
2178         unsigned int ii;
2179         schannels = chanserv_support_channels();
2180         for (ii = 0; ii < schannels->used; ++ii)
2181             if (find_handle_in_channel(schannels->list[ii], hi, NULL))
2182                 break;
2183         if (ii == schannels->used)
2184             HANDLE_CLEAR_FLAG(hi, HELPING);
2185     }
2186
2187     if (after && !before) {
2188         /* Add user to current helper list. */
2189         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2190             userList_append(&curr_helpers, uNode);
2191     } else if (!after && before) {
2192         /* Remove user from current helper list. */
2193         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2194             userList_remove(&curr_helpers, uNode);
2195     }
2196
2197     return 1;
2198 }
2199
2200 static void
2201 set_list(struct userNode *user, struct handle_info *hi, int override)
2202 {
2203     option_func_t *opt;
2204     unsigned int i;
2205     char *set_display[] = {
2206         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
2207         "EMAIL", "MAXLOGINS", "LANGUAGE"
2208     };
2209
2210     send_message(user, nickserv, "NSMSG_SETTING_LIST");
2211
2212     /* Do this so options are presented in a consistent order. */
2213     for (i = 0; i < ArrayLength(set_display); ++i)
2214         if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
2215             opt(user, hi, override, 0, NULL);
2216 }
2217
2218 static NICKSERV_FUNC(cmd_set)
2219 {
2220     struct handle_info *hi;
2221     option_func_t *opt;
2222
2223     hi = user->handle_info;
2224     if (argc < 2) {
2225         set_list(user, hi, 0);
2226         return 1;
2227     }
2228     if (!(opt = dict_find(nickserv_opt_dict, argv[1], NULL))) {
2229         reply("NSMSG_INVALID_OPTION", argv[1]);
2230         return 0;
2231     }
2232     return opt(user, hi, 0, argc-1, argv+1);
2233 }
2234
2235 static NICKSERV_FUNC(cmd_oset)
2236 {
2237     struct handle_info *hi;
2238     struct svccmd *subcmd;
2239     option_func_t *opt;
2240     char cmdname[MAXLEN];
2241
2242     NICKSERV_MIN_PARMS(2);
2243
2244     if (!(hi = get_victim_oper(user, argv[1])))
2245         return 0;
2246
2247     if (argc < 3) {
2248         set_list(user, hi, 0);
2249         return 1;
2250     }
2251
2252     if (!(opt = dict_find(nickserv_opt_dict, argv[2], NULL))) {
2253         reply("NSMSG_INVALID_OPTION", argv[2]);
2254         return 0;
2255     }
2256
2257     sprintf(cmdname, "%s %s", cmd->name, argv[2]);
2258     subcmd = dict_find(cmd->parent->commands, cmdname, NULL);
2259     if (subcmd && !svccmd_can_invoke(user, cmd->parent->bot, subcmd, NULL, SVCCMD_NOISY))
2260         return 0;
2261
2262     return opt(user, hi, 1, argc-2, argv+2);
2263 }
2264
2265 static OPTION_FUNC(opt_info)
2266 {
2267     const char *info;
2268     if (argc > 1) {
2269         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2270             free(hi->infoline);
2271             hi->infoline = NULL;
2272         } else {
2273             hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
2274         }
2275     }
2276
2277     info = hi->infoline ? hi->infoline : user_find_message(user, "MSG_NONE");
2278     send_message(user, nickserv, "NSMSG_SET_INFO", info);
2279     return 1;
2280 }
2281
2282 static OPTION_FUNC(opt_width)
2283 {
2284     if (argc > 1)
2285         hi->screen_width = strtoul(argv[1], NULL, 0);
2286
2287     if ((hi->screen_width > 0) && (hi->screen_width < MIN_LINE_SIZE))
2288         hi->screen_width = MIN_LINE_SIZE;
2289     else if (hi->screen_width > MAX_LINE_SIZE)
2290         hi->screen_width = MAX_LINE_SIZE;
2291
2292     send_message(user, nickserv, "NSMSG_SET_WIDTH", hi->screen_width);
2293     return 1;
2294 }
2295
2296 static OPTION_FUNC(opt_tablewidth)
2297 {
2298     if (argc > 1)
2299         hi->table_width = strtoul(argv[1], NULL, 0);
2300
2301     if ((hi->table_width > 0) && (hi->table_width < MIN_LINE_SIZE))
2302         hi->table_width = MIN_LINE_SIZE;
2303     else if (hi->screen_width > MAX_LINE_SIZE)
2304         hi->table_width = MAX_LINE_SIZE;
2305
2306     send_message(user, nickserv, "NSMSG_SET_TABLEWIDTH", hi->table_width);
2307     return 1;
2308 }
2309
2310 static OPTION_FUNC(opt_color)
2311 {
2312     if (argc > 1) {
2313         if (enabled_string(argv[1]))
2314             HANDLE_SET_FLAG(hi, MIRC_COLOR);
2315         else if (disabled_string(argv[1]))
2316             HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
2317         else {
2318             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2319             return 0;
2320         }
2321     }
2322
2323     send_message(user, nickserv, "NSMSG_SET_COLOR", user_find_message(user, HANDLE_FLAGGED(hi, MIRC_COLOR) ? "MSG_ON" : "MSG_OFF"));
2324     return 1;
2325 }
2326
2327 static OPTION_FUNC(opt_privmsg)
2328 {
2329     if (argc > 1) {
2330         if (enabled_string(argv[1]))
2331             HANDLE_SET_FLAG(hi, USE_PRIVMSG);
2332         else if (disabled_string(argv[1]))
2333             HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
2334         else {
2335             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2336             return 0;
2337         }
2338     }
2339
2340     send_message(user, nickserv, "NSMSG_SET_PRIVMSG", user_find_message(user, HANDLE_FLAGGED(hi, USE_PRIVMSG) ? "MSG_ON" : "MSG_OFF"));
2341     return 1;
2342 }
2343
2344 static OPTION_FUNC(opt_style)
2345 {
2346     char *style;
2347
2348     if (argc > 1) {
2349         if (!irccasecmp(argv[1], "Zoot"))
2350             hi->userlist_style = HI_STYLE_ZOOT;
2351         else if (!irccasecmp(argv[1], "def"))
2352             hi->userlist_style = HI_STYLE_DEF;
2353     }
2354
2355     switch (hi->userlist_style) {
2356     case HI_STYLE_DEF:
2357         style = "def";
2358         break;
2359     case HI_STYLE_ZOOT:
2360     default:
2361         style = "Zoot";
2362     }
2363
2364     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
2365     return 1;
2366 }
2367
2368 static OPTION_FUNC(opt_password)
2369 {
2370     if (!override) {
2371         send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
2372         return 0;
2373     }
2374
2375     if (argc > 1)
2376         cryptpass(argv[1], hi->passwd);
2377
2378     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
2379     return 1;
2380 }
2381
2382 static OPTION_FUNC(opt_flags)
2383 {
2384     char flags[33];
2385     unsigned int ii, flen;
2386
2387     if (!override) {
2388         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2389         return 0;
2390     }
2391
2392     if (argc > 1)
2393         nickserv_apply_flags(user, hi, argv[1]);
2394
2395     for (ii = flen = 0; handle_flags[ii]; ii++)
2396         if (hi->flags & (1 << ii))
2397             flags[flen++] = handle_flags[ii];
2398     flags[flen] = '\0';
2399     if (hi->flags)
2400         send_message(user, nickserv, "NSMSG_SET_FLAGS", flags);
2401     else
2402         send_message(user, nickserv, "NSMSG_SET_FLAGS", user_find_message(user, "MSG_NONE"));
2403     return 1;
2404 }
2405
2406 static OPTION_FUNC(opt_email)
2407 {
2408     if (argc > 1) {
2409         const char *str;
2410         if (!is_valid_email_addr(argv[1])) {
2411             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
2412             return 0;
2413         }
2414         if ((str = mail_prohibited_address(argv[1]))) {
2415             send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", argv[1], str);
2416             return 0;
2417         }
2418         if (hi->email_addr && !irccasecmp(hi->email_addr, argv[1]))
2419             send_message(user, nickserv, "NSMSG_EMAIL_SAME");
2420         else if (!override)
2421                 nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1]);
2422         else {
2423             nickserv_set_email_addr(hi, argv[1]);
2424             if (hi->cookie)
2425                 nickserv_eat_cookie(hi->cookie);
2426             send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2427         }
2428     } else
2429         send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2430     return 1;
2431 }
2432
2433 static OPTION_FUNC(opt_maxlogins)
2434 {
2435     unsigned char maxlogins;
2436     if (argc > 1) {
2437         maxlogins = strtoul(argv[1], NULL, 0);
2438         if ((maxlogins > nickserv_conf.hard_maxlogins) && !override) {
2439             send_message(user, nickserv, "NSMSG_BAD_MAX_LOGINS", nickserv_conf.hard_maxlogins);
2440             return 0;
2441         }
2442         hi->maxlogins = maxlogins;
2443     }
2444     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
2445     send_message(user, nickserv, "NSMSG_SET_MAXLOGINS", maxlogins);
2446     return 1;
2447 }
2448
2449 static OPTION_FUNC(opt_language)
2450 {
2451     struct language *lang;
2452     if (argc > 1) {
2453         lang = language_find(argv[1]);
2454         if (irccasecmp(lang->name, argv[1]))
2455             send_message(user, nickserv, "NSMSG_LANGUAGE_NOT_FOUND", argv[1], lang->name);
2456         hi->language = lang;
2457     }
2458     send_message(user, nickserv, "NSMSG_SET_LANGUAGE", hi->language->name);
2459     return 1;
2460 }
2461
2462 static OPTION_FUNC(opt_karma)
2463 {
2464     if (!override) {
2465         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2466         return 0;
2467     }
2468
2469     if (argc > 1) {
2470         if (argv[1][0] == '+' && isdigit(argv[1][1])) {
2471             hi->karma += strtoul(argv[1] + 1, NULL, 10);
2472         } else if (argv[1][0] == '-' && isdigit(argv[1][1])) {
2473             hi->karma -= strtoul(argv[1] + 1, NULL, 10);
2474         } else {
2475             send_message(user, nickserv, "NSMSG_INVALID_KARMA", argv[1]);
2476         }
2477     }
2478
2479     send_message(user, nickserv, "NSMSG_SET_KARMA", hi->karma);
2480     return 1;
2481 }
2482
2483 int
2484 oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
2485     if (!oper_has_access(user, bot, nickserv_conf.modoper_level, 0))
2486         return 0;
2487     if ((user->handle_info->opserv_level < target->opserv_level)
2488         || ((user->handle_info->opserv_level == target->opserv_level)
2489             && (user->handle_info->opserv_level < 1000))) {
2490         send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
2491         return 0;
2492     }
2493     if ((user->handle_info->opserv_level < new_level)
2494         || ((user->handle_info->opserv_level == new_level)
2495             && (user->handle_info->opserv_level < 1000))) {
2496         send_message(user, bot, "NSMSG_OPSERV_LEVEL_BAD");
2497         return 0;
2498     }
2499     if (user->handle_info == target) {
2500         send_message(user, bot, "MSG_STUPID_ACCESS_CHANGE");
2501         return 0;
2502     }
2503     if (target->opserv_level == new_level)
2504         return 0;
2505     log_module(NS_LOG, LOG_INFO, "Account %s setting oper level for account %s to %d (from %d).",
2506         user->handle_info->handle, target->handle, new_level, target->opserv_level);
2507     target->opserv_level = new_level;
2508     return 1;
2509 }
2510
2511 static OPTION_FUNC(opt_level)
2512 {
2513     int res;
2514
2515     if (!override) {
2516         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2517         return 0;
2518     }
2519
2520     res = (argc > 1) ? oper_try_set_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
2521     send_message(user, nickserv, "NSMSG_SET_LEVEL", hi->opserv_level);
2522     return res;
2523 }
2524
2525 static OPTION_FUNC(opt_epithet)
2526 {
2527     if (!override) {
2528         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2529         return 0;
2530     }
2531
2532     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_epithet_level, 0)) {
2533         char *epithet = unsplit_string(argv+1, argc-1, NULL);
2534         if (hi->epithet)
2535             free(hi->epithet);
2536         if ((epithet[0] == '*') && !epithet[1])
2537             hi->epithet = NULL;
2538         else
2539             hi->epithet = strdup(epithet);
2540     }
2541
2542     if (hi->epithet)
2543         send_message(user, nickserv, "NSMSG_SET_EPITHET", hi->epithet);
2544     else
2545         send_message(user, nickserv, "NSMSG_SET_EPITHET", user_find_message(user, "MSG_NONE"));
2546     return 1;
2547 }
2548
2549 static OPTION_FUNC(opt_title)
2550 {
2551     const char *title;
2552
2553     if (!override) {
2554         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2555         return 0;
2556     }
2557
2558     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_title_level, 0)) {
2559         title = argv[1];
2560         if (strchr(title, '.')) {
2561             send_message(user, nickserv, "NSMSG_TITLE_INVALID");
2562             return 0;
2563         }
2564         if ((strlen(user->handle_info->handle) + strlen(title) +
2565              strlen(nickserv_conf.titlehost_suffix) + 2) > HOSTLEN) {
2566             send_message(user, nickserv, "NSMSG_TITLE_TRUNCATED");
2567             return 0;
2568         }
2569
2570         free(hi->fakehost);
2571         if (!strcmp(title, "*")) {
2572             hi->fakehost = NULL;
2573         } else {
2574             hi->fakehost = malloc(strlen(title)+2);
2575             hi->fakehost[0] = '.';
2576             strcpy(hi->fakehost+1, title);
2577         }
2578         apply_fakehost(hi);
2579     } else if (hi->fakehost && (hi->fakehost[0] == '.'))
2580         title = hi->fakehost + 1;
2581     else
2582         title = NULL;
2583     if (!title)
2584         title = user_find_message(user, "MSG_NONE");
2585     send_message(user, nickserv, "NSMSG_SET_TITLE", title);
2586     return 1;
2587 }
2588
2589 static OPTION_FUNC(opt_fakehost)
2590 {
2591     const char *fake;
2592
2593     if (!override) {
2594         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2595         return 0;
2596     }
2597
2598     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakehost_level, 0)) {
2599         fake = argv[1];
2600         if ((strlen(fake) > HOSTLEN) || (fake[0] == '.')) {
2601             send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
2602             return 0;
2603         }
2604         free(hi->fakehost);
2605         if (!strcmp(fake, "*"))
2606             hi->fakehost = NULL;
2607         else
2608             hi->fakehost = strdup(fake);
2609         fake = hi->fakehost;
2610         apply_fakehost(hi);
2611     } else
2612         fake = generate_fakehost(hi);
2613     if (!fake)
2614         fake = user_find_message(user, "MSG_NONE");
2615     send_message(user, nickserv, "NSMSG_SET_FAKEHOST", fake);
2616     return 1;
2617 }
2618
2619 static NICKSERV_FUNC(cmd_reclaim)
2620 {
2621     struct handle_info *hi;
2622     struct nick_info *ni;
2623     struct userNode *victim;
2624
2625     NICKSERV_MIN_PARMS(2);
2626     hi = user->handle_info;
2627     ni = dict_find(nickserv_nick_dict, argv[1], 0);
2628     if (!ni) {
2629         reply("NSMSG_UNKNOWN_NICK", argv[1]);
2630         return 0;
2631     }
2632     if (ni->owner != user->handle_info) {
2633         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
2634         return 0;
2635     }
2636     victim = GetUserH(ni->nick);
2637     if (!victim) {
2638         reply("MSG_NICK_UNKNOWN", ni->nick);
2639         return 0;
2640     }
2641     if (victim == user) {
2642         reply("NSMSG_NICK_USER_YOU");
2643         return 0;
2644     }
2645     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
2646     switch (nickserv_conf.reclaim_action) {
2647     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
2648     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
2649     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
2650     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
2651     }
2652     return 1;
2653 }
2654
2655 static NICKSERV_FUNC(cmd_unregnick)
2656 {
2657     const char *nick;
2658     struct handle_info *hi;
2659     struct nick_info *ni;
2660
2661     hi = user->handle_info;
2662     nick = (argc < 2) ? user->nick : (const char*)argv[1];
2663     ni = dict_find(nickserv_nick_dict, nick, NULL);
2664     if (!ni) {
2665         reply("NSMSG_UNKNOWN_NICK", nick);
2666         return 0;
2667     }
2668     if (hi != ni->owner) {
2669         reply("NSMSG_NOT_YOUR_NICK", nick);
2670         return 0;
2671     }
2672     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2673     delete_nick(ni);
2674     return 1;
2675 }
2676
2677 static NICKSERV_FUNC(cmd_ounregnick)
2678 {
2679     struct nick_info *ni;
2680
2681     NICKSERV_MIN_PARMS(2);
2682     if (!(ni = get_nick_info(argv[1]))) {
2683         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
2684         return 0;
2685     }
2686     if (!oper_outranks(user, ni->owner))
2687         return 0;
2688     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2689     delete_nick(ni);
2690     return 1;
2691 }
2692
2693 static NICKSERV_FUNC(cmd_unregister)
2694 {
2695     struct handle_info *hi;
2696     char *passwd;
2697
2698     NICKSERV_MIN_PARMS(2);
2699     hi = user->handle_info;
2700     passwd = argv[1];
2701     argv[1] = "****";
2702     if (checkpass(passwd, hi->passwd)) {
2703         nickserv_unregister_handle(hi, user);
2704         return 1;
2705     } else {
2706         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
2707         reply("NSMSG_PASSWORD_INVALID");
2708         return 0;
2709     }
2710 }
2711
2712 static NICKSERV_FUNC(cmd_ounregister)
2713 {
2714     struct handle_info *hi;
2715     char reason[MAXLEN];
2716     int force;
2717
2718     NICKSERV_MIN_PARMS(2);
2719     if (!(hi = get_victim_oper(user, argv[1])))
2720         return 0;
2721
2722     if (HANDLE_FLAGGED(hi, NODELETE)) {
2723         reply("NSMSG_UNREGISTER_NODELETE", hi->handle);
2724         return 0;
2725     }
2726
2727     force = IsOper(user) && (argc > 2) && !irccasecmp(argv[2], "force");
2728     if (!force &&
2729         ((hi->flags & nickserv_conf.ounregister_flags)
2730          || hi->users
2731          || (hi->last_quit_host[0] && ((unsigned)(now - hi->lastseen) < nickserv_conf.ounregister_inactive)))) {
2732         reply((IsOper(user) ? "NSMSG_UNREGISTER_MUST_FORCE" : "NSMSG_UNREGISTER_CANNOT_FORCE"), hi->handle);
2733         return 0;
2734     }
2735
2736     snprintf(reason, sizeof(reason), "%s unregistered account %s.", user->handle_info->handle, hi->handle);
2737     global_message(MESSAGE_RECIPIENT_STAFF, reason);
2738     nickserv_unregister_handle(hi, user);
2739     return 1;
2740 }
2741
2742 static NICKSERV_FUNC(cmd_status)
2743 {
2744     if (nickserv_conf.disable_nicks) {
2745         reply("NSMSG_GLOBAL_STATS_NONICK",
2746                         dict_size(nickserv_handle_dict));
2747     } else {
2748         if (user->handle_info) {
2749             int cnt=0;
2750             struct nick_info *ni;
2751             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
2752             reply("NSMSG_HANDLE_STATS", cnt);
2753         } else {
2754             reply("NSMSG_HANDLE_NONE");
2755         }
2756         reply("NSMSG_GLOBAL_STATS",
2757               dict_size(nickserv_handle_dict),
2758               dict_size(nickserv_nick_dict));
2759     }
2760     return 1;
2761 }
2762
2763 static NICKSERV_FUNC(cmd_ghost)
2764 {
2765     struct userNode *target;
2766     char reason[MAXLEN];
2767
2768     NICKSERV_MIN_PARMS(2);
2769     if (!(target = GetUserH(argv[1]))) {
2770         reply("MSG_NICK_UNKNOWN", argv[1]);
2771         return 0;
2772     }
2773     if (target == user) {
2774         reply("NSMSG_CANNOT_GHOST_SELF");
2775         return 0;
2776     }
2777     if (!target->handle_info || (target->handle_info != user->handle_info)) {
2778         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
2779         return 0;
2780     }
2781     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
2782     DelUser(target, nickserv, 1, reason);
2783     reply("NSMSG_GHOST_KILLED", argv[1]);
2784     return 1;
2785 }
2786
2787 static NICKSERV_FUNC(cmd_vacation)
2788 {
2789     HANDLE_SET_FLAG(user->handle_info, FROZEN);
2790     reply("NSMSG_ON_VACATION");
2791     return 1;
2792 }
2793
2794 static NICKSERV_FUNC(cmd_addnote)
2795 {
2796     struct handle_info *hi;
2797     unsigned long duration;
2798     char text[MAXLEN];
2799     unsigned int id;
2800     struct handle_note *prev;
2801     struct handle_note *note;
2802
2803     /* Parse parameters and figure out values for note's fields. */
2804     NICKSERV_MIN_PARMS(4);
2805     hi = get_victim_oper(user, argv[1]);
2806     if (!hi)
2807         return 0;
2808     if(!strcmp(argv[2], "0"))
2809         duration = 0;
2810     else if(!(duration = ParseInterval(argv[2])))
2811     {
2812         reply("MSG_INVALID_DURATION", argv[2]);
2813         return 0;
2814     }
2815     if (duration > 2*365*86400) {
2816         reply("NSMSG_EXCESSIVE_DURATION", argv[2]);
2817         return 0;
2818     }
2819     unsplit_string(argv + 3, argc - 3, text);
2820     WALK_NOTES(hi, prev, note) {}
2821     id = prev ? (prev->id + 1) : 1;
2822
2823     /* Create the new note structure. */
2824     note = calloc(1, sizeof(*note) + strlen(text));
2825     note->next = NULL;
2826     note->expires = duration ? (now + duration) : 0;
2827     note->set = now;
2828     note->id = id;
2829     safestrncpy(note->setter, user->handle_info->handle, sizeof(note->setter));
2830     strcpy(note->note, text);
2831     if (prev)
2832         prev->next = note;
2833     else
2834         hi->notes = note;
2835     reply("NSMSG_NOTE_ADDED", id, hi->handle);
2836     return 1;
2837 }
2838
2839 static NICKSERV_FUNC(cmd_delnote)
2840 {
2841     struct handle_info *hi;
2842     struct handle_note *prev;
2843     struct handle_note *note;
2844     int id;
2845
2846     NICKSERV_MIN_PARMS(3);
2847     hi = get_victim_oper(user, argv[1]);
2848     if (!hi)
2849         return 0;
2850     id = strtoul(argv[2], NULL, 10);
2851     WALK_NOTES(hi, prev, note) {
2852         if (id == note->id) {
2853             if (prev)
2854                 prev->next = note->next;
2855             else
2856                 hi->notes = note->next;
2857             free(note);
2858             reply("NSMSG_NOTE_REMOVED", id, hi->handle);
2859             return 1;
2860         }
2861     }
2862     reply("NSMSG_NO_SUCH_NOTE", hi->handle, id);
2863     return 0;
2864 }
2865
2866 static int
2867 nickserv_saxdb_write(struct saxdb_context *ctx) {
2868     dict_iterator_t it;
2869     struct handle_info *hi;
2870     char flags[33];
2871
2872     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2873         hi = iter_data(it);
2874         assert(hi->id != 0);
2875         saxdb_start_record(ctx, iter_key(it), 0);
2876         if (hi->cookie) {
2877             struct handle_cookie *cookie = hi->cookie;
2878             char *type;
2879
2880             switch (cookie->type) {
2881             case ACTIVATION: type = KEY_ACTIVATION; break;
2882             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
2883             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
2884             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
2885             default: type = NULL; break;
2886             }
2887             if (type) {
2888                 saxdb_start_record(ctx, KEY_COOKIE, 0);
2889                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
2890                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
2891                 if (cookie->data)
2892                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
2893                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
2894                 saxdb_end_record(ctx);
2895             }
2896         }
2897         if (hi->notes) {
2898             struct handle_note *prev, *note;
2899             saxdb_start_record(ctx, KEY_NOTES, 0);
2900             WALK_NOTES(hi, prev, note) {
2901                 snprintf(flags, sizeof(flags), "%d", note->id);
2902                 saxdb_start_record(ctx, flags, 0);
2903                 if (note->expires)
2904                     saxdb_write_int(ctx, KEY_NOTE_EXPIRES, note->expires);
2905                 saxdb_write_int(ctx, KEY_NOTE_SET, note->set);
2906                 saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
2907                 saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
2908                 saxdb_end_record(ctx);
2909             }
2910             saxdb_end_record(ctx);
2911         }
2912         if (hi->email_addr)
2913             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
2914         if (hi->epithet)
2915             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
2916         if (hi->fakehost)
2917             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
2918         if (hi->flags) {
2919             int ii, flen;
2920
2921             for (ii=flen=0; handle_flags[ii]; ++ii)
2922                 if (hi->flags & (1 << ii))
2923                     flags[flen++] = handle_flags[ii];
2924             flags[flen] = 0;
2925             saxdb_write_string(ctx, KEY_FLAGS, flags);
2926         }
2927         saxdb_write_int(ctx, KEY_ID, hi->id);
2928         if (hi->infoline)
2929             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
2930         if (hi->last_quit_host[0])
2931             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
2932         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
2933         if (hi->karma != 0)
2934             saxdb_write_sint(ctx, KEY_KARMA, hi->karma);
2935         if (hi->masks->used)
2936             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
2937         if (hi->maxlogins)
2938             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
2939         if (hi->nicks) {
2940             struct string_list *slist;
2941             struct nick_info *ni;
2942
2943             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
2944             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
2945             saxdb_write_string_list(ctx, KEY_NICKS, slist);
2946             free(slist->list);
2947             free(slist);
2948         }
2949         if (hi->opserv_level)
2950             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
2951         if (hi->language != lang_C)
2952             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
2953         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
2954         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
2955         if (hi->screen_width)
2956             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
2957         if (hi->table_width)
2958             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
2959         flags[0] = hi->userlist_style;
2960         flags[1] = 0;
2961         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
2962         saxdb_end_record(ctx);
2963     }
2964     return 0;
2965 }
2966
2967 static handle_merge_func_t *handle_merge_func_list;
2968 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
2969
2970 void
2971 reg_handle_merge_func(handle_merge_func_t func)
2972 {
2973     if (handle_merge_func_used == handle_merge_func_size) {
2974         if (handle_merge_func_size) {
2975             handle_merge_func_size <<= 1;
2976             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
2977         } else {
2978             handle_merge_func_size = 8;
2979             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
2980         }
2981     }
2982     handle_merge_func_list[handle_merge_func_used++] = func;
2983 }
2984
2985 static NICKSERV_FUNC(cmd_merge)
2986 {
2987     struct handle_info *hi_from, *hi_to;
2988     struct userNode *last_user;
2989     struct userData *cList, *cListNext;
2990     unsigned int ii, jj, n;
2991     char buffer[MAXLEN];
2992
2993     NICKSERV_MIN_PARMS(3);
2994
2995     if (!(hi_from = get_victim_oper(user, argv[1])))
2996         return 0;
2997     if (!(hi_to = get_victim_oper(user, argv[2])))
2998         return 0;
2999     if (hi_to == hi_from) {
3000         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
3001         return 0;
3002     }
3003
3004     for (n=0; n<handle_merge_func_used; n++)
3005         handle_merge_func_list[n](user, hi_to, hi_from);
3006
3007     /* Append "from" handle's nicks to "to" handle's nick list. */
3008     if (hi_to->nicks) {
3009         struct nick_info *last_ni;
3010         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
3011         last_ni->next = hi_from->nicks;
3012     }
3013     while (hi_from->nicks) {
3014         hi_from->nicks->owner = hi_to;
3015         hi_from->nicks = hi_from->nicks->next;
3016     }
3017
3018     /* Merge the hostmasks. */
3019     for (ii=0; ii<hi_from->masks->used; ii++) {
3020         char *mask = hi_from->masks->list[ii];
3021         for (jj=0; jj<hi_to->masks->used; jj++)
3022             if (match_ircglobs(hi_to->masks->list[jj], mask))
3023                 break;
3024         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
3025             string_list_append(hi_to->masks, strdup(mask));
3026     }
3027
3028     /* Merge the lists of authed users. */
3029     if (hi_to->users) {
3030         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
3031         last_user->next_authed = hi_from->users;
3032     } else {
3033         hi_to->users = hi_from->users;
3034     }
3035     /* Repoint the old "from" handle's users. */
3036     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
3037         last_user->handle_info = hi_to;
3038     }
3039     hi_from->users = NULL;
3040
3041     /* Merge channel userlists. */
3042     for (cList=hi_from->channels; cList; cList=cListNext) {
3043         struct userData *cList2;
3044         cListNext = cList->u_next;
3045         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
3046             if (cList->channel == cList2->channel)
3047                 break;
3048         if (cList2 && (cList2->access >= cList->access)) {
3049             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);
3050             /* keep cList2 in hi_to; remove cList from hi_from */
3051             del_channel_user(cList, 1);
3052         } else {
3053             if (cList2) {
3054                 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);
3055                 /* remove the lower-ranking cList2 from hi_to */
3056                 del_channel_user(cList2, 1);
3057             } else {
3058                 log_module(NS_LOG, LOG_INFO, "Merge: %s had no access in %s", hi_to->handle, cList->channel->channel->name);
3059             }
3060             /* cList needs to be moved from hi_from to hi_to */
3061             cList->handle = hi_to;
3062             /* Remove from linked list for hi_from */
3063             assert(!cList->u_prev);
3064             hi_from->channels = cList->u_next;
3065             if (cList->u_next)
3066                 cList->u_next->u_prev = cList->u_prev;
3067             /* Add to linked list for hi_to */
3068             cList->u_prev = NULL;
3069             cList->u_next = hi_to->channels;
3070             if (hi_to->channels)
3071                 hi_to->channels->u_prev = cList;
3072             hi_to->channels = cList;
3073         }
3074     }
3075
3076     /* Do they get an OpServ level promotion? */
3077     if (hi_from->opserv_level > hi_to->opserv_level)
3078         hi_to->opserv_level = hi_from->opserv_level;
3079
3080     /* What about last seen time? */
3081     if (hi_from->lastseen > hi_to->lastseen)
3082         hi_to->lastseen = hi_from->lastseen;
3083
3084     /* New karma is the sum of the two original karmas. */
3085     hi_to->karma += hi_from->karma;
3086
3087     /* Does a fakehost carry over?  (This intentionally doesn't set it
3088      * for users previously attached to hi_to.  They'll just have to
3089      * reconnect.)
3090      */
3091     if (hi_from->fakehost && !hi_to->fakehost)
3092         hi_to->fakehost = strdup(hi_from->fakehost);
3093
3094     /* Notify of success. */
3095     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
3096     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
3097     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
3098
3099     /* Unregister the "from" handle. */
3100     nickserv_unregister_handle(hi_from, NULL);
3101
3102     return 1;
3103 }
3104
3105 struct nickserv_discrim {
3106     unsigned long flags_on, flags_off;
3107     unsigned long min_registered, max_registered;
3108     unsigned long lastseen;
3109     unsigned int limit;
3110     int min_level, max_level;
3111     int min_karma, max_karma;
3112     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
3113     const char *nickmask;
3114     const char *hostmask;
3115     const char *fakehostmask;
3116     const char *handlemask;
3117     const char *emailmask;
3118 };
3119
3120 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
3121
3122 struct discrim_apply_info {
3123     struct nickserv_discrim *discrim;
3124     discrim_search_func func;
3125     struct userNode *source;
3126     unsigned int matched;
3127 };
3128
3129 static struct nickserv_discrim *
3130 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
3131 {
3132     unsigned int i;
3133     struct nickserv_discrim *discrim;
3134
3135     discrim = malloc(sizeof(*discrim));
3136     memset(discrim, 0, sizeof(*discrim));
3137     discrim->min_level = 0;
3138     discrim->max_level = INT_MAX;
3139     discrim->limit = 50;
3140     discrim->min_registered = 0;
3141     discrim->max_registered = ULONG_MAX;
3142     discrim->lastseen = ULONG_MAX;
3143     discrim->min_karma = INT_MIN;
3144     discrim->max_karma = INT_MAX;
3145
3146     for (i=0; i<argc; i++) {
3147         if (i == argc - 1) {
3148             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3149             goto fail;
3150         }
3151         if (!irccasecmp(argv[i], "limit")) {
3152             discrim->limit = strtoul(argv[++i], NULL, 0);
3153         } else if (!irccasecmp(argv[i], "flags")) {
3154             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
3155         } else if (!irccasecmp(argv[i], "registered")) {
3156             const char *cmp = argv[++i];
3157             if (cmp[0] == '<') {
3158                 if (cmp[1] == '=') {
3159                     discrim->min_registered = now - ParseInterval(cmp+2);
3160                 } else {
3161                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
3162                 }
3163             } else if (cmp[0] == '=') {
3164                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
3165             } else if (cmp[0] == '>') {
3166                 if (cmp[1] == '=') {
3167                     discrim->max_registered = now - ParseInterval(cmp+2);
3168                 } else {
3169                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
3170                 }
3171             } else {
3172                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3173             }
3174         } else if (!irccasecmp(argv[i], "seen")) {
3175             discrim->lastseen = now - ParseInterval(argv[++i]);
3176         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
3177             discrim->nickmask = argv[++i];
3178         } else if (!irccasecmp(argv[i], "hostmask")) {
3179             i++;
3180             if (!irccasecmp(argv[i], "exact")) {
3181                 if (i == argc - 1) {
3182                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3183                     goto fail;
3184                 }
3185                 discrim->hostmask_type = EXACT;
3186             } else if (!irccasecmp(argv[i], "subset")) {
3187                 if (i == argc - 1) {
3188                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3189                     goto fail;
3190                 }
3191                 discrim->hostmask_type = SUBSET;
3192             } else if (!irccasecmp(argv[i], "superset")) {
3193                 if (i == argc - 1) {
3194                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3195                     goto fail;
3196                 }
3197                 discrim->hostmask_type = SUPERSET;
3198             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
3199                 if (i == argc - 1) {
3200                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3201                     goto fail;
3202                 }
3203                 discrim->hostmask_type = LASTQUIT;
3204             } else {
3205                 i--;
3206                 discrim->hostmask_type = SUPERSET;
3207             }
3208             discrim->hostmask = argv[++i];
3209         } else if (!irccasecmp(argv[i], "fakehost")) {
3210             if (!irccasecmp(argv[++i], "*")) {
3211                 discrim->fakehostmask = 0;
3212             } else {
3213                 discrim->fakehostmask = argv[i];
3214             }
3215         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
3216             if (!irccasecmp(argv[++i], "*")) {
3217                 discrim->handlemask = 0;
3218             } else {
3219                 discrim->handlemask = argv[i];
3220             }
3221         } else if (!irccasecmp(argv[i], "email")) {
3222             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
3223                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
3224                 goto fail;
3225             } else if (!irccasecmp(argv[++i], "*")) {
3226                 discrim->emailmask = 0;
3227             } else {
3228                 discrim->emailmask = argv[i];
3229             }
3230         } else if (!irccasecmp(argv[i], "access")) {
3231             const char *cmp = argv[++i];
3232             if (cmp[0] == '<') {
3233                 if (discrim->min_level == 0) discrim->min_level = 1;
3234                 if (cmp[1] == '=') {
3235                     discrim->max_level = strtoul(cmp+2, NULL, 0);
3236                 } else {
3237                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
3238                 }
3239             } else if (cmp[0] == '=') {
3240                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
3241             } else if (cmp[0] == '>') {
3242                 if (cmp[1] == '=') {
3243                     discrim->min_level = strtoul(cmp+2, NULL, 0);
3244                 } else {
3245                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
3246                 }
3247             } else {
3248                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3249             }
3250         } else if (!irccasecmp(argv[i], "karma")) {
3251             const char *cmp = argv[++i];
3252             if (cmp[0] == '<') {
3253                 if (cmp[1] == '=') {
3254                     discrim->max_karma = strtoul(cmp+2, NULL, 0);
3255                 } else {
3256                     discrim->max_karma = strtoul(cmp+1, NULL, 0) - 1;
3257                 }
3258             } else if (cmp[0] == '=') {
3259                 discrim->min_karma = discrim->max_karma = strtoul(cmp+1, NULL, 0);
3260             } else if (cmp[0] == '>') {
3261                 if (cmp[1] == '=') {
3262                     discrim->min_karma = strtoul(cmp+2, NULL, 0);
3263                 } else {
3264                     discrim->min_karma = strtoul(cmp+1, NULL, 0) + 1;
3265                 }
3266             } else {
3267                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3268             }
3269         } else {
3270             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
3271             goto fail;
3272         }
3273     }
3274     return discrim;
3275   fail:
3276     free(discrim);
3277     return NULL;
3278 }
3279
3280 static int
3281 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
3282 {
3283     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
3284         || (discrim->flags_off & hi->flags)
3285         || (discrim->min_registered > hi->registered)
3286         || (discrim->max_registered < hi->registered)
3287         || (discrim->lastseen < (hi->users?now:hi->lastseen))
3288         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
3289         || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
3290         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
3291         || (discrim->min_level > hi->opserv_level)
3292         || (discrim->max_level < hi->opserv_level)
3293         || (discrim->min_karma > hi->karma)
3294         || (discrim->max_karma < hi->karma)
3295         ) {
3296         return 0;
3297     }
3298     if (discrim->hostmask) {
3299         unsigned int i;
3300         for (i=0; i<hi->masks->used; i++) {
3301             const char *mask = hi->masks->list[i];
3302             if ((discrim->hostmask_type == SUBSET)
3303                 && (match_ircglobs(discrim->hostmask, mask))) break;
3304             else if ((discrim->hostmask_type == EXACT)
3305                      && !irccasecmp(discrim->hostmask, mask)) break;
3306             else if ((discrim->hostmask_type == SUPERSET)
3307                      && (match_ircglobs(mask, discrim->hostmask))) break;
3308             else if ((discrim->hostmask_type == LASTQUIT)
3309                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
3310         }
3311         if (i==hi->masks->used) return 0;
3312     }
3313     if (discrim->nickmask) {
3314         struct nick_info *nick = hi->nicks;
3315         while (nick) {
3316             if (match_ircglob(nick->nick, discrim->nickmask)) break;
3317             nick = nick->next;
3318         }
3319         if (!nick) return 0;
3320     }
3321     return 1;
3322 }
3323
3324 static unsigned int
3325 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
3326 {
3327     dict_iterator_t it, next;
3328     unsigned int matched;
3329
3330     for (it = dict_first(nickserv_handle_dict), matched = 0;
3331          it && (matched < discrim->limit);
3332          it = next) {
3333         next = iter_next(it);
3334         if (nickserv_discrim_match(discrim, iter_data(it))) {
3335             dsf(source, iter_data(it));
3336             matched++;
3337         }
3338     }
3339     return matched;
3340 }
3341
3342 static void
3343 search_print_func(struct userNode *source, struct handle_info *match)
3344 {
3345     send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
3346 }
3347
3348 static void
3349 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
3350 {
3351 }
3352
3353 static void
3354 search_unregister_func (struct userNode *source, struct handle_info *match)
3355 {
3356     if (oper_has_access(source, nickserv, match->opserv_level, 0))
3357         nickserv_unregister_handle(match, source);
3358 }
3359
3360 static int
3361 nickserv_sort_accounts_by_access(const void *a, const void *b)
3362 {
3363     const struct handle_info *hi_a = *(const struct handle_info**)a;
3364     const struct handle_info *hi_b = *(const struct handle_info**)b;
3365     if (hi_a->opserv_level != hi_b->opserv_level)
3366         return hi_b->opserv_level - hi_a->opserv_level;
3367     return irccasecmp(hi_a->handle, hi_b->handle);
3368 }
3369
3370 void
3371 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
3372 {
3373     struct handle_info_list hil;
3374     struct helpfile_table tbl;
3375     unsigned int ii;
3376     dict_iterator_t it;
3377     const char **ary;
3378
3379     memset(&hil, 0, sizeof(hil));
3380     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3381         struct handle_info *hi = iter_data(it);
3382         if (hi->opserv_level)
3383             handle_info_list_append(&hil, hi);
3384     }
3385     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
3386     tbl.length = hil.used + 1;
3387     tbl.width = 2;
3388     tbl.flags = TABLE_NO_FREE;
3389     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
3390     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
3391     ary[0] = "Account";
3392     ary[1] = "Level";
3393     for (ii = 0; ii < hil.used; ) {
3394         ary = malloc(tbl.width * sizeof(ary[0]));
3395         ary[0] = hil.list[ii]->handle;
3396         ary[1] = strtab(hil.list[ii]->opserv_level);
3397         tbl.contents[++ii] = ary;
3398     }
3399     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3400     reply("MSG_MATCH_COUNT", hil.used);
3401     for (ii = 0; ii < hil.used; ii++)
3402         free(tbl.contents[ii]);
3403     free(tbl.contents);
3404     free(hil.list);
3405 }
3406
3407 static NICKSERV_FUNC(cmd_search)
3408 {
3409     struct nickserv_discrim *discrim;
3410     discrim_search_func action;
3411     struct svccmd *subcmd;
3412     unsigned int matches;
3413     char buf[MAXLEN];
3414
3415     NICKSERV_MIN_PARMS(3);
3416     sprintf(buf, "search %s", argv[1]);
3417     subcmd = dict_find(nickserv_service->commands, buf, NULL);
3418     if (!irccasecmp(argv[1], "print"))
3419         action = search_print_func;
3420     else if (!irccasecmp(argv[1], "count"))
3421         action = search_count_func;
3422     else if (!irccasecmp(argv[1], "unregister"))
3423         action = search_unregister_func;
3424     else {
3425         reply("NSMSG_INVALID_ACTION", argv[1]);
3426         return 0;
3427     }
3428
3429     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
3430         return 0;
3431
3432     discrim = nickserv_discrim_create(user, argc-2, argv+2);
3433     if (!discrim)
3434         return 0;
3435
3436     if (action == search_print_func)
3437         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
3438     else if (action == search_count_func)
3439         discrim->limit = INT_MAX;
3440
3441     matches = nickserv_discrim_search(discrim, action, user);
3442
3443     if (matches)
3444         reply("MSG_MATCH_COUNT", matches);
3445     else
3446         reply("MSG_NO_MATCHES");
3447
3448     free(discrim);
3449     return 0;
3450 }
3451
3452 static MODCMD_FUNC(cmd_checkpass)
3453 {
3454     struct handle_info *hi;
3455
3456     NICKSERV_MIN_PARMS(3);
3457     if (!(hi = get_handle_info(argv[1]))) {
3458         reply("MSG_HANDLE_UNKNOWN", argv[1]);
3459         return 0;
3460     }
3461     if (checkpass(argv[2], hi->passwd))
3462         reply("CHECKPASS_YES");
3463     else
3464         reply("CHECKPASS_NO");
3465     argv[2] = "****";
3466     return 1;
3467 }
3468
3469 static MODCMD_FUNC(cmd_checkemail)
3470 {
3471     struct handle_info *hi;
3472
3473     NICKSERV_MIN_PARMS(3);
3474     if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
3475         return 0;
3476     }
3477     if (!hi->email_addr)
3478         reply("CHECKEMAIL_NOT_SET");
3479     else if (!irccasecmp(argv[2], hi->email_addr))
3480         reply("CHECKEMAIL_YES");
3481     else
3482         reply("CHECKEMAIL_NO");
3483     return 1;
3484 }
3485
3486
3487 static void
3488 nickserv_db_read_handle(const char *handle, dict_t obj)
3489 {
3490     const char *str;
3491     struct string_list *masks, *slist;
3492     struct handle_info *hi;
3493     struct userNode *authed_users;
3494     struct userData *channel_list;
3495     unsigned long id;
3496     unsigned int ii;
3497     dict_t subdb;
3498
3499     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
3500     id = str ? strtoul(str, NULL, 0) : 0;
3501     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
3502     if (!str) {
3503         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
3504         return;
3505     }
3506     if ((hi = get_handle_info(handle))) {
3507         authed_users = hi->users;
3508         channel_list = hi->channels;
3509         hi->users = NULL;
3510         hi->channels = NULL;
3511         dict_remove(nickserv_handle_dict, hi->handle);
3512     } else {
3513         authed_users = NULL;
3514         channel_list = NULL;
3515     }
3516     hi = register_handle(handle, str, id);
3517     if (authed_users) {
3518         hi->users = authed_users;
3519         while (authed_users) {
3520             authed_users->handle_info = hi;
3521             authed_users = authed_users->next_authed;
3522         }
3523     }
3524     hi->channels = channel_list;
3525     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
3526     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
3527     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
3528     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
3529     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
3530     hi->language = language_find(str ? str : "C");
3531     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
3532     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
3533     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
3534     if (str)
3535         hi->infoline = strdup(str);
3536     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
3537     hi->registered = str ? strtoul(str, NULL, 0) : now;
3538     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
3539     hi->lastseen = str ? strtoul(str, NULL, 0) : hi->registered;
3540     str = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
3541     hi->karma = str ? strtoul(str, NULL, 0) : 0;
3542     /* We want to read the nicks even if disable_nicks is set.  This is so
3543      * that we don't lose the nick data entirely. */
3544     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
3545     if (slist) {
3546         for (ii=0; ii<slist->used; ii++)
3547             register_nick(slist->list[ii], hi);
3548     }
3549     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
3550     if (str) {
3551         for (ii=0; str[ii]; ii++)
3552             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
3553     }
3554     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
3555     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
3556     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
3557     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
3558     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
3559     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
3560     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
3561     if (!str)
3562         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
3563     if (str)
3564         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
3565     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
3566     if (str)
3567         nickserv_set_email_addr(hi, str);
3568     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
3569     if (str)
3570         hi->epithet = strdup(str);
3571     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
3572     if (str)
3573         hi->fakehost = strdup(str);
3574     /* Read the "cookie" sub-database (if it exists). */
3575     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
3576     if (subdb) {
3577         const char *data, *type, *expires, *cookie_str;
3578         struct handle_cookie *cookie;
3579
3580         cookie = calloc(1, sizeof(*cookie));
3581         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
3582         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
3583         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
3584         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
3585         if (!type || !expires || !cookie_str) {
3586             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
3587             goto cookie_out;
3588         }
3589         if (!irccasecmp(type, KEY_ACTIVATION))
3590             cookie->type = ACTIVATION;
3591         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
3592             cookie->type = PASSWORD_CHANGE;
3593         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
3594             cookie->type = EMAIL_CHANGE;
3595         else if (!irccasecmp(type, KEY_ALLOWAUTH))
3596             cookie->type = ALLOWAUTH;
3597         else {
3598             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
3599             goto cookie_out;
3600         }
3601         cookie->expires = strtoul(expires, NULL, 0);
3602         if (cookie->expires < now)
3603             goto cookie_out;
3604         if (data)
3605             cookie->data = strdup(data);
3606         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
3607         cookie->hi = hi;
3608       cookie_out:
3609         if (cookie->hi)
3610             nickserv_bake_cookie(cookie);
3611         else
3612             nickserv_free_cookie(cookie);
3613     }
3614     /* Read the "notes" sub-database (if it exists). */
3615     subdb = database_get_data(obj, KEY_NOTES, RECDB_OBJECT);
3616     if (subdb) {
3617         dict_iterator_t it;
3618         struct handle_note *last_note;
3619         struct handle_note *note;
3620
3621         last_note = NULL;
3622         for (it = dict_first(subdb); it; it = iter_next(it)) {
3623             const char *expires;
3624             const char *setter;
3625             const char *text;
3626             const char *set;
3627             const char *note_id;
3628             dict_t notedb;
3629
3630             note_id = iter_key(it);
3631             notedb = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
3632             if (!notedb) {
3633                 log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", note_id, hi->handle);
3634                 continue;
3635             }
3636             expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
3637             setter = database_get_data(notedb, KEY_NOTE_SETTER, RECDB_QSTRING);
3638             text = database_get_data(notedb, KEY_NOTE_NOTE, RECDB_QSTRING);
3639             set = database_get_data(notedb, KEY_NOTE_SET, RECDB_QSTRING);
3640             if (!setter || !text || !set) {
3641                 log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", note_id, hi->handle);
3642                 continue;
3643             }
3644             note = calloc(1, sizeof(*note) + strlen(text));
3645             note->next = NULL;
3646             note->expires = expires ? strtoul(expires, NULL, 10) : 0;
3647             note->set = strtoul(set, NULL, 10);
3648             note->id = strtoul(note_id, NULL, 10);
3649             safestrncpy(note->setter, setter, sizeof(note->setter));
3650             strcpy(note->note, text);
3651             if (last_note)
3652                 last_note->next = note;
3653             else
3654                 hi->notes = note;
3655             last_note = note;
3656         }
3657     }
3658 }
3659
3660 static int
3661 nickserv_saxdb_read(dict_t db) {
3662     dict_iterator_t it;
3663     struct record_data *rd;
3664
3665     for (it=dict_first(db); it; it=iter_next(it)) {
3666         rd = iter_data(it);
3667         nickserv_db_read_handle(iter_key(it), rd->d.object);
3668     }
3669     return 0;
3670 }
3671
3672 static NICKSERV_FUNC(cmd_mergedb)
3673 {
3674     struct timeval start, stop;
3675     dict_t db;
3676
3677     NICKSERV_MIN_PARMS(2);
3678     gettimeofday(&start, NULL);
3679     if (!(db = parse_database(argv[1]))) {
3680         reply("NSMSG_DB_UNREADABLE", argv[1]);
3681         return 0;
3682     }
3683     nickserv_saxdb_read(db);
3684     free_database(db);
3685     gettimeofday(&stop, NULL);
3686     stop.tv_sec -= start.tv_sec;
3687     stop.tv_usec -= start.tv_usec;
3688     if (stop.tv_usec < 0) {
3689         stop.tv_sec -= 1;
3690         stop.tv_usec += 1000000;
3691     }
3692     reply("NSMSG_DB_MERGED", argv[1], (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
3693     return 1;
3694 }
3695
3696 static void
3697 expire_handles(UNUSED_ARG(void *data))
3698 {
3699     dict_iterator_t it, next;
3700     unsigned long expiry;
3701     struct handle_info *hi;
3702
3703     for (it=dict_first(nickserv_handle_dict); it; it=next) {
3704         next = iter_next(it);
3705         hi = iter_data(it);
3706         if ((hi->opserv_level > 0)
3707             || hi->users
3708             || HANDLE_FLAGGED(hi, FROZEN)
3709             || HANDLE_FLAGGED(hi, NODELETE)) {
3710             continue;
3711         }
3712         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
3713         if ((now - hi->lastseen) > expiry) {
3714             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
3715             nickserv_unregister_handle(hi, NULL);
3716         }
3717     }
3718
3719     if (nickserv_conf.handle_expire_frequency)
3720         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3721 }
3722
3723 static void
3724 nickserv_load_dict(const char *fname)
3725 {
3726     FILE *file;
3727     char line[128];
3728     if (!(file = fopen(fname, "r"))) {
3729         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
3730         return;
3731     }
3732     while (fgets(line, sizeof(line), file)) {
3733         if (!line[0])
3734             continue;
3735         if (line[strlen(line)-1] == '\n')
3736             line[strlen(line)-1] = 0;
3737         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
3738     }
3739     fclose(file);
3740     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
3741 }
3742
3743 static enum reclaim_action
3744 reclaim_action_from_string(const char *str) {
3745     if (!str)
3746         return RECLAIM_NONE;
3747     else if (!irccasecmp(str, "warn"))
3748         return RECLAIM_WARN;
3749     else if (!irccasecmp(str, "svsnick"))
3750         return RECLAIM_SVSNICK;
3751     else if (!irccasecmp(str, "kill"))
3752         return RECLAIM_KILL;
3753     else
3754         return RECLAIM_NONE;
3755 }
3756
3757 static void
3758 nickserv_conf_read(void)
3759 {
3760     dict_t conf_node, child;
3761     const char *str;
3762     dict_iterator_t it;
3763
3764     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
3765         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
3766         return;
3767     }
3768     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
3769     if (!str)
3770         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
3771     if (nickserv_conf.valid_handle_regex_set)
3772         regfree(&nickserv_conf.valid_handle_regex);
3773     if (str) {
3774         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3775         nickserv_conf.valid_handle_regex_set = !err;
3776         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
3777     } else {
3778         nickserv_conf.valid_handle_regex_set = 0;
3779     }
3780     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
3781     if (nickserv_conf.valid_nick_regex_set)
3782         regfree(&nickserv_conf.valid_nick_regex);
3783     if (str) {
3784         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3785         nickserv_conf.valid_nick_regex_set = !err;
3786         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
3787     } else {
3788         nickserv_conf.valid_nick_regex_set = 0;
3789     }
3790     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
3791     if (!str)
3792         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
3793     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
3794     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
3795     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
3796     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
3797     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
3798     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
3799     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
3800     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
3801     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
3802     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
3803     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
3804     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
3805     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
3806     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
3807     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3808     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
3809     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
3810     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
3811     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
3812     str = database_get_data(conf_node, KEY_SET_TITLE_LEVEL, RECDB_QSTRING);
3813     nickserv_conf.set_title_level = str ? strtoul(str, NULL, 0) : 900;
3814     str = database_get_data(conf_node, KEY_SET_FAKEHOST_LEVEL, RECDB_QSTRING);
3815     nickserv_conf.set_fakehost_level = str ? strtoul(str, NULL, 0) : 1000;
3816     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
3817     if (!str)
3818         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
3819     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
3820     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3821     if (!str)
3822         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3823     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
3824     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3825     if (!str)
3826         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3827     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
3828     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
3829     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
3830     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
3831     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
3832     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
3833     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
3834     str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
3835     nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
3836     str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
3837     if (!str)
3838         str = "ShgsfnHbu";
3839     nickserv_conf.ounregister_flags = 0;
3840     while(*str) {
3841         unsigned int pos = handle_inverse_flags[(unsigned char)*str];
3842         str++;
3843         if(pos)
3844             nickserv_conf.ounregister_flags |= 1 << (pos - 1);
3845     }
3846     str = database_get_data(conf_node, KEY_HANDLE_TS_MODE, RECDB_QSTRING);
3847     if (!str)
3848         nickserv_conf.handle_ts_mode = TS_IGNORE;
3849     else if (!irccasecmp(str, "ircu"))
3850         nickserv_conf.handle_ts_mode = TS_IRCU;
3851     else
3852         nickserv_conf.handle_ts_mode = TS_IGNORE;
3853     if (!nickserv_conf.disable_nicks) {
3854         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
3855         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3856         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
3857         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
3858         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
3859         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3860         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
3861         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
3862     }
3863     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
3864     for (it=dict_first(child); it; it=iter_next(it)) {
3865         const char *key = iter_key(it), *value;
3866         unsigned char flag;
3867         int pos;
3868
3869         if (!strncasecmp(key, "uc_", 3))
3870             flag = toupper(key[3]);
3871         else if (!strncasecmp(key, "lc_", 3))
3872             flag = tolower(key[3]);
3873         else
3874             flag = key[0];
3875
3876         if ((pos = handle_inverse_flags[flag])) {
3877             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
3878             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
3879         }
3880     }
3881     if (nickserv_conf.weak_password_dict)
3882         dict_delete(nickserv_conf.weak_password_dict);
3883     nickserv_conf.weak_password_dict = dict_new();
3884     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
3885     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
3886     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
3887     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
3888     if (str)
3889         nickserv_load_dict(str);
3890     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
3891     if (nickserv && str)
3892         NickChange(nickserv, str, 0);
3893     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
3894     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
3895     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
3896     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
3897     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
3898     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
3899     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
3900     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
3901     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
3902     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
3903     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
3904     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
3905     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
3906     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
3907     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
3908     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
3909     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
3910     nickserv_conf.titlehost_suffix = str ? str : "example.net";
3911     str = conf_get_data("server/network", RECDB_QSTRING);
3912     nickserv_conf.network_name = str ? str : "some IRC network";
3913     if (!nickserv_conf.auth_policer_params) {
3914         nickserv_conf.auth_policer_params = policer_params_new();
3915         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
3916         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
3917     }
3918     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
3919     for (it=dict_first(child); it; it=iter_next(it))
3920         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
3921 }
3922
3923 static void
3924 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
3925     const char *msg;
3926     char newnick[NICKLEN+1];
3927
3928     assert(user);
3929     assert(ni);
3930     switch (action) {
3931     case RECLAIM_NONE:
3932         /* do nothing */
3933         break;
3934     case RECLAIM_WARN:
3935         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3936         break;
3937     case RECLAIM_SVSNICK:
3938         do {
3939             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
3940         } while (GetUserH(newnick));
3941         irc_svsnick(nickserv, user, newnick);
3942         break;
3943     case RECLAIM_KILL:
3944         msg = user_find_message(user, "NSMSG_RECLAIM_KILL");
3945         DelUser(user, nickserv, 1, msg);
3946         break;
3947     }
3948 }
3949
3950 static void
3951 nickserv_reclaim_p(void *data) {
3952     struct userNode *user = data;
3953     struct nick_info *ni = get_nick_info(user->nick);
3954     if (ni)
3955         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3956 }
3957
3958 static void
3959 check_user_nick(struct userNode *user) {
3960     struct nick_info *ni;
3961     user->modes &= ~FLAGS_REGNICK;
3962     if (!(ni = get_nick_info(user->nick)))
3963         return;
3964     if (user->handle_info == ni->owner) {
3965         user->modes |= FLAGS_REGNICK;
3966         irc_regnick(user);
3967         return;
3968     }
3969     if (nickserv_conf.warn_nick_owned)
3970         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3971     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
3972         return;
3973     if (nickserv_conf.auto_reclaim_delay)
3974         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
3975     else
3976         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3977 }
3978
3979 void
3980 handle_account(struct userNode *user, const char *stamp, unsigned long timestamp, unsigned long serial)
3981 {
3982     struct handle_info *hi = NULL;
3983
3984     if (stamp != NULL)
3985         hi = dict_find(nickserv_handle_dict, stamp, NULL);
3986     if ((hi == NULL) && (serial != 0)) {
3987         char id[IDLEN + 1];
3988         inttobase64(id, serial, IDLEN);
3989         hi = dict_find(nickserv_id_dict, id, NULL);
3990     }
3991
3992     if (hi) {
3993         if ((nickserv_conf.handle_ts_mode == TS_IRCU)
3994             && (timestamp != hi->registered)) {
3995             return;
3996         }
3997         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
3998             return;
3999         }
4000         set_user_handle_info(user, hi, 0);
4001     } else {
4002         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s:%lu:%lu.", user->nick, stamp, timestamp, serial);
4003     }
4004 }
4005
4006 void
4007 handle_nick_change(struct userNode *user, const char *old_nick)
4008 {
4009     struct handle_info *hi;
4010
4011     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
4012         dict_remove(nickserv_allow_auth_dict, old_nick);
4013         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
4014     }
4015     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4016     check_user_nick(user);
4017 }
4018
4019 void
4020 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
4021 {
4022     dict_remove(nickserv_allow_auth_dict, user->nick);
4023     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4024     set_user_handle_info(user, NULL, 0);
4025 }
4026
4027 static struct modcmd *
4028 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
4029 {
4030     if (min_level > 0) {
4031         char buf[16];
4032         sprintf(buf, "%u", min_level);
4033         if (must_be_qualified) {
4034             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
4035         } else {
4036             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
4037         }
4038     } else if (min_level == 0) {
4039         if (must_be_qualified) {
4040             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4041         } else {
4042             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4043         }
4044     } else {
4045         if (must_be_qualified) {
4046             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
4047         } else {
4048             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
4049         }
4050     }
4051 }
4052
4053 static void
4054 nickserv_db_cleanup(void)
4055 {
4056     unreg_del_user_func(nickserv_remove_user);
4057     userList_clean(&curr_helpers);
4058     policer_params_delete(nickserv_conf.auth_policer_params);
4059     dict_delete(nickserv_handle_dict);
4060     dict_delete(nickserv_nick_dict);
4061     dict_delete(nickserv_opt_dict);
4062     dict_delete(nickserv_allow_auth_dict);
4063     dict_delete(nickserv_email_dict);
4064     dict_delete(nickserv_id_dict);
4065     dict_delete(nickserv_conf.weak_password_dict);
4066     free(auth_func_list);
4067     free(unreg_func_list);
4068     free(rf_list);
4069     free(allowauth_func_list);
4070     free(handle_merge_func_list);
4071     free(failpw_func_list);
4072     if (nickserv_conf.valid_handle_regex_set)
4073         regfree(&nickserv_conf.valid_handle_regex);
4074     if (nickserv_conf.valid_nick_regex_set)
4075         regfree(&nickserv_conf.valid_nick_regex);
4076 }
4077
4078 void
4079 init_nickserv(const char *nick)
4080 {
4081     unsigned int i;
4082     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
4083     reg_new_user_func(check_user_nick);
4084     reg_nick_change_func(handle_nick_change);
4085     reg_del_user_func(nickserv_remove_user);
4086     reg_account_func(handle_account);
4087
4088     /* set up handle_inverse_flags */
4089     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
4090     for (i=0; handle_flags[i]; i++) {
4091         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
4092         flag_access_levels[i] = 0;
4093     }
4094
4095     conf_register_reload(nickserv_conf_read);
4096     nickserv_opt_dict = dict_new();
4097     nickserv_email_dict = dict_new();
4098     dict_set_free_keys(nickserv_email_dict, free);
4099     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
4100
4101     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
4102     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
4103     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
4104     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
4105     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
4106     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
4107     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
4108     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
4109     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
4110     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
4111     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
4112     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
4113     nickserv_define_func("SET", cmd_set, -1, 1, 0);
4114     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
4115     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
4116     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
4117     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
4118     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
4119     nickserv_define_func("MERGE", cmd_merge, 750, 1, 0);
4120     nickserv_define_func("ADDNOTE", cmd_addnote, 0, 1, 0);
4121     nickserv_define_func("DELNOTE", cmd_delnote, 0, 1, 0);
4122     nickserv_define_func("NOTES", cmd_notes, 0, 1, 0);
4123     if (!nickserv_conf.disable_nicks) {
4124         /* nick management commands */
4125         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
4126         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
4127         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
4128         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
4129         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
4130         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
4131     }
4132     if (nickserv_conf.email_enabled) {
4133         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
4134         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
4135         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
4136         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
4137         nickserv_define_func("ODELCOOKIE", cmd_odelcookie, 0, 1, 0);
4138         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
4139     }
4140     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
4141     /* miscellaneous commands */
4142     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
4143     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
4144     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
4145     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
4146     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
4147     nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0);
4148     /* other options */
4149     dict_insert(nickserv_opt_dict, "INFO", opt_info);
4150     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
4151     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
4152     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
4153     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
4154     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
4155     dict_insert(nickserv_opt_dict, "PASS", opt_password);
4156     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
4157     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
4158     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
4159     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
4160     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
4161     if (nickserv_conf.titlehost_suffix) {
4162         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
4163         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
4164     }
4165     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
4166     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
4167     dict_insert(nickserv_opt_dict, "KARMA", opt_karma);
4168     nickserv_define_func("OSET KARMA", NULL, 0, 1, 0);
4169
4170     nickserv_handle_dict = dict_new();
4171     dict_set_free_keys(nickserv_handle_dict, free);
4172     dict_set_free_data(nickserv_handle_dict, free_handle_info);
4173
4174     nickserv_id_dict = dict_new();
4175     dict_set_free_keys(nickserv_id_dict, free);
4176
4177     nickserv_nick_dict = dict_new();
4178     dict_set_free_data(nickserv_nick_dict, free);
4179
4180     nickserv_allow_auth_dict = dict_new();
4181
4182     userList_init(&curr_helpers);
4183
4184     if (nick) {
4185         const char *modes = conf_get_data("services/nickserv/modes", RECDB_QSTRING);
4186         nickserv = AddLocalUser(nick, nick, NULL, "Nick Services", modes);
4187         nickserv_service = service_register(nickserv);
4188     }
4189     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
4190     reg_exit_func(nickserv_db_cleanup);
4191     if(nickserv_conf.handle_expire_frequency)
4192         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
4193     message_register_table(msgtab);
4194 }