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