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