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