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