fixed small memory leak (free old devnull names when setting new ones)
[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, const char *ipmask)
1829 {
1830     struct handle_info *hi;
1831     unsigned int match = 0, ii = 0;
1832     hi = dict_find(nickserv_handle_dict, user, NULL);
1833     if(!hi)
1834         return NULL;
1835     /* If no hostmasks on the account, allow it. */
1836     if (hi->masks->used) {
1837         /* If any hostmask matches, allow it. */
1838         for (ii=0; ii<hi->masks->used; ii++)
1839           if (match_ircglob(hostmask, hi->masks->list[ii]) || match_ircglob(ipmask, hi->masks->list[ii])) {
1840             match = 1;
1841             break;
1842           }
1843         if(!match) 
1844           return NULL;
1845     }
1846     if(!checkpass(pass, hi->passwd))
1847         return NULL;
1848     if (HANDLE_FLAGGED(hi, SUSPENDED))
1849         return NULL;
1850     /** following in one of the next commits
1851     struct last_login *login,*clogin,*old;
1852     unsigned int ii = 0;
1853     login = calloc(1, sizeof(*login));
1854     login->last_login = hi->last_login;
1855     login->hostmask = strdup(hostmask);
1856     login->authtime = now;
1857     login->quittime = 0;
1858     login->quit = NULL;
1859     login->user = NULL;
1860     login->loc_pending = strdup(numeric);
1861     for (clogin = hi->last_login; clogin != NULL && ii < 9; clogin = clogin->last_login) {
1862         if(ii == 8 && clogin->last_login) {
1863             old = clogin->last_login;
1864             clogin->last_login = NULL;
1865             free(old->hostmask);
1866             if(old->quit)
1867                 free(old->quit);
1868             if(old->loc_pending)
1869                 free(old->loc_pending);
1870             free(old);
1871         }
1872     }
1873     hi->last_login = login;
1874     */
1875     return hi;
1876 }
1877
1878 char *getfakehost(const char *user)
1879 {
1880     struct handle_info *hi;
1881     hi = dict_find(nickserv_handle_dict, user, NULL);
1882     if(!hi)
1883         return 0;
1884     return generate_fakehost(hi);
1885 }
1886
1887 static allowauth_func_t *allowauth_func_list;
1888 static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
1889
1890 void
1891 reg_allowauth_func(allowauth_func_t func)
1892 {
1893     if (allowauth_func_used == allowauth_func_size) {
1894         if (allowauth_func_size) {
1895             allowauth_func_size <<= 1;
1896             allowauth_func_list = realloc(allowauth_func_list, allowauth_func_size*sizeof(allowauth_func_t));
1897         } else {
1898             allowauth_func_size = 8;
1899             allowauth_func_list = malloc(allowauth_func_size*sizeof(allowauth_func_t));
1900         }
1901     }
1902     allowauth_func_list[allowauth_func_used++] = func;
1903 }
1904
1905 static NICKSERV_FUNC(cmd_allowauth)
1906 {
1907     struct userNode *target;
1908     struct handle_info *hi;
1909     unsigned int n;
1910
1911     NICKSERV_MIN_PARMS(2);
1912     if (!(target = GetUserH(argv[1]))) {
1913         reply("MSG_NICK_UNKNOWN", argv[1]);
1914         return 0;
1915     }
1916     if (target->handle_info) {
1917         reply("NSMSG_USER_PREV_AUTH", target->nick);
1918         return 0;
1919     }
1920     if (IsStamped(target)) {
1921         /* Unauthenticated users might still have been stamped
1922            previously and could therefore have a hidden host;
1923            do not allow them to authenticate to an account. */
1924         reply("NSMSG_USER_PREV_STAMP", target->nick);
1925         return 0;
1926     }
1927     if (argc == 2)
1928         hi = NULL;
1929     else if (!(hi = get_handle_info(argv[2]))) {
1930         reply("MSG_HANDLE_UNKNOWN", argv[2]);
1931         return 0;
1932     }
1933     if (hi) {
1934         if (hi->opserv_level > user->handle_info->opserv_level) {
1935             reply("MSG_USER_OUTRANKED", hi->handle);
1936             return 0;
1937         }
1938         if (((hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER))
1939              || (hi->opserv_level > 0))
1940             && ((argc < 4) || irccasecmp(argv[3], "staff"))) {
1941             reply("NSMSG_ALLOWAUTH_STAFF", hi->handle);
1942             return 0;
1943         }
1944         dict_insert(nickserv_allow_auth_dict, target->nick, hi);
1945         reply("NSMSG_AUTH_ALLOWED", target->nick, hi->handle);
1946         send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_MSG", hi->handle, hi->handle);
1947         if (nickserv_conf.email_enabled)
1948             send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_EMAIL");
1949     } else {
1950         if (dict_remove(nickserv_allow_auth_dict, target->nick))
1951             reply("NSMSG_AUTH_NORMAL_ONLY", target->nick);
1952         else
1953             reply("NSMSG_AUTH_UNSPECIAL", target->nick);
1954     }
1955     for (n=0; n<allowauth_func_used; n++)
1956         allowauth_func_list[n](user, target, hi);
1957     return 1;
1958 }
1959
1960 static NICKSERV_FUNC(cmd_authcookie)
1961 {
1962     struct handle_info *hi;
1963
1964     NICKSERV_MIN_PARMS(2);
1965     if (user->handle_info) {
1966         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1967         return 0;
1968     }
1969     if (IsStamped(user)) {
1970         /* Unauthenticated users might still have been stamped
1971            previously and could therefore have a hidden host;
1972            do not allow them to authenticate to an account. */
1973         reply("NSMSG_STAMPED_AUTHCOOKIE");
1974         return 0;
1975     }
1976     if (!(hi = get_handle_info(argv[1]))) {
1977         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1978         return 0;
1979     }
1980     if (!hi->email_addr) {
1981         reply("MSG_SET_EMAIL_ADDR");
1982         return 0;
1983     }
1984     nickserv_make_cookie(user, hi, ALLOWAUTH, NULL);
1985     return 1;
1986 }
1987
1988 static NICKSERV_FUNC(cmd_delcookie)
1989 {
1990     struct handle_info *hi;
1991
1992     hi = user->handle_info;
1993     if (!hi->cookie) {
1994         reply("NSMSG_NO_COOKIE");
1995         return 0;
1996     }
1997     switch (hi->cookie->type) {
1998     case ACTIVATION:
1999     case EMAIL_CHANGE:
2000         reply("NSMSG_MUST_TIME_OUT");
2001         break;
2002     default:
2003         nickserv_eat_cookie(hi->cookie);
2004         reply("NSMSG_ATE_COOKIE");
2005         break;
2006     }
2007     return 1;
2008 }
2009
2010 static NICKSERV_FUNC(cmd_odelcookie)
2011 {
2012     struct handle_info *hi;
2013
2014     NICKSERV_MIN_PARMS(2);
2015
2016     if (!(hi = get_victim_oper(user, argv[1])))
2017         return 0;
2018
2019     if (!hi->cookie) {
2020         reply("NSMSG_NO_COOKIE_FOREIGN", hi->handle);
2021         return 0;
2022     }
2023
2024     nickserv_eat_cookie(hi->cookie);
2025     reply("NSMSG_ATE_COOKIE_FOREIGN", hi->handle);
2026     return 1;
2027 }
2028
2029
2030 static NICKSERV_FUNC(cmd_resetpass)
2031 {
2032     struct handle_info *hi;
2033     char crypted[MD5_CRYPT_LENGTH];
2034
2035     NICKSERV_MIN_PARMS(3);
2036     if (user->handle_info) {
2037         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
2038         return 0;
2039     }
2040     if (IsStamped(user)) {
2041         /* Unauthenticated users might still have been stamped
2042            previously and could therefore have a hidden host;
2043            do not allow them to activate an account. */
2044         reply("NSMSG_STAMPED_RESETPASS");
2045         return 0;
2046     }
2047     if (!(hi = get_handle_info(argv[1]))) {
2048         reply("MSG_HANDLE_UNKNOWN", argv[1]);
2049         return 0;
2050     }
2051     if (!hi->email_addr) {
2052         reply("MSG_SET_EMAIL_ADDR");
2053         return 0;
2054     }
2055     cryptpass(argv[2], crypted);
2056     argv[2] = "****";
2057     nickserv_make_cookie(user, hi, PASSWORD_CHANGE, crypted);
2058     return 1;
2059 }
2060
2061 static NICKSERV_FUNC(cmd_cookie)
2062 {
2063     struct handle_info *hi;
2064     const char *cookie;
2065
2066     if ((argc == 2) && (hi = user->handle_info) && hi->cookie && (hi->cookie->type == EMAIL_CHANGE)) {
2067         cookie = argv[1];
2068     } else {
2069         NICKSERV_MIN_PARMS(3);
2070         if (!(hi = get_handle_info(argv[1]))) {
2071             reply("MSG_HANDLE_UNKNOWN", argv[1]);
2072             return 0;
2073         }
2074         cookie = argv[2];
2075     }
2076
2077     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
2078         reply("NSMSG_HANDLE_SUSPENDED");
2079         return 0;
2080     }
2081
2082     if (!hi->cookie) {
2083         reply("NSMSG_NO_COOKIE");
2084         return 0;
2085     }
2086
2087     /* Check validity of operation before comparing cookie to
2088      * prohibit guessing by authed users. */
2089     if (user->handle_info
2090         && (hi->cookie->type != EMAIL_CHANGE)
2091         && (hi->cookie->type != PASSWORD_CHANGE)) {
2092         reply("NSMSG_CANNOT_COOKIE");
2093         return 0;
2094     }
2095
2096     if (strcmp(cookie, hi->cookie->cookie)) {
2097         reply("NSMSG_BAD_COOKIE");
2098         return 0;
2099     }
2100
2101     switch (hi->cookie->type) {
2102     case ACTIVATION:
2103         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
2104         set_user_handle_info(user, hi, 1);
2105         reply("NSMSG_HANDLE_ACTIVATED");
2106         break;
2107     case PASSWORD_CHANGE:
2108         set_user_handle_info(user, hi, 1);
2109         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
2110         reply("NSMSG_PASSWORD_CHANGED");
2111         break;
2112     case EMAIL_CHANGE:
2113         nickserv_set_email_addr(hi, hi->cookie->data);
2114         reply("NSMSG_EMAIL_CHANGED");
2115         break;
2116     case ALLOWAUTH: {
2117         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
2118         set_user_handle_info(user, hi, 1);
2119         nickserv_addmask(user, hi, mask);
2120         reply("NSMSG_AUTH_SUCCESS");
2121         free(mask);
2122         break;
2123     }
2124     default:
2125         reply("NSMSG_BAD_COOKIE_TYPE", hi->cookie->type);
2126         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d for account %s.", hi->cookie->type, hi->handle);
2127         break;
2128     }
2129
2130     nickserv_eat_cookie(hi->cookie);
2131
2132     return 1;
2133 }
2134
2135 static NICKSERV_FUNC(cmd_oregnick) {
2136     const char *nick;
2137     struct handle_info *target;
2138     struct nick_info *ni;
2139
2140     NICKSERV_MIN_PARMS(3);
2141     if (!(target = modcmd_get_handle_info(user, argv[1])))
2142         return 0;
2143     nick = argv[2];
2144     if (!is_registerable_nick(nick)) {
2145         reply("NSMSG_BAD_NICK", nick);
2146         return 0;
2147     }
2148     ni = dict_find(nickserv_nick_dict, nick, NULL);
2149     if (ni) {
2150         reply("NSMSG_NICK_EXISTS", nick);
2151         return 0;
2152     }
2153     register_nick(nick, target);
2154     reply("NSMSG_OREGNICK_SUCCESS", nick, target->handle);
2155     return 1;
2156 }
2157
2158 static NICKSERV_FUNC(cmd_regnick) {
2159     unsigned n;
2160     struct nick_info *ni;
2161
2162     if (!is_registerable_nick(user->nick)) {
2163         reply("NSMSG_BAD_NICK", user->nick);
2164         return 0;
2165     }
2166     /* count their nicks, see if it's too many */
2167     for (n=0,ni=user->handle_info->nicks; ni; n++,ni=ni->next) ;
2168     if (n >= nickserv_conf.nicks_per_handle) {
2169         reply("NSMSG_TOO_MANY_NICKS");
2170         return 0;
2171     }
2172     ni = dict_find(nickserv_nick_dict, user->nick, NULL);
2173     if (ni) {
2174         reply("NSMSG_NICK_EXISTS", user->nick);
2175         return 0;
2176     }
2177     register_nick(user->nick, user->handle_info);
2178     reply("NSMSG_REGNICK_SUCCESS", user->nick);
2179     return 1;
2180 }
2181
2182 static NICKSERV_FUNC(cmd_pass)
2183 {
2184     struct handle_info *hi;
2185     const char *old_pass, *new_pass;
2186
2187     NICKSERV_MIN_PARMS(3);
2188     hi = user->handle_info;
2189     old_pass = argv[1];
2190     new_pass = argv[2];
2191     argv[2] = "****";
2192     if (!is_secure_password(hi->handle, new_pass, user)) return 0;
2193     if (!checkpass(old_pass, hi->passwd)) {
2194         argv[1] = "BADPASS";
2195         reply("NSMSG_PASSWORD_INVALID");
2196         return 0;
2197     }
2198     cryptpass(new_pass, hi->passwd);
2199     argv[1] = "****";
2200     reply("NSMSG_PASS_SUCCESS");
2201     return 1;
2202 }
2203
2204 static int
2205 nickserv_addmask(struct userNode *user, struct handle_info *hi, const char *mask)
2206 {
2207     unsigned int i;
2208     char *new_mask = canonicalize_hostmask(strdup(mask));
2209     for (i=0; i<hi->masks->used; i++) {
2210         if (!irccasecmp(new_mask, hi->masks->list[i])) {
2211             send_message(user, nickserv, "NSMSG_ADDMASK_ALREADY", new_mask);
2212             free(new_mask);
2213             return 0;
2214         }
2215     }
2216     string_list_append(hi->masks, new_mask);
2217     send_message(user, nickserv, "NSMSG_ADDMASK_SUCCESS", new_mask);
2218     return 1;
2219 }
2220
2221 static NICKSERV_FUNC(cmd_addmask)
2222 {
2223     if (argc < 2) {
2224         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
2225         int res = nickserv_addmask(user, user->handle_info, mask);
2226         free(mask);
2227         return res;
2228     } else {
2229         if (!is_gline(argv[1])) {
2230             reply("NSMSG_MASK_INVALID", argv[1]);
2231             return 0;
2232         }
2233         return nickserv_addmask(user, user->handle_info, argv[1]);
2234     }
2235 }
2236
2237 static NICKSERV_FUNC(cmd_oaddmask)
2238 {
2239     struct handle_info *hi;
2240
2241     NICKSERV_MIN_PARMS(3);
2242     if (!(hi = get_victim_oper(user, argv[1])))
2243         return 0;
2244     return nickserv_addmask(user, hi, argv[2]);
2245 }
2246
2247 static int
2248 nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask, int force)
2249 {
2250     unsigned int i;
2251     for (i=0; i<hi->masks->used; i++) {
2252         if (!strcmp(del_mask, hi->masks->list[i])) {
2253             char *old_mask = hi->masks->list[i];
2254             if (hi->masks->used == 1 && !force) {
2255                 send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
2256                 return 0;
2257             }
2258             hi->masks->list[i] = hi->masks->list[--hi->masks->used];
2259             send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
2260             free(old_mask);
2261             return 1;
2262         }
2263     }
2264     send_message(user, nickserv, "NSMSG_DELMASK_NOT_FOUND");
2265     return 0;
2266 }
2267
2268 static NICKSERV_FUNC(cmd_delmask)
2269 {
2270     NICKSERV_MIN_PARMS(2);
2271     return nickserv_delmask(user, user->handle_info, argv[1], 0);
2272 }
2273
2274 static NICKSERV_FUNC(cmd_odelmask)
2275 {
2276     struct handle_info *hi;
2277     NICKSERV_MIN_PARMS(3);
2278     if (!(hi = get_victim_oper(user, argv[1])))
2279         return 0;
2280     return nickserv_delmask(user, hi, argv[2], 1);
2281 }
2282
2283 int
2284 nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *padded, unsigned long *premoved) {
2285     unsigned int nn, add = 1, pos;
2286     unsigned long added, removed, flag;
2287
2288     for (added=removed=nn=0; str[nn]; nn++) {
2289         switch (str[nn]) {
2290         case '+': add = 1; break;
2291         case '-': add = 0; break;
2292         default:
2293             if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
2294                 send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
2295                 return 0;
2296             }
2297             if (user && (user->handle_info->opserv_level < flag_access_levels[pos-1])) {
2298                 /* cheesy avoidance of looking up the flag name.. */
2299                 send_message(user, bot, "NSMSG_FLAG_PRIVILEGED", str[nn]);
2300                 return 0;
2301             }
2302             flag = 1 << (pos - 1);
2303             if (add)
2304                 added |= flag, removed &= ~flag;
2305             else
2306                 removed |= flag, added &= ~flag;
2307             break;
2308         }
2309     }
2310     *padded = added;
2311     *premoved = removed;
2312     return 1;
2313 }
2314
2315 static int
2316 nickserv_apply_flags(struct userNode *user, struct handle_info *hi, const char *flags)
2317 {
2318     unsigned long before, after, added, removed;
2319     struct userNode *uNode;
2320
2321     before = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2322     if (!nickserv_modify_handle_flags(user, nickserv, flags, &added, &removed))
2323         return 0;
2324     hi->flags = (hi->flags | added) & ~removed;
2325     after = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2326
2327     /* Strip helping flag if they're only a support helper and not
2328      * currently in #support. */
2329     if (HANDLE_FLAGGED(hi, HELPING) && (after == HI_FLAG_SUPPORT_HELPER)) {
2330         struct channelList *schannels;
2331         unsigned int ii;
2332         schannels = chanserv_support_channels();
2333         for (ii = 0; ii < schannels->used; ++ii)
2334             if (find_handle_in_channel(schannels->list[ii], hi, NULL))
2335                 break;
2336         if (ii == schannels->used)
2337             HANDLE_CLEAR_FLAG(hi, HELPING);
2338     }
2339
2340     if (after && !before) {
2341         /* Add user to current helper list. */
2342         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2343             userList_append(&curr_helpers, uNode);
2344     } else if (!after && before) {
2345         /* Remove user from current helper list. */
2346         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2347             userList_remove(&curr_helpers, uNode);
2348     }
2349
2350     return 1;
2351 }
2352
2353 static void
2354 set_list(struct userNode *user, struct handle_info *hi, int override)
2355 {
2356     option_func_t *opt;
2357     unsigned int i;
2358     char *set_display[] = {
2359         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
2360         "EMAIL", "MAXLOGINS", "LANGUAGE", "DEVNULL"
2361     };
2362
2363     send_message(user, nickserv, "NSMSG_SETTING_LIST");
2364
2365     /* Do this so options are presented in a consistent order. */
2366     for (i = 0; i < ArrayLength(set_display); ++i)
2367         if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
2368             opt(user, hi, override, 0, NULL);
2369 }
2370
2371 static NICKSERV_FUNC(cmd_set)
2372 {
2373     struct handle_info *hi;
2374     option_func_t *opt;
2375
2376     hi = user->handle_info;
2377     if (argc < 2) {
2378         set_list(user, hi, 0);
2379         return 1;
2380     }
2381     if (!(opt = dict_find(nickserv_opt_dict, argv[1], NULL))) {
2382         reply("NSMSG_INVALID_OPTION", argv[1]);
2383         return 0;
2384     }
2385     return opt(user, hi, 0, argc-1, argv+1);
2386 }
2387
2388 static NICKSERV_FUNC(cmd_oset)
2389 {
2390     struct handle_info *hi;
2391     struct svccmd *subcmd;
2392     option_func_t *opt;
2393     char cmdname[MAXLEN];
2394
2395     NICKSERV_MIN_PARMS(2);
2396
2397     if (!(hi = get_victim_oper(user, argv[1])))
2398         return 0;
2399
2400     if (argc < 3) {
2401         set_list(user, hi, 0);
2402         return 1;
2403     }
2404
2405     if (!(opt = dict_find(nickserv_opt_dict, argv[2], NULL))) {
2406         reply("NSMSG_INVALID_OPTION", argv[2]);
2407         return 0;
2408     }
2409
2410     sprintf(cmdname, "%s %s", cmd->name, argv[2]);
2411     subcmd = dict_find(cmd->parent->commands, cmdname, NULL);
2412     if (subcmd && !svccmd_can_invoke(user, cmd->parent->bot, subcmd, NULL, SVCCMD_NOISY))
2413         return 0;
2414
2415     return opt(user, hi, 1, argc-2, argv+2);
2416 }
2417
2418 static OPTION_FUNC(opt_info)
2419 {
2420     const char *info;
2421     if (argc > 1) {
2422         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2423             free(hi->infoline);
2424             hi->infoline = NULL;
2425         } else {
2426             hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
2427         }
2428     }
2429
2430     info = hi->infoline ? hi->infoline : user_find_message(user, "MSG_NONE");
2431     send_message(user, nickserv, "NSMSG_SET_INFO", info);
2432     return 1;
2433 }
2434
2435 static OPTION_FUNC(opt_devnull)
2436 {
2437     const char *devnull;
2438     
2439     if (argc > 1) {
2440         if (!override) {
2441             send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2442             return 0;
2443         }
2444         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2445             free(hi->devnull);
2446             hi->devnull = NULL;
2447         } else {
2448             devnull = unsplit_string(argv+1, argc-1, NULL);
2449             if(devnull_check(devnull) == 1) { 
2450                 if(hi->devnull)
2451                     free(hi->devnull);
2452                 hi->devnull = strdup(devnull);
2453             }
2454         }
2455     }
2456
2457     devnull = hi->devnull ? hi->devnull : user_find_message(user, "MSG_NONE");
2458     send_message(user, nickserv, "NSMSG_SET_DEVNULL", devnull);
2459     return 1;
2460 }
2461
2462 void nickserv_devnull_delete(char *name) {
2463     dict_iterator_t it;
2464     struct handle_info *hi;
2465
2466     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2467         hi = iter_data(it);
2468         if (hi->devnull && !irccasecmp(name, hi->devnull)) {
2469             free(hi->devnull);
2470             hi->devnull = NULL;
2471         }
2472     }
2473 }
2474
2475 void nickserv_devnull_rename(char *oldname, char *newname) {
2476     dict_iterator_t it;
2477     struct handle_info *hi;
2478
2479     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2480         hi = iter_data(it);
2481         if (hi->devnull && !irccasecmp(oldname, hi->devnull)) {
2482             hi->devnull = strdup(newname);
2483         }
2484     }
2485 }
2486
2487 static OPTION_FUNC(opt_website)
2488 {
2489     const char *website;
2490     
2491     if (argc > 1) {
2492         if (!HANDLE_FLAGGED(user->handle_info, BOT)) {
2493             send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2494             return 0;
2495         }
2496         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2497             free(hi->website);
2498             hi->website = NULL;
2499         } else {
2500             website = unsplit_string(argv+1, argc-1, NULL);
2501             hi->website = strdup(website);
2502         }
2503     }
2504     if (HANDLE_FLAGGED(user->handle_info, BOT)) {
2505         website = hi->website ? hi->website : user_find_message(user, "MSG_NONE");
2506         send_message(user, nickserv, "NSMSG_SET_WEBSITE", website);
2507     }
2508     return 1;
2509 }
2510
2511 static OPTION_FUNC(opt_width)
2512 {
2513     if (argc > 1)
2514         hi->screen_width = strtoul(argv[1], NULL, 0);
2515
2516     if ((hi->screen_width > 0) && (hi->screen_width < MIN_LINE_SIZE))
2517         hi->screen_width = MIN_LINE_SIZE;
2518     else if (hi->screen_width > MAX_LINE_SIZE)
2519         hi->screen_width = MAX_LINE_SIZE;
2520
2521     send_message(user, nickserv, "NSMSG_SET_WIDTH", hi->screen_width);
2522     return 1;
2523 }
2524
2525 static OPTION_FUNC(opt_tablewidth)
2526 {
2527     if (argc > 1)
2528         hi->table_width = strtoul(argv[1], NULL, 0);
2529
2530     if ((hi->table_width > 0) && (hi->table_width < MIN_LINE_SIZE))
2531         hi->table_width = MIN_LINE_SIZE;
2532     else if (hi->screen_width > MAX_LINE_SIZE)
2533         hi->table_width = MAX_LINE_SIZE;
2534
2535     send_message(user, nickserv, "NSMSG_SET_TABLEWIDTH", hi->table_width);
2536     return 1;
2537 }
2538
2539 static OPTION_FUNC(opt_color)
2540 {
2541     if (argc > 1) {
2542         if (enabled_string(argv[1]))
2543             HANDLE_SET_FLAG(hi, MIRC_COLOR);
2544         else if (disabled_string(argv[1]))
2545             HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
2546         else {
2547             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2548             return 0;
2549         }
2550     }
2551
2552     send_message(user, nickserv, "NSMSG_SET_COLOR", user_find_message(user, HANDLE_FLAGGED(hi, MIRC_COLOR) ? "MSG_ON" : "MSG_OFF"));
2553     return 1;
2554 }
2555
2556 static OPTION_FUNC(opt_privmsg)
2557 {
2558     if (argc > 1) {
2559         if (enabled_string(argv[1]))
2560             HANDLE_SET_FLAG(hi, USE_PRIVMSG);
2561         else if (disabled_string(argv[1]))
2562             HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
2563         else {
2564             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2565             return 0;
2566         }
2567     }
2568
2569     send_message(user, nickserv, "NSMSG_SET_PRIVMSG", user_find_message(user, HANDLE_FLAGGED(hi, USE_PRIVMSG) ? "MSG_ON" : "MSG_OFF"));
2570     return 1;
2571 }
2572
2573 static OPTION_FUNC(opt_autohide)
2574 {
2575     if (argc > 1) {
2576         if (enabled_string(argv[1]))
2577             HANDLE_SET_FLAG(hi, AUTOHIDE);
2578         else if (disabled_string(argv[1]))
2579             HANDLE_CLEAR_FLAG(hi, AUTOHIDE);
2580         else {
2581             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2582             return 0;
2583         }
2584     }
2585
2586     send_message(user, nickserv, "NSMSG_SET_AUTOHIDE", user_find_message(user, HANDLE_FLAGGED(hi, AUTOHIDE) ? "MSG_ON" : "MSG_OFF"));
2587     return 1;
2588 }
2589
2590 static OPTION_FUNC(opt_style)
2591 {
2592     char *style;
2593
2594     if (argc > 1) {
2595         if (!irccasecmp(argv[1], "Zoot"))
2596             hi->userlist_style = HI_STYLE_ZOOT;
2597         else if (!irccasecmp(argv[1], "def"))
2598             hi->userlist_style = HI_STYLE_DEF;
2599     }
2600
2601     switch (hi->userlist_style) {
2602     case HI_STYLE_DEF:
2603         style = "def";
2604         break;
2605     case HI_STYLE_ZOOT:
2606     default:
2607         style = "Zoot";
2608     }
2609
2610     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
2611     return 1;
2612 }
2613
2614 static OPTION_FUNC(opt_password)
2615 {
2616     if (!override) {
2617         send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
2618         return 0;
2619     }
2620
2621     if (argc > 1)
2622         cryptpass(argv[1], hi->passwd);
2623
2624     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
2625     return 1;
2626 }
2627
2628 static OPTION_FUNC(opt_flags)
2629 {
2630     char flags[33];
2631     unsigned int ii, flen;
2632
2633     if (!override) {
2634         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2635         return 0;
2636     }
2637
2638     if (argc > 1)
2639         nickserv_apply_flags(user, hi, argv[1]);
2640
2641     for (ii = flen = 0; handle_flags[ii]; ii++)
2642         if (hi->flags & (1 << ii))
2643             flags[flen++] = handle_flags[ii];
2644     flags[flen] = '\0';
2645     if (hi->flags)
2646         send_message(user, nickserv, "NSMSG_SET_FLAGS", flags);
2647     else
2648         send_message(user, nickserv, "NSMSG_SET_FLAGS", user_find_message(user, "MSG_NONE"));
2649     return 1;
2650 }
2651
2652 static OPTION_FUNC(opt_email)
2653 {
2654     if (argc > 1) {
2655         const char *str;
2656         if (!is_valid_email_addr(argv[1])) {
2657             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
2658             return 0;
2659         }
2660         if ((str = mail_prohibited_address(argv[1]))) {
2661             send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", argv[1], str);
2662             return 0;
2663         }
2664         if (hi->email_addr && !irccasecmp(hi->email_addr, argv[1]))
2665             send_message(user, nickserv, "NSMSG_EMAIL_SAME");
2666         else if (!override)
2667                 nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1]);
2668         else {
2669             nickserv_set_email_addr(hi, argv[1]);
2670             if (hi->cookie)
2671                 nickserv_eat_cookie(hi->cookie);
2672             send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2673         }
2674     } else
2675         send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2676     return 1;
2677 }
2678
2679 static OPTION_FUNC(opt_maxlogins)
2680 {
2681     unsigned char maxlogins;
2682     if (argc > 1) {
2683         maxlogins = strtoul(argv[1], NULL, 0);
2684         if ((maxlogins > nickserv_conf.hard_maxlogins) && !override) {
2685             send_message(user, nickserv, "NSMSG_BAD_MAX_LOGINS", nickserv_conf.hard_maxlogins);
2686             return 0;
2687         }
2688         hi->maxlogins = maxlogins;
2689     }
2690     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
2691     send_message(user, nickserv, "NSMSG_SET_MAXLOGINS", maxlogins);
2692     return 1;
2693 }
2694
2695 static OPTION_FUNC(opt_language)
2696 {
2697     struct language *lang;
2698     if (argc > 1) {
2699         lang = language_find(argv[1]);
2700         if (irccasecmp(lang->name, argv[1]))
2701             send_message(user, nickserv, "NSMSG_LANGUAGE_NOT_FOUND", argv[1], lang->name);
2702         hi->language = lang;
2703     }
2704     send_message(user, nickserv, "NSMSG_SET_LANGUAGE", hi->language->name);
2705     return 1;
2706 }
2707
2708 static OPTION_FUNC(opt_karma)
2709 {
2710     if (!override) {
2711         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2712         return 0;
2713     }
2714
2715     if (argc > 1) {
2716         if (argv[1][0] == '+' && isdigit(argv[1][1])) {
2717             hi->karma += strtoul(argv[1] + 1, NULL, 10);
2718         } else if (argv[1][0] == '-' && isdigit(argv[1][1])) {
2719             hi->karma -= strtoul(argv[1] + 1, NULL, 10);
2720         } else {
2721             send_message(user, nickserv, "NSMSG_INVALID_KARMA", argv[1]);
2722         }
2723     }
2724
2725     send_message(user, nickserv, "NSMSG_SET_KARMA", hi->karma);
2726     return 1;
2727 }
2728
2729 int
2730 oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
2731     if (!oper_has_access(user, bot, nickserv_conf.modoper_level, 0))
2732         return 0;
2733     if ((user->handle_info->opserv_level < target->opserv_level)
2734         || ((user->handle_info->opserv_level == target->opserv_level)
2735             && (user->handle_info->opserv_level < 1000))) {
2736         send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
2737         return 0;
2738     }
2739     if ((user->handle_info->opserv_level < new_level)
2740         || ((user->handle_info->opserv_level == new_level)
2741             && (user->handle_info->opserv_level < 1000))) {
2742         send_message(user, bot, "NSMSG_OPSERV_LEVEL_BAD");
2743         return 0;
2744     }
2745     if (user->handle_info == target) {
2746         send_message(user, bot, "MSG_STUPID_ACCESS_CHANGE");
2747         return 0;
2748     }
2749     if (target->opserv_level == new_level)
2750         return 0;
2751     log_module(NS_LOG, LOG_INFO, "Account %s setting oper level for account %s to %d (from %d).",
2752         user->handle_info->handle, target->handle, new_level, target->opserv_level);
2753     target->opserv_level = new_level;
2754     return 1;
2755 }
2756
2757 static OPTION_FUNC(opt_level)
2758 {
2759     int res;
2760
2761     if (!override) {
2762         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2763         return 0;
2764     }
2765
2766     res = (argc > 1) ? oper_try_set_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
2767     send_message(user, nickserv, "NSMSG_SET_LEVEL", hi->opserv_level);
2768     return res;
2769 }
2770
2771 static OPTION_FUNC(opt_epithet)
2772 {
2773     if (!override) {
2774         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2775         return 0;
2776     }
2777
2778     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_epithet_level, 0)) {
2779         char *epithet = unsplit_string(argv+1, argc-1, NULL);
2780         if (hi->epithet)
2781             free(hi->epithet);
2782         if ((epithet[0] == '*') && !epithet[1])
2783             hi->epithet = NULL;
2784         else
2785             hi->epithet = strdup(epithet);
2786     }
2787
2788     if (hi->epithet)
2789         send_message(user, nickserv, "NSMSG_SET_EPITHET", hi->epithet);
2790     else
2791         send_message(user, nickserv, "NSMSG_SET_EPITHET", user_find_message(user, "MSG_NONE"));
2792     return 1;
2793 }
2794
2795 static OPTION_FUNC(opt_title)
2796 {
2797     const char *title;
2798
2799     if (!override) {
2800         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2801         return 0;
2802     }
2803
2804     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_title_level, 0)) {
2805         title = argv[1];
2806         if (strchr(title, '.')) {
2807             send_message(user, nickserv, "NSMSG_TITLE_INVALID");
2808             return 0;
2809         }
2810         if ((strlen(user->handle_info->handle) + strlen(title) +
2811              strlen(titlehost_suffix) + 2) > HOSTLEN) {
2812             send_message(user, nickserv, "NSMSG_TITLE_TRUNCATED");
2813             return 0;
2814         }
2815
2816         free(hi->fakehost);
2817         if (!strcmp(title, "*")) {
2818             hi->fakehost = NULL;
2819         } else {
2820             hi->fakehost = malloc(strlen(title)+2);
2821             hi->fakehost[0] = '.';
2822             strcpy(hi->fakehost+1, title);
2823         }
2824         apply_fakehost(hi, NULL);
2825     } else if (hi->fakehost && (hi->fakehost[0] == '.'))
2826         title = hi->fakehost + 1;
2827     else
2828         title = NULL;
2829     if (!title)
2830         title = user_find_message(user, "MSG_NONE");
2831     send_message(user, nickserv, "NSMSG_SET_TITLE", title);
2832     return 1;
2833 }
2834
2835 static OPTION_FUNC(opt_fakehost)
2836 {
2837     char mask[USERLEN + HOSTLEN + 2];
2838     char *host, *ident;
2839
2840     if (!override) {
2841         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2842         return 0;
2843     }
2844
2845     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakehost_level, 0)) {
2846         if(strlen(argv[1]) >= sizeof(mask)) {
2847             send_message(user, nickserv, "NSMSG_FAKEMASK_INVALID", USERLEN + HOSTLEN + 1);
2848             return 0;
2849         }
2850
2851         safestrncpy(mask, argv[1], sizeof(mask));
2852
2853         if ((host = strrchr(mask, '@')) && host != mask) {
2854             /* If ident@host was used and the user doesn't have access to set idents, do not change anything. */
2855             if (!oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
2856                 host = NULL;
2857                 ident = NULL;
2858             } else {
2859                 ident = mask;
2860                 *host++ = '\0';
2861             }
2862         } else {
2863             ident = NULL;
2864             host = mask;
2865         }
2866
2867         if (ident && strlen(ident) > USERLEN) {
2868             send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
2869             return 0;
2870         }
2871
2872         if (host && ((strlen(host) > HOSTLEN) || (host[0] == '.'))) {
2873             send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
2874             return 0;
2875         }
2876
2877         if (host && host[0]) {
2878             free(hi->fakehost);
2879             if (!strcmp(host, "*"))
2880                 hi->fakehost = NULL;
2881             else
2882                 hi->fakehost = strdup(host);
2883             host = hi->fakehost;
2884         }
2885         else
2886             host = generate_fakehost(hi);
2887
2888         if (ident) {
2889             free(hi->fakeident);
2890             if (!strcmp(ident, "*"))
2891                 hi->fakeident = NULL;
2892             else
2893                 hi->fakeident = strdup(ident);
2894             ident = hi->fakeident;
2895         }
2896         else
2897             ident = generate_fakeident(hi, NULL);
2898
2899         apply_fakehost(hi, NULL);
2900     } else {
2901         host = generate_fakehost(hi);
2902         ident = generate_fakeident(hi, NULL);
2903     }
2904     if (!host)
2905         host = (char *) user_find_message(user, "MSG_NONE");
2906     if(ident)
2907         send_message(user, nickserv, "NSMSG_SET_FAKEIDENTHOST", ident, host);
2908     else
2909         send_message(user, nickserv, "NSMSG_SET_FAKEHOST", host);
2910     return 1;
2911 }
2912
2913 static OPTION_FUNC(opt_fakeident)
2914 {
2915     const char *ident;
2916
2917     if (!override) {
2918         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2919         return 0;
2920     }
2921
2922     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
2923         ident = argv[1];
2924         if (strlen(ident) > USERLEN) {
2925             send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
2926             return 0;
2927         }
2928         free(hi->fakeident);
2929         if (!strcmp(ident, "*"))
2930             hi->fakeident = NULL;
2931         else
2932             hi->fakeident = strdup(ident);
2933         ident = hi->fakeident;
2934         apply_fakehost(hi, NULL);
2935     } else
2936         ident = generate_fakeident(hi, NULL); /* NULL if no fake ident set */
2937     if (!ident)
2938         ident = user_find_message(user, "MSG_NONE");
2939     send_message(user, nickserv, "NSMSG_SET_FAKEIDENT", ident);
2940     return 1;
2941 }
2942
2943 static NICKSERV_FUNC(cmd_reclaim)
2944 {
2945     struct nick_info *ni;
2946     struct userNode *victim;
2947
2948     NICKSERV_MIN_PARMS(2);
2949     ni = dict_find(nickserv_nick_dict, argv[1], 0);
2950     if (!ni) {
2951         reply("NSMSG_UNKNOWN_NICK", argv[1]);
2952         return 0;
2953     }
2954     if (ni->owner != user->handle_info) {
2955         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
2956         return 0;
2957     }
2958     victim = GetUserH(ni->nick);
2959     if (!victim) {
2960         reply("MSG_NICK_UNKNOWN", ni->nick);
2961         return 0;
2962     }
2963     if (victim == user) {
2964         reply("NSMSG_NICK_USER_YOU");
2965         return 0;
2966     }
2967     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
2968     switch (nickserv_conf.reclaim_action) {
2969     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
2970     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
2971     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
2972     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
2973     }
2974     return 1;
2975 }
2976
2977 static NICKSERV_FUNC(cmd_unregnick)
2978 {
2979     const char *nick;
2980     struct handle_info *hi;
2981     struct nick_info *ni;
2982
2983     hi = user->handle_info;
2984     nick = (argc < 2) ? user->nick : (const char*)argv[1];
2985     ni = dict_find(nickserv_nick_dict, nick, NULL);
2986     if (!ni) {
2987         reply("NSMSG_UNKNOWN_NICK", nick);
2988         return 0;
2989     }
2990     if (hi != ni->owner) {
2991         reply("NSMSG_NOT_YOUR_NICK", nick);
2992         return 0;
2993     }
2994     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2995     delete_nick(ni);
2996     return 1;
2997 }
2998
2999 static NICKSERV_FUNC(cmd_ounregnick)
3000 {
3001     struct nick_info *ni;
3002
3003     NICKSERV_MIN_PARMS(2);
3004     if (!(ni = get_nick_info(argv[1]))) {
3005         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
3006         return 0;
3007     }
3008     if (!oper_outranks(user, ni->owner))
3009         return 0;
3010     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
3011     delete_nick(ni);
3012     return 1;
3013 }
3014
3015 static NICKSERV_FUNC(cmd_unregister)
3016 {
3017     struct handle_info *hi;
3018     char *passwd;
3019
3020     NICKSERV_MIN_PARMS(2);
3021     hi = user->handle_info;
3022     passwd = argv[1];
3023     argv[1] = "****";
3024     if (checkpass(passwd, hi->passwd)) {
3025         nickserv_unregister_handle(hi, user);
3026         return 1;
3027     } else {
3028         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
3029         reply("NSMSG_PASSWORD_INVALID");
3030         return 0;
3031     }
3032 }
3033
3034 static NICKSERV_FUNC(cmd_ounregister)
3035 {
3036     struct handle_info *hi;
3037     char reason[MAXLEN];
3038     int force;
3039
3040     NICKSERV_MIN_PARMS(2);
3041     if (!(hi = get_victim_oper(user, argv[1])))
3042         return 0;
3043
3044     if (HANDLE_FLAGGED(hi, NODELETE)) {
3045         reply("NSMSG_UNREGISTER_NODELETE", hi->handle);
3046         return 0;
3047     }
3048
3049     force = IsOper(user) && (argc > 2) && !irccasecmp(argv[2], "force");
3050     if (!force &&
3051         ((hi->flags & nickserv_conf.ounregister_flags)
3052          || hi->users
3053          || (hi->last_quit_host[0] && ((unsigned)(now - hi->lastseen) < nickserv_conf.ounregister_inactive)))) {
3054         reply((IsOper(user) ? "NSMSG_UNREGISTER_MUST_FORCE" : "NSMSG_UNREGISTER_CANNOT_FORCE"), hi->handle);
3055         return 0;
3056     }
3057
3058     snprintf(reason, sizeof(reason), "%s unregistered account %s.", user->handle_info->handle, hi->handle);
3059     global_message(MESSAGE_RECIPIENT_STAFF, reason);
3060     nickserv_unregister_handle(hi, user);
3061     return 1;
3062 }
3063
3064 static NICKSERV_FUNC(cmd_status)
3065 {
3066     if (nickserv_conf.disable_nicks) {
3067         reply("NSMSG_GLOBAL_STATS_NONICK",
3068                         dict_size(nickserv_handle_dict));
3069     } else {
3070         if (user->handle_info) {
3071             int cnt=0;
3072             struct nick_info *ni;
3073             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
3074             reply("NSMSG_HANDLE_STATS", cnt);
3075         } else {
3076             reply("NSMSG_HANDLE_NONE");
3077         }
3078         reply("NSMSG_GLOBAL_STATS",
3079               dict_size(nickserv_handle_dict),
3080               dict_size(nickserv_nick_dict));
3081     }
3082     return 1;
3083 }
3084
3085 static NICKSERV_FUNC(cmd_ghost)
3086 {
3087     struct userNode *target;
3088     char reason[MAXLEN];
3089
3090     NICKSERV_MIN_PARMS(2);
3091     if (!(target = GetUserH(argv[1]))) {
3092         reply("MSG_NICK_UNKNOWN", argv[1]);
3093         return 0;
3094     }
3095     if (target == user) {
3096         reply("NSMSG_CANNOT_GHOST_SELF");
3097         return 0;
3098     }
3099     if (!target->handle_info || (target->handle_info != user->handle_info)) {
3100         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
3101         return 0;
3102     }
3103     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
3104     DelUser(target, nickserv, 1, reason);
3105     reply("NSMSG_GHOST_KILLED", argv[1]);
3106     return 1;
3107 }
3108
3109 static NICKSERV_FUNC(cmd_vacation)
3110 {
3111     HANDLE_SET_FLAG(user->handle_info, FROZEN);
3112     reply("NSMSG_ON_VACATION");
3113     return 1;
3114 }
3115
3116 static NICKSERV_FUNC(cmd_addnote)
3117 {
3118     struct handle_info *hi;
3119     unsigned long duration;
3120     char text[MAXLEN];
3121     unsigned int id;
3122     struct handle_note *prev;
3123     struct handle_note *note;
3124
3125     /* Parse parameters and figure out values for note's fields. */
3126     NICKSERV_MIN_PARMS(4);
3127     hi = get_victim_oper(user, argv[1]);
3128     if (!hi)
3129         return 0;
3130     if(!strcmp(argv[2], "0"))
3131         duration = 0;
3132     else if(!(duration = ParseInterval(argv[2])))
3133     {
3134         reply("MSG_INVALID_DURATION", argv[2]);
3135         return 0;
3136     }
3137     if (duration > 2*365*86400) {
3138         reply("NSMSG_EXCESSIVE_DURATION", argv[2]);
3139         return 0;
3140     }
3141     unsplit_string(argv + 3, argc - 3, text);
3142     WALK_NOTES(hi, prev, note) {}
3143     id = prev ? (prev->id + 1) : 1;
3144
3145     /* Create the new note structure. */
3146     note = calloc(1, sizeof(*note) + strlen(text));
3147     note->next = NULL;
3148     note->expires = duration ? (now + duration) : 0;
3149     note->set = now;
3150     note->id = id;
3151     safestrncpy(note->setter, user->handle_info->handle, sizeof(note->setter));
3152     strcpy(note->note, text);
3153     if (prev)
3154         prev->next = note;
3155     else
3156         hi->notes = note;
3157     reply("NSMSG_NOTE_ADDED", id, hi->handle);
3158     return 1;
3159 }
3160
3161 static NICKSERV_FUNC(cmd_delnote)
3162 {
3163     struct handle_info *hi;
3164     struct handle_note *prev;
3165     struct handle_note *note;
3166     int id;
3167
3168     NICKSERV_MIN_PARMS(3);
3169     hi = get_victim_oper(user, argv[1]);
3170     if (!hi)
3171         return 0;
3172     id = strtoul(argv[2], NULL, 10);
3173     WALK_NOTES(hi, prev, note) {
3174         if (id == note->id) {
3175             if (prev)
3176                 prev->next = note->next;
3177             else
3178                 hi->notes = note->next;
3179             free(note);
3180             reply("NSMSG_NOTE_REMOVED", id, hi->handle);
3181             return 1;
3182         }
3183     }
3184     reply("NSMSG_NO_SUCH_NOTE", hi->handle, id);
3185     return 0;
3186 }
3187
3188 static int
3189 nickserv_saxdb_write(struct saxdb_context *ctx) {
3190     dict_iterator_t it;
3191     struct handle_info *hi;
3192     char flags[33];
3193
3194     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3195         hi = iter_data(it);
3196         assert(hi->id != 0);
3197         saxdb_start_record(ctx, iter_key(it), 0);
3198         if (hi->cookie) {
3199             struct handle_cookie *cookie = hi->cookie;
3200             char *type;
3201
3202             switch (cookie->type) {
3203             case ACTIVATION: type = KEY_ACTIVATION; break;
3204             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
3205             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
3206             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
3207             default: type = NULL; break;
3208             }
3209             if (type) {
3210                 saxdb_start_record(ctx, KEY_COOKIE, 0);
3211                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
3212                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
3213                 if (cookie->data)
3214                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
3215                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
3216                 saxdb_end_record(ctx);
3217             }
3218         }
3219         if (hi->notes) {
3220             struct handle_note *prev, *note;
3221             saxdb_start_record(ctx, KEY_NOTES, 0);
3222             WALK_NOTES(hi, prev, note) {
3223                 snprintf(flags, sizeof(flags), "%d", note->id);
3224                 saxdb_start_record(ctx, flags, 0);
3225                 if (note->expires)
3226                     saxdb_write_int(ctx, KEY_NOTE_EXPIRES, note->expires);
3227                 saxdb_write_int(ctx, KEY_NOTE_SET, note->set);
3228                 saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
3229                 saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
3230                 saxdb_end_record(ctx);
3231             }
3232             saxdb_end_record(ctx);
3233         }
3234         if (hi->email_addr)
3235             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
3236         if (hi->epithet)
3237             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
3238         if (hi->fakehost)
3239             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
3240         if (hi->fakeident)
3241             saxdb_write_string(ctx, KEY_FAKEIDENT, hi->fakeident);
3242         if (hi->flags) {
3243             int ii, flen;
3244
3245             for (ii=flen=0; handle_flags[ii]; ++ii)
3246                 if (hi->flags & (1 << ii))
3247                     flags[flen++] = handle_flags[ii];
3248             flags[flen] = 0;
3249             saxdb_write_string(ctx, KEY_FLAGS, flags);
3250         }
3251         saxdb_write_int(ctx, KEY_ID, hi->id);
3252         if (hi->infoline)
3253             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
3254         if (hi->devnull)
3255             saxdb_write_string(ctx, KEY_DEVNULL, hi->devnull);
3256         if (hi->website)
3257             saxdb_write_string(ctx, KEY_WEBSITE, hi->website);
3258         if (hi->last_quit_host[0])
3259             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
3260         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
3261         if (hi->karma != 0)
3262             saxdb_write_sint(ctx, KEY_KARMA, hi->karma);
3263         if (hi->masks->used)
3264             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
3265         if (hi->maxlogins)
3266             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
3267         if (hi->nicks) {
3268             struct string_list *slist;
3269             struct nick_info *ni;
3270
3271             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
3272             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
3273             saxdb_write_string_list(ctx, KEY_NICKS, slist);
3274             free(slist->list);
3275             free(slist);
3276         }
3277         if (hi->opserv_level)
3278             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
3279         if (hi->language != lang_C)
3280             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
3281         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
3282         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
3283         if (hi->screen_width)
3284             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
3285         if (hi->table_width)
3286             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
3287         flags[0] = hi->userlist_style;
3288         flags[1] = 0;
3289         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
3290         saxdb_end_record(ctx);
3291     }
3292     return 0;
3293 }
3294
3295 static handle_merge_func_t *handle_merge_func_list;
3296 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
3297
3298 void
3299 reg_handle_merge_func(handle_merge_func_t func)
3300 {
3301     if (handle_merge_func_used == handle_merge_func_size) {
3302         if (handle_merge_func_size) {
3303             handle_merge_func_size <<= 1;
3304             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
3305         } else {
3306             handle_merge_func_size = 8;
3307             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
3308         }
3309     }
3310     handle_merge_func_list[handle_merge_func_used++] = func;
3311 }
3312
3313 static NICKSERV_FUNC(cmd_merge)
3314 {
3315     struct handle_info *hi_from, *hi_to;
3316     struct userNode *last_user;
3317     struct userData *cList, *cListNext;
3318     unsigned int ii, jj, n;
3319     char buffer[MAXLEN];
3320
3321     NICKSERV_MIN_PARMS(3);
3322
3323     if (!(hi_from = get_victim_oper(user, argv[1])))
3324         return 0;
3325     if (!(hi_to = get_victim_oper(user, argv[2])))
3326         return 0;
3327     if (hi_to == hi_from) {
3328         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
3329         return 0;
3330     }
3331
3332     for (n=0; n<handle_merge_func_used; n++)
3333         handle_merge_func_list[n](user, hi_to, hi_from);
3334
3335     /* Append "from" handle's nicks to "to" handle's nick list. */
3336     if (hi_to->nicks) {
3337         struct nick_info *last_ni;
3338         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
3339         last_ni->next = hi_from->nicks;
3340     }
3341     while (hi_from->nicks) {
3342         hi_from->nicks->owner = hi_to;
3343         hi_from->nicks = hi_from->nicks->next;
3344     }
3345
3346     /* Merge the hostmasks. */
3347     for (ii=0; ii<hi_from->masks->used; ii++) {
3348         char *mask = hi_from->masks->list[ii];
3349         for (jj=0; jj<hi_to->masks->used; jj++)
3350             if (match_ircglobs(hi_to->masks->list[jj], mask))
3351                 break;
3352         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
3353             string_list_append(hi_to->masks, strdup(mask));
3354     }
3355
3356     /* Merge the lists of authed users. */
3357     if (hi_to->users) {
3358         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
3359         last_user->next_authed = hi_from->users;
3360     } else {
3361         hi_to->users = hi_from->users;
3362     }
3363     /* Repoint the old "from" handle's users. */
3364     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
3365         last_user->handle_info = hi_to;
3366     }
3367     hi_from->users = NULL;
3368
3369     /* Merge channel userlists. */
3370     for (cList=hi_from->channels; cList; cList=cListNext) {
3371         struct userData *cList2;
3372         cListNext = cList->u_next;
3373         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
3374             if (cList->channel == cList2->channel)
3375                 break;
3376         if (cList2 && (cList2->access >= cList->access)) {
3377             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);
3378             /* keep cList2 in hi_to; remove cList from hi_from */
3379             del_channel_user(cList, 1);
3380         } else {
3381             if (cList2) {
3382                 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);
3383                 /* remove the lower-ranking cList2 from hi_to */
3384                 del_channel_user(cList2, 1);
3385             } else {
3386                 log_module(NS_LOG, LOG_INFO, "Merge: %s had no access in %s", hi_to->handle, cList->channel->channel->name);
3387             }
3388             /* cList needs to be moved from hi_from to hi_to */
3389             cList->handle = hi_to;
3390             /* Remove from linked list for hi_from */
3391             assert(!cList->u_prev);
3392             hi_from->channels = cList->u_next;
3393             if (cList->u_next)
3394                 cList->u_next->u_prev = cList->u_prev;
3395             /* Add to linked list for hi_to */
3396             cList->u_prev = NULL;
3397             cList->u_next = hi_to->channels;
3398             if (hi_to->channels)
3399                 hi_to->channels->u_prev = cList;
3400             hi_to->channels = cList;
3401         }
3402     }
3403
3404     /* Do they get an OpServ level promotion? */
3405     if (hi_from->opserv_level > hi_to->opserv_level)
3406         hi_to->opserv_level = hi_from->opserv_level;
3407
3408     /* What about last seen time? */
3409     if (hi_from->lastseen > hi_to->lastseen)
3410         hi_to->lastseen = hi_from->lastseen;
3411
3412     /* New karma is the sum of the two original karmas. */
3413     hi_to->karma += hi_from->karma;
3414
3415     /* Does a fakehost carry over?  (This intentionally doesn't set it
3416      * for users previously attached to hi_to.  They'll just have to
3417      * reconnect.)
3418      */
3419     if (hi_from->fakehost && !hi_to->fakehost)
3420         hi_to->fakehost = strdup(hi_from->fakehost);
3421     if (hi_from->fakeident && !hi_to->fakeident)
3422         hi_to->fakeident = strdup(hi_from->fakeident);
3423
3424     /* Notify of success. */
3425     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
3426     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
3427     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
3428
3429     /* Unregister the "from" handle. */
3430     nickserv_unregister_handle(hi_from, NULL);
3431
3432     return 1;
3433 }
3434
3435 struct nickserv_discrim {
3436     unsigned long flags_on, flags_off;
3437     unsigned long min_registered, max_registered;
3438     unsigned long lastseen;
3439     unsigned int limit;
3440     int min_level, max_level;
3441     int min_karma, max_karma;
3442     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
3443     const char *nickmask;
3444     const char *hostmask;
3445     const char *fakehostmask;
3446     const char *fakeidentmask;
3447     const char *website;
3448     const char *devnullclass;
3449     const char *handlemask;
3450     const char *emailmask;
3451 };
3452
3453 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
3454
3455 struct discrim_apply_info {
3456     struct nickserv_discrim *discrim;
3457     discrim_search_func func;
3458     struct userNode *source;
3459     unsigned int matched;
3460 };
3461
3462 static struct nickserv_discrim *
3463 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
3464 {
3465     unsigned int i;
3466     struct nickserv_discrim *discrim;
3467
3468     discrim = malloc(sizeof(*discrim));
3469     memset(discrim, 0, sizeof(*discrim));
3470     discrim->min_level = 0;
3471     discrim->max_level = INT_MAX;
3472     discrim->limit = 50;
3473     discrim->min_registered = 0;
3474     discrim->max_registered = ULONG_MAX;
3475     discrim->lastseen = ULONG_MAX;
3476     discrim->min_karma = INT_MIN;
3477     discrim->max_karma = INT_MAX;
3478
3479     for (i=0; i<argc; i++) {
3480         if (i == argc - 1) {
3481             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3482             goto fail;
3483         }
3484         if (!irccasecmp(argv[i], "limit")) {
3485             discrim->limit = strtoul(argv[++i], NULL, 0);
3486         } else if (!irccasecmp(argv[i], "flags")) {
3487             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
3488         } else if (!irccasecmp(argv[i], "registered")) {
3489             const char *cmp = argv[++i];
3490             if (cmp[0] == '<') {
3491                 if (cmp[1] == '=') {
3492                     discrim->min_registered = now - ParseInterval(cmp+2);
3493                 } else {
3494                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
3495                 }
3496             } else if (cmp[0] == '=') {
3497                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
3498             } else if (cmp[0] == '>') {
3499                 if (cmp[1] == '=') {
3500                     discrim->max_registered = now - ParseInterval(cmp+2);
3501                 } else {
3502                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
3503                 }
3504             } else {
3505                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3506             }
3507         } else if (!irccasecmp(argv[i], "seen")) {
3508             discrim->lastseen = now - ParseInterval(argv[++i]);
3509         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
3510             discrim->nickmask = argv[++i];
3511         } else if (!irccasecmp(argv[i], "hostmask")) {
3512             i++;
3513             if (!irccasecmp(argv[i], "exact")) {
3514                 if (i == argc - 1) {
3515                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3516                     goto fail;
3517                 }
3518                 discrim->hostmask_type = EXACT;
3519             } else if (!irccasecmp(argv[i], "subset")) {
3520                 if (i == argc - 1) {
3521                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3522                     goto fail;
3523                 }
3524                 discrim->hostmask_type = SUBSET;
3525             } else if (!irccasecmp(argv[i], "superset")) {
3526                 if (i == argc - 1) {
3527                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3528                     goto fail;
3529                 }
3530                 discrim->hostmask_type = SUPERSET;
3531             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
3532                 if (i == argc - 1) {
3533                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3534                     goto fail;
3535                 }
3536                 discrim->hostmask_type = LASTQUIT;
3537             } else {
3538                 i--;
3539                 discrim->hostmask_type = SUPERSET;
3540             }
3541             discrim->hostmask = argv[++i];
3542         } else if (!irccasecmp(argv[i], "fakehost")) {
3543             if (!irccasecmp(argv[++i], "*")) {
3544                 discrim->fakehostmask = 0;
3545             } else {
3546                 discrim->fakehostmask = argv[i];
3547             }
3548         } else if (!irccasecmp(argv[i], "fakeident")) {
3549             if (!irccasecmp(argv[++i], "*")) {
3550                 discrim->fakeidentmask = 0;
3551             } else {
3552                 discrim->fakeidentmask = argv[i];
3553             }
3554         } else if (!irccasecmp(argv[i], "website")) {
3555             if (!irccasecmp(argv[++i], "*")) {
3556                 discrim->website = 0;
3557             } else {
3558                 discrim->website = argv[i];
3559             }
3560         } else if (!irccasecmp(argv[i], "devnull")) {
3561             if (!irccasecmp(argv[++i], "*")) {
3562                 discrim->devnullclass = 0;
3563             } else {
3564                 discrim->devnullclass = argv[i];
3565             }
3566         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
3567             if (!irccasecmp(argv[++i], "*")) {
3568                 discrim->handlemask = 0;
3569             } else {
3570                 discrim->handlemask = argv[i];
3571             }
3572         } else if (!irccasecmp(argv[i], "email")) {
3573             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
3574                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
3575                 goto fail;
3576             } else if (!irccasecmp(argv[++i], "*")) {
3577                 discrim->emailmask = 0;
3578             } else {
3579                 discrim->emailmask = argv[i];
3580             }
3581         } else if (!irccasecmp(argv[i], "access")) {
3582             const char *cmp = argv[++i];
3583             if (cmp[0] == '<') {
3584                 if (discrim->min_level == 0) discrim->min_level = 1;
3585                 if (cmp[1] == '=') {
3586                     discrim->max_level = strtoul(cmp+2, NULL, 0);
3587                 } else {
3588                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
3589                 }
3590             } else if (cmp[0] == '=') {
3591                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
3592             } else if (cmp[0] == '>') {
3593                 if (cmp[1] == '=') {
3594                     discrim->min_level = strtoul(cmp+2, NULL, 0);
3595                 } else {
3596                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
3597                 }
3598             } else {
3599                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3600             }
3601         } else if (!irccasecmp(argv[i], "karma")) {
3602             const char *cmp = argv[++i];
3603             if (cmp[0] == '<') {
3604                 if (cmp[1] == '=') {
3605                     discrim->max_karma = strtoul(cmp+2, NULL, 0);
3606                 } else {
3607                     discrim->max_karma = strtoul(cmp+1, NULL, 0) - 1;
3608                 }
3609             } else if (cmp[0] == '=') {
3610                 discrim->min_karma = discrim->max_karma = strtoul(cmp+1, NULL, 0);
3611             } else if (cmp[0] == '>') {
3612                 if (cmp[1] == '=') {
3613                     discrim->min_karma = strtoul(cmp+2, NULL, 0);
3614                 } else {
3615                     discrim->min_karma = strtoul(cmp+1, NULL, 0) + 1;
3616                 }
3617             } else {
3618                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3619             }
3620         } else {
3621             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
3622             goto fail;
3623         }
3624     }
3625     return discrim;
3626   fail:
3627     free(discrim);
3628     return NULL;
3629 }
3630
3631 static int
3632 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
3633 {
3634     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
3635         || (discrim->flags_off & hi->flags)
3636         || (discrim->min_registered > hi->registered)
3637         || (discrim->max_registered < hi->registered)
3638         || (discrim->lastseen < (hi->users?now:hi->lastseen))
3639         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
3640         || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
3641         || (discrim->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask)))
3642         || (discrim->website && (!hi->website || !match_ircglob(hi->website, discrim->website)))
3643         || (discrim->devnullclass && (!hi->devnull || !match_ircglob(hi->devnull, discrim->devnullclass)))
3644         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
3645         || (discrim->min_level > hi->opserv_level)
3646         || (discrim->max_level < hi->opserv_level)
3647         || (discrim->min_karma > hi->karma)
3648         || (discrim->max_karma < hi->karma)
3649         ) {
3650         return 0;
3651     }
3652     if (discrim->hostmask) {
3653         unsigned int i;
3654         for (i=0; i<hi->masks->used; i++) {
3655             const char *mask = hi->masks->list[i];
3656             if ((discrim->hostmask_type == SUBSET)
3657                 && (match_ircglobs(discrim->hostmask, mask))) break;
3658             else if ((discrim->hostmask_type == EXACT)
3659                      && !irccasecmp(discrim->hostmask, mask)) break;
3660             else if ((discrim->hostmask_type == SUPERSET)
3661                      && (match_ircglobs(mask, discrim->hostmask))) break;
3662             else if ((discrim->hostmask_type == LASTQUIT)
3663                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
3664         }
3665         if (i==hi->masks->used) return 0;
3666     }
3667     if (discrim->nickmask) {
3668         struct nick_info *nick = hi->nicks;
3669         while (nick) {
3670             if (match_ircglob(nick->nick, discrim->nickmask)) break;
3671             nick = nick->next;
3672         }
3673         if (!nick) return 0;
3674     }
3675     return 1;
3676 }
3677
3678 static unsigned int
3679 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
3680 {
3681     dict_iterator_t it, next;
3682     unsigned int matched;
3683
3684     for (it = dict_first(nickserv_handle_dict), matched = 0;
3685          it && (matched < discrim->limit);
3686          it = next) {
3687         next = iter_next(it);
3688         if (nickserv_discrim_match(discrim, iter_data(it))) {
3689             dsf(source, iter_data(it));
3690             matched++;
3691         }
3692     }
3693     return matched;
3694 }
3695
3696 static void
3697 search_print_func(struct userNode *source, struct handle_info *match)
3698 {
3699     send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
3700 }
3701
3702 static void
3703 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
3704 {
3705 }
3706
3707 static void
3708 search_unregister_func (struct userNode *source, struct handle_info *match)
3709 {
3710     if (oper_has_access(source, nickserv, match->opserv_level, 0))
3711         nickserv_unregister_handle(match, source);
3712 }
3713
3714 static int
3715 nickserv_sort_accounts_by_access(const void *a, const void *b)
3716 {
3717     const struct handle_info *hi_a = *(const struct handle_info**)a;
3718     const struct handle_info *hi_b = *(const struct handle_info**)b;
3719     if (hi_a->opserv_level != hi_b->opserv_level)
3720         return hi_b->opserv_level - hi_a->opserv_level;
3721     return irccasecmp(hi_a->handle, hi_b->handle);
3722 }
3723
3724 void
3725 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
3726 {
3727     struct handle_info_list hil;
3728     struct helpfile_table tbl;
3729     unsigned int ii;
3730     dict_iterator_t it;
3731     const char **ary;
3732
3733     memset(&hil, 0, sizeof(hil));
3734     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3735         struct handle_info *hi = iter_data(it);
3736         if (hi->opserv_level)
3737             handle_info_list_append(&hil, hi);
3738     }
3739     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
3740     tbl.length = hil.used + 1;
3741     tbl.width = 2;
3742     tbl.flags = TABLE_NO_FREE;
3743     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
3744     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
3745     ary[0] = "Account";
3746     ary[1] = "Level";
3747     for (ii = 0; ii < hil.used; ) {
3748         ary = malloc(tbl.width * sizeof(ary[0]));
3749         ary[0] = hil.list[ii]->handle;
3750         ary[1] = strtab(hil.list[ii]->opserv_level);
3751         tbl.contents[++ii] = ary;
3752     }
3753     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3754     reply("MSG_MATCH_COUNT", hil.used);
3755     for (ii = 0; ii < hil.used; ii++)
3756         free(tbl.contents[ii]);
3757     free(tbl.contents);
3758     free(hil.list);
3759 }
3760
3761 static NICKSERV_FUNC(cmd_search)
3762 {
3763     struct nickserv_discrim *discrim;
3764     discrim_search_func action;
3765     struct svccmd *subcmd;
3766     unsigned int matches;
3767     char buf[MAXLEN];
3768
3769     NICKSERV_MIN_PARMS(3);
3770     sprintf(buf, "search %s", argv[1]);
3771     subcmd = dict_find(nickserv_service->commands, buf, NULL);
3772     if (!irccasecmp(argv[1], "print"))
3773         action = search_print_func;
3774     else if (!irccasecmp(argv[1], "count"))
3775         action = search_count_func;
3776     else if (!irccasecmp(argv[1], "unregister"))
3777         action = search_unregister_func;
3778     else {
3779         reply("NSMSG_INVALID_ACTION", argv[1]);
3780         return 0;
3781     }
3782
3783     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
3784         return 0;
3785
3786     discrim = nickserv_discrim_create(user, argc-2, argv+2);
3787     if (!discrim)
3788         return 0;
3789
3790     if (action == search_print_func)
3791         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
3792     else if (action == search_count_func)
3793         discrim->limit = INT_MAX;
3794
3795     matches = nickserv_discrim_search(discrim, action, user);
3796
3797     if (matches)
3798         reply("MSG_MATCH_COUNT", matches);
3799     else
3800         reply("MSG_NO_MATCHES");
3801
3802     free(discrim);
3803     return 0;
3804 }
3805
3806 static MODCMD_FUNC(cmd_checkpass)
3807 {
3808     struct handle_info *hi;
3809
3810     NICKSERV_MIN_PARMS(3);
3811     if (!(hi = get_handle_info(argv[1]))) {
3812         reply("MSG_HANDLE_UNKNOWN", argv[1]);
3813         return 0;
3814     }
3815     if (checkpass(argv[2], hi->passwd))
3816         reply("CHECKPASS_YES");
3817     else
3818         reply("CHECKPASS_NO");
3819     argv[2] = "****";
3820     return 1;
3821 }
3822
3823 static MODCMD_FUNC(cmd_checkemail)
3824 {
3825     struct handle_info *hi;
3826
3827     NICKSERV_MIN_PARMS(3);
3828     if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
3829         return 0;
3830     }
3831     if (!hi->email_addr)
3832         reply("CHECKEMAIL_NOT_SET");
3833     else if (!irccasecmp(argv[2], hi->email_addr))
3834         reply("CHECKEMAIL_YES");
3835     else
3836         reply("CHECKEMAIL_NO");
3837     return 1;
3838 }
3839
3840
3841 static void
3842 nickserv_db_read_handle(const char *handle, dict_t obj)
3843 {
3844     const char *str;
3845     struct string_list *masks, *slist;
3846     struct handle_info *hi;
3847     struct userNode *authed_users;
3848     struct userData *channel_list;
3849     unsigned long id;
3850     unsigned int ii;
3851     dict_t subdb;
3852
3853     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
3854     id = str ? strtoul(str, NULL, 0) : 0;
3855     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
3856     if (!str) {
3857         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
3858         return;
3859     }
3860     if ((hi = get_handle_info(handle))) {
3861         authed_users = hi->users;
3862         channel_list = hi->channels;
3863         hi->users = NULL;
3864         hi->channels = NULL;
3865         dict_remove(nickserv_handle_dict, hi->handle);
3866     } else {
3867         authed_users = NULL;
3868         channel_list = NULL;
3869     }
3870     hi = register_handle(handle, str, id);
3871     if (authed_users) {
3872         hi->users = authed_users;
3873         while (authed_users) {
3874             authed_users->handle_info = hi;
3875             authed_users = authed_users->next_authed;
3876         }
3877     }
3878     hi->channels = channel_list;
3879     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
3880     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
3881     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
3882     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
3883     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
3884     hi->language = language_find(str ? str : "C");
3885     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
3886     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
3887     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
3888     if (str)
3889         hi->infoline = strdup(str);
3890     str = database_get_data(obj, KEY_WEBSITE, RECDB_QSTRING);
3891     if (str)
3892         hi->website = strdup(str);
3893     str = database_get_data(obj, KEY_DEVNULL, RECDB_QSTRING);
3894     if (str)
3895         hi->devnull = strdup(str);
3896     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
3897     hi->registered = str ? strtoul(str, NULL, 0) : now;
3898     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
3899     hi->lastseen = str ? strtoul(str, NULL, 0) : hi->registered;
3900     str = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
3901     hi->karma = str ? strtoul(str, NULL, 0) : 0;
3902     /* We want to read the nicks even if disable_nicks is set.  This is so
3903      * that we don't lose the nick data entirely. */
3904     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
3905     if (slist) {
3906         for (ii=0; ii<slist->used; ii++)
3907             register_nick(slist->list[ii], hi);
3908     }
3909     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
3910     if (str) {
3911         for (ii=0; str[ii]; ii++)
3912             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
3913     }
3914     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
3915     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
3916     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
3917     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
3918     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
3919     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
3920     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
3921     if (!str)
3922         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
3923     if (str)
3924         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
3925     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
3926     if (str)
3927         nickserv_set_email_addr(hi, str);
3928     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
3929     if (str)
3930         hi->epithet = strdup(str);
3931     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
3932     if (str)
3933         hi->fakehost = strdup(str);
3934     str = database_get_data(obj, KEY_FAKEIDENT, RECDB_QSTRING);
3935     if (str)
3936         hi->fakeident = strdup(str);
3937     /* Read the "cookie" sub-database (if it exists). */
3938     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
3939     if (subdb) {
3940         const char *data, *type, *expires, *cookie_str;
3941         struct handle_cookie *cookie;
3942
3943         cookie = calloc(1, sizeof(*cookie));
3944         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
3945         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
3946         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
3947         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
3948         if (!type || !expires || !cookie_str) {
3949             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
3950             goto cookie_out;
3951         }
3952         if (!irccasecmp(type, KEY_ACTIVATION))
3953             cookie->type = ACTIVATION;
3954         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
3955             cookie->type = PASSWORD_CHANGE;
3956         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
3957             cookie->type = EMAIL_CHANGE;
3958         else if (!irccasecmp(type, KEY_ALLOWAUTH))
3959             cookie->type = ALLOWAUTH;
3960         else {
3961             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
3962             goto cookie_out;
3963         }
3964         cookie->expires = strtoul(expires, NULL, 0);
3965         if (cookie->expires < now)
3966             goto cookie_out;
3967         if (data)
3968             cookie->data = strdup(data);
3969         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
3970         cookie->hi = hi;
3971       cookie_out:
3972         if (cookie->hi)
3973             nickserv_bake_cookie(cookie);
3974         else
3975             nickserv_free_cookie(cookie);
3976     }
3977     /* Read the "notes" sub-database (if it exists). */
3978     subdb = database_get_data(obj, KEY_NOTES, RECDB_OBJECT);
3979     if (subdb) {
3980         dict_iterator_t it;
3981         struct handle_note *last_note;
3982         struct handle_note *note;
3983
3984         last_note = NULL;
3985         for (it = dict_first(subdb); it; it = iter_next(it)) {
3986             const char *expires;
3987             const char *setter;
3988             const char *text;
3989             const char *set;
3990             const char *note_id;
3991             dict_t notedb;
3992
3993             note_id = iter_key(it);
3994             notedb = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
3995             if (!notedb) {
3996                 log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", note_id, hi->handle);
3997                 continue;
3998             }
3999             expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
4000             setter = database_get_data(notedb, KEY_NOTE_SETTER, RECDB_QSTRING);
4001             text = database_get_data(notedb, KEY_NOTE_NOTE, RECDB_QSTRING);
4002             set = database_get_data(notedb, KEY_NOTE_SET, RECDB_QSTRING);
4003             if (!setter || !text || !set) {
4004                 log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", note_id, hi->handle);
4005                 continue;
4006             }
4007             note = calloc(1, sizeof(*note) + strlen(text));
4008             note->next = NULL;
4009             note->expires = expires ? strtoul(expires, NULL, 10) : 0;
4010             note->set = strtoul(set, NULL, 10);
4011             note->id = strtoul(note_id, NULL, 10);
4012             safestrncpy(note->setter, setter, sizeof(note->setter));
4013             strcpy(note->note, text);
4014             if (last_note)
4015                 last_note->next = note;
4016             else
4017                 hi->notes = note;
4018             last_note = note;
4019         }
4020     }
4021 }
4022
4023 static int
4024 nickserv_saxdb_read(dict_t db) {
4025     dict_iterator_t it;
4026     struct record_data *rd;
4027
4028     for (it=dict_first(db); it; it=iter_next(it)) {
4029         rd = iter_data(it);
4030         nickserv_db_read_handle(iter_key(it), rd->d.object);
4031     }
4032     return 0;
4033 }
4034
4035 static NICKSERV_FUNC(cmd_mergedb)
4036 {
4037     struct timeval start, stop;
4038     dict_t db;
4039
4040     NICKSERV_MIN_PARMS(2);
4041     gettimeofday(&start, NULL);
4042     if (!(db = parse_database(argv[1]))) {
4043         reply("NSMSG_DB_UNREADABLE", argv[1]);
4044         return 0;
4045     }
4046     nickserv_saxdb_read(db);
4047     free_database(db);
4048     gettimeofday(&stop, NULL);
4049     stop.tv_sec -= start.tv_sec;
4050     stop.tv_usec -= start.tv_usec;
4051     if (stop.tv_usec < 0) {
4052         stop.tv_sec -= 1;
4053         stop.tv_usec += 1000000;
4054     }
4055     reply("NSMSG_DB_MERGED", argv[1], (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
4056     return 1;
4057 }
4058
4059 static void
4060 expire_handles(UNUSED_ARG(void *data))
4061 {
4062     dict_iterator_t it, next;
4063     unsigned long expiry;
4064     struct handle_info *hi;
4065
4066     for (it=dict_first(nickserv_handle_dict); it; it=next) {
4067         next = iter_next(it);
4068         hi = iter_data(it);
4069         if ((hi->opserv_level > 0)
4070             || hi->users
4071             || HANDLE_FLAGGED(hi, FROZEN)
4072             || HANDLE_FLAGGED(hi, NODELETE)) {
4073             continue;
4074         }
4075         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
4076         if ((now - hi->lastseen) > expiry) {
4077             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
4078             nickserv_unregister_handle(hi, NULL);
4079         }
4080     }
4081
4082     if (nickserv_conf.handle_expire_frequency)
4083         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
4084 }
4085
4086 static void
4087 nickserv_load_dict(const char *fname)
4088 {
4089     FILE *file;
4090     char line[128];
4091     if (!(file = fopen(fname, "r"))) {
4092         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
4093         return;
4094     }
4095     while (fgets(line, sizeof(line), file)) {
4096         if (!line[0])
4097             continue;
4098         if (line[strlen(line)-1] == '\n')
4099             line[strlen(line)-1] = 0;
4100         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
4101     }
4102     fclose(file);
4103     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
4104 }
4105
4106 static enum reclaim_action
4107 reclaim_action_from_string(const char *str) {
4108     if (!str)
4109         return RECLAIM_NONE;
4110     else if (!irccasecmp(str, "warn"))
4111         return RECLAIM_WARN;
4112     else if (!irccasecmp(str, "svsnick"))
4113         return RECLAIM_SVSNICK;
4114     else if (!irccasecmp(str, "kill"))
4115         return RECLAIM_KILL;
4116     else
4117         return RECLAIM_NONE;
4118 }
4119
4120 static void
4121 nickserv_conf_read(void)
4122 {
4123     dict_t conf_node, child;
4124     const char *str;
4125     dict_iterator_t it;
4126
4127     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
4128         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
4129         return;
4130     }
4131     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
4132     if (!str)
4133         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
4134     if (nickserv_conf.valid_handle_regex_set)
4135         regfree(&nickserv_conf.valid_handle_regex);
4136     if (str) {
4137         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
4138         nickserv_conf.valid_handle_regex_set = !err;
4139         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
4140     } else {
4141         nickserv_conf.valid_handle_regex_set = 0;
4142     }
4143     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
4144     if (nickserv_conf.valid_nick_regex_set)
4145         regfree(&nickserv_conf.valid_nick_regex);
4146     if (str) {
4147         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
4148         nickserv_conf.valid_nick_regex_set = !err;
4149         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
4150     } else {
4151         nickserv_conf.valid_nick_regex_set = 0;
4152     }
4153     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
4154     if (!str)
4155         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
4156     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
4157     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
4158     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
4159     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
4160     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
4161     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
4162     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
4163     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
4164     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
4165     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
4166     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
4167     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
4168     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
4169     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
4170     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
4171     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
4172     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
4173     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
4174     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
4175     str = database_get_data(conf_node, KEY_SET_TITLE_LEVEL, RECDB_QSTRING);
4176     nickserv_conf.set_title_level = str ? strtoul(str, NULL, 0) : 900;
4177     str = database_get_data(conf_node, KEY_SET_FAKEHOST_LEVEL, RECDB_QSTRING);
4178     nickserv_conf.set_fakehost_level = str ? strtoul(str, NULL, 0) : 1000;
4179     str = database_get_data(conf_node, KEY_SET_FAKEIDENT_LEVEL, RECDB_QSTRING);
4180     nickserv_conf.set_fakeident_level = str ? strtoul(str, NULL, 0) : 1000;
4181     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
4182     if (!str)
4183         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
4184     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
4185     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
4186     if (!str)
4187         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
4188     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
4189     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
4190     if (!str)
4191         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
4192     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
4193     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
4194     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
4195     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
4196     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
4197     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
4198     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
4199     str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
4200     nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
4201     str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
4202     if (!str)
4203         str = "ShgsfnHbu";
4204     nickserv_conf.ounregister_flags = 0;
4205     while(*str) {
4206         unsigned int pos = handle_inverse_flags[(unsigned char)*str];
4207         str++;
4208         if(pos)
4209             nickserv_conf.ounregister_flags |= 1 << (pos - 1);
4210     }
4211     str = database_get_data(conf_node, KEY_HANDLE_TS_MODE, RECDB_QSTRING);
4212     if (!str)
4213         nickserv_conf.handle_ts_mode = TS_IGNORE;
4214     else if (!irccasecmp(str, "ircu"))
4215         nickserv_conf.handle_ts_mode = TS_IRCU;
4216     else
4217         nickserv_conf.handle_ts_mode = TS_IGNORE;
4218     if (!nickserv_conf.disable_nicks) {
4219         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
4220         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
4221         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
4222         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
4223         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
4224         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
4225         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
4226         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
4227     }
4228     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
4229     for (it=dict_first(child); it; it=iter_next(it)) {
4230         const char *key = iter_key(it), *value;
4231         unsigned char flag;
4232         int pos;
4233
4234         if (!strncasecmp(key, "uc_", 3))
4235             flag = toupper(key[3]);
4236         else if (!strncasecmp(key, "lc_", 3))
4237             flag = tolower(key[3]);
4238         else
4239             flag = key[0];
4240
4241         if ((pos = handle_inverse_flags[flag])) {
4242             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
4243             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
4244         }
4245     }
4246     if (nickserv_conf.weak_password_dict)
4247         dict_delete(nickserv_conf.weak_password_dict);
4248     nickserv_conf.weak_password_dict = dict_new();
4249     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
4250     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
4251     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
4252     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
4253     if (str)
4254         nickserv_load_dict(str);
4255     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
4256     if (nickserv && str)
4257         NickChange(nickserv, str, 0);
4258     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
4259     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
4260     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
4261     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
4262     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
4263     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
4264     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
4265     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
4266     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
4267     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
4268     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
4269     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
4270     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
4271     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
4272     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
4273     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
4274     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
4275     titlehost_suffix = str ? str : "example.net";
4276     str = conf_get_data("server/network", RECDB_QSTRING);
4277     nickserv_conf.network_name = str ? str : "some IRC network";
4278     if (!nickserv_conf.auth_policer_params) {
4279         nickserv_conf.auth_policer_params = policer_params_new();
4280         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
4281         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
4282     }
4283     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
4284     for (it=dict_first(child); it; it=iter_next(it))
4285         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
4286 }
4287
4288 static void
4289 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
4290     const char *msg;
4291     char newnick[NICKLEN+1];
4292
4293     assert(user);
4294     assert(ni);
4295     switch (action) {
4296     case RECLAIM_NONE:
4297         /* do nothing */
4298         break;
4299     case RECLAIM_WARN:
4300         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
4301         break;
4302     case RECLAIM_SVSNICK:
4303         do {
4304             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
4305         } while (GetUserH(newnick));
4306         irc_svsnick(nickserv, user, newnick);
4307         break;
4308     case RECLAIM_KILL:
4309         msg = user_find_message(user, "NSMSG_RECLAIM_KILL");
4310         DelUser(user, nickserv, 1, msg);
4311         break;
4312     }
4313 }
4314
4315 static void
4316 nickserv_reclaim_p(void *data) {
4317     struct userNode *user = data;
4318     struct nick_info *ni = get_nick_info(user->nick);
4319     if (ni)
4320         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
4321 }
4322
4323 static void
4324 check_user_nick(struct userNode *user) {
4325     struct nick_info *ni;
4326     user->modes &= ~FLAGS_REGNICK;
4327     if (!(ni = get_nick_info(user->nick)))
4328         return;
4329     if (user->handle_info == ni->owner) {
4330         user->modes |= FLAGS_REGNICK;
4331         irc_regnick(user);
4332         return;
4333     }
4334     if (nickserv_conf.warn_nick_owned)
4335         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
4336     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
4337         return;
4338     if (nickserv_conf.auto_reclaim_delay)
4339         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
4340     else
4341         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
4342 }
4343
4344 void
4345 handle_account(struct userNode *user, const char *stamp, unsigned long timestamp, unsigned long serial)
4346 {
4347     struct handle_info *hi = NULL;
4348
4349     if (stamp != NULL)
4350         hi = dict_find(nickserv_handle_dict, stamp, NULL);
4351     if ((hi == NULL) && (serial != 0)) {
4352         char id[IDLEN + 1];
4353         inttobase64(id, serial, IDLEN);
4354         hi = dict_find(nickserv_id_dict, id, NULL);
4355     }
4356
4357     if (hi) {
4358         if ((nickserv_conf.handle_ts_mode == TS_IRCU)
4359             && (timestamp != hi->registered)) {
4360             return;
4361         }
4362         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
4363             return;
4364         }
4365         set_user_handle_info(user, hi, 0);
4366     } else {
4367         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s:%lu:%lu.", user->nick, stamp, timestamp, serial);
4368     }
4369 }
4370
4371 void
4372 handle_nick_change(struct userNode *user, const char *old_nick)
4373 {
4374     struct handle_info *hi;
4375
4376     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
4377         dict_remove(nickserv_allow_auth_dict, old_nick);
4378         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
4379     }
4380     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4381     check_user_nick(user);
4382 }
4383
4384 void
4385 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
4386 {
4387     dict_remove(nickserv_allow_auth_dict, user->nick);
4388     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4389     set_user_handle_info(user, NULL, 0);
4390 }
4391
4392 static struct modcmd *
4393 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
4394 {
4395     if (min_level > 0) {
4396         char buf[16];
4397         sprintf(buf, "%u", min_level);
4398         if (must_be_qualified) {
4399             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
4400         } else {
4401             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
4402         }
4403     } else if (min_level == 0) {
4404         if (must_be_qualified) {
4405             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4406         } else {
4407             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4408         }
4409     } else {
4410         if (must_be_qualified) {
4411             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
4412         } else {
4413             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
4414         }
4415     }
4416 }
4417
4418 static void
4419 nickserv_db_cleanup(void)
4420 {
4421     unreg_del_user_func(nickserv_remove_user);
4422     userList_clean(&curr_helpers);
4423     policer_params_delete(nickserv_conf.auth_policer_params);
4424     dict_delete(nickserv_handle_dict);
4425     dict_delete(nickserv_nick_dict);
4426     dict_delete(nickserv_opt_dict);
4427     dict_delete(nickserv_allow_auth_dict);
4428     dict_delete(nickserv_email_dict);
4429     dict_delete(nickserv_id_dict);
4430     dict_delete(nickserv_conf.weak_password_dict);
4431     free(auth_func_list);
4432     free(unreg_func_list);
4433     free(rf_list);
4434     free(allowauth_func_list);
4435     free(handle_merge_func_list);
4436     free(failpw_func_list);
4437     if (nickserv_conf.valid_handle_regex_set)
4438         regfree(&nickserv_conf.valid_handle_regex);
4439     if (nickserv_conf.valid_nick_regex_set)
4440         regfree(&nickserv_conf.valid_nick_regex);
4441 }
4442
4443 void
4444 init_nickserv(const char *nick)
4445 {
4446     unsigned int i;
4447     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
4448     reg_new_user_func(check_user_nick);
4449     reg_nick_change_func(handle_nick_change);
4450     reg_del_user_func(nickserv_remove_user);
4451     reg_account_func(handle_account);
4452
4453     /* set up handle_inverse_flags */
4454     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
4455     for (i=0; handle_flags[i]; i++) {
4456         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
4457         flag_access_levels[i] = 0;
4458     }
4459
4460     conf_register_reload(nickserv_conf_read);
4461     nickserv_opt_dict = dict_new();
4462     nickserv_email_dict = dict_new();
4463     dict_set_free_keys(nickserv_email_dict, free);
4464     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
4465
4466     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
4467     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
4468     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
4469     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
4470     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
4471     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
4472     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
4473     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
4474     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
4475     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
4476     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
4477     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
4478     nickserv_define_func("SET", cmd_set, -1, 1, 0);
4479     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
4480     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
4481     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
4482     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
4483     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
4484     nickserv_define_func("MERGE", cmd_merge, 750, 1, 0);
4485     nickserv_define_func("ADDNOTE", cmd_addnote, 0, 1, 0);
4486     nickserv_define_func("DELNOTE", cmd_delnote, 0, 1, 0);
4487     nickserv_define_func("NOTES", cmd_notes, 0, 1, 0);
4488     if (!nickserv_conf.disable_nicks) {
4489         /* nick management commands */
4490         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
4491         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
4492         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
4493         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
4494         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
4495         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
4496     }
4497     if (nickserv_conf.email_enabled) {
4498         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
4499         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
4500         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
4501         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
4502         nickserv_define_func("ODELCOOKIE", cmd_odelcookie, 0, 1, 0);
4503         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
4504     }
4505     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
4506     /* miscellaneous commands */
4507     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
4508     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
4509     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
4510     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
4511     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
4512     nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0);
4513     /* other options */
4514     dict_insert(nickserv_opt_dict, "INFO", opt_info);
4515     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
4516     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
4517     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
4518     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
4519     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
4520     dict_insert(nickserv_opt_dict, "AUTOHIDE", opt_autohide);
4521     dict_insert(nickserv_opt_dict, "PASS", opt_password);
4522     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
4523     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
4524     dict_insert(nickserv_opt_dict, "WEBSITE", opt_website);
4525     dict_insert(nickserv_opt_dict, "DEVNULL", opt_devnull);
4526     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
4527     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
4528     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
4529     if (titlehost_suffix) {
4530         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
4531         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
4532         dict_insert(nickserv_opt_dict, "FAKEIDENT", opt_fakeident);
4533     }
4534     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
4535     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
4536     dict_insert(nickserv_opt_dict, "KARMA", opt_karma);
4537     nickserv_define_func("OSET KARMA", NULL, 0, 1, 0);
4538
4539     nickserv_handle_dict = dict_new();
4540     dict_set_free_keys(nickserv_handle_dict, free);
4541     dict_set_free_data(nickserv_handle_dict, free_handle_info);
4542
4543     nickserv_id_dict = dict_new();
4544     dict_set_free_keys(nickserv_id_dict, free);
4545
4546     nickserv_nick_dict = dict_new();
4547     dict_set_free_data(nickserv_nick_dict, free);
4548
4549     nickserv_allow_auth_dict = dict_new();
4550
4551     userList_init(&curr_helpers);
4552
4553     if (nick) {
4554         const char *modes = conf_get_data("services/nickserv/modes", RECDB_QSTRING);
4555         nickserv = AddLocalUser(nick, nick, NULL, "Nick Services", modes);
4556         nickserv_service = service_register(nickserv);
4557     }
4558     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
4559     reg_exit_func(nickserv_db_cleanup);
4560     if(nickserv_conf.handle_expire_frequency)
4561         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
4562     message_register_table(msgtab);
4563 }