Get rid of ugly goto in opt_fakehost.
[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 ident@host was used and the user doesn't have access to set idents, do not change anything.
2654             if (!oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
2655                 host = NULL;
2656                 ident = NULL;
2657             } else {
2658                 ident = mask;
2659                 *host++ = '\0';
2660             }
2661         } else {
2662             ident = NULL;
2663             host = mask;
2664         }
2665
2666         if (ident && strlen(ident) > USERLEN) {
2667             send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
2668             return 0;
2669         }
2670
2671         if (host && ((strlen(host) > HOSTLEN) || (host[0] == '.'))) {
2672             send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
2673             return 0;
2674         }
2675
2676         if (host && host[0]) {
2677             free(hi->fakehost);
2678             if (!strcmp(host, "*"))
2679                 hi->fakehost = NULL;
2680             else
2681                 hi->fakehost = strdup(host);
2682             host = hi->fakehost;
2683         }
2684         else
2685             host = generate_fakehost(hi);
2686
2687         if (ident) {
2688             free(hi->fakeident);
2689             if (!strcmp(ident, "*"))
2690                 hi->fakeident = NULL;
2691             else
2692                 hi->fakeident = strdup(ident);
2693             ident = hi->fakeident;
2694         }
2695         else
2696             ident = generate_fakeident(hi, NULL);
2697
2698         apply_fakehost(hi, NULL);
2699     } else {
2700         host = generate_fakehost(hi);
2701         ident = generate_fakeident(hi, NULL);
2702     }
2703     if (!host)
2704         host = (char *) user_find_message(user, "MSG_NONE");
2705     if(ident)
2706         send_message(user, nickserv, "NSMSG_SET_FAKEIDENTHOST", ident, host);
2707     else
2708         send_message(user, nickserv, "NSMSG_SET_FAKEHOST", host);
2709     return 1;
2710 }
2711
2712 static OPTION_FUNC(opt_fakeident)
2713 {
2714     const char *ident;
2715
2716     if (!override) {
2717         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2718         return 0;
2719     }
2720
2721     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
2722         ident = argv[1];
2723         if (strlen(ident) > USERLEN) {
2724             send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
2725             return 0;
2726         }
2727         free(hi->fakeident);
2728         if (!strcmp(ident, "*"))
2729             hi->fakeident = NULL;
2730         else
2731             hi->fakeident = strdup(ident);
2732         ident = hi->fakeident;
2733         apply_fakehost(hi, NULL);
2734     } else
2735         ident = generate_fakeident(hi, NULL); /* NULL if no fake ident set */
2736     if (!ident)
2737         ident = user_find_message(user, "MSG_NONE");
2738     send_message(user, nickserv, "NSMSG_SET_FAKEIDENT", ident);
2739     return 1;
2740 }
2741
2742 static NICKSERV_FUNC(cmd_reclaim)
2743 {
2744     struct handle_info *hi;
2745     struct nick_info *ni;
2746     struct userNode *victim;
2747
2748     NICKSERV_MIN_PARMS(2);
2749     hi = user->handle_info;
2750     ni = dict_find(nickserv_nick_dict, argv[1], 0);
2751     if (!ni) {
2752         reply("NSMSG_UNKNOWN_NICK", argv[1]);
2753         return 0;
2754     }
2755     if (ni->owner != user->handle_info) {
2756         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
2757         return 0;
2758     }
2759     victim = GetUserH(ni->nick);
2760     if (!victim) {
2761         reply("MSG_NICK_UNKNOWN", ni->nick);
2762         return 0;
2763     }
2764     if (victim == user) {
2765         reply("NSMSG_NICK_USER_YOU");
2766         return 0;
2767     }
2768     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
2769     switch (nickserv_conf.reclaim_action) {
2770     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
2771     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
2772     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
2773     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
2774     }
2775     return 1;
2776 }
2777
2778 static NICKSERV_FUNC(cmd_unregnick)
2779 {
2780     const char *nick;
2781     struct handle_info *hi;
2782     struct nick_info *ni;
2783
2784     hi = user->handle_info;
2785     nick = (argc < 2) ? user->nick : (const char*)argv[1];
2786     ni = dict_find(nickserv_nick_dict, nick, NULL);
2787     if (!ni) {
2788         reply("NSMSG_UNKNOWN_NICK", nick);
2789         return 0;
2790     }
2791     if (hi != ni->owner) {
2792         reply("NSMSG_NOT_YOUR_NICK", nick);
2793         return 0;
2794     }
2795     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2796     delete_nick(ni);
2797     return 1;
2798 }
2799
2800 static NICKSERV_FUNC(cmd_ounregnick)
2801 {
2802     struct nick_info *ni;
2803
2804     NICKSERV_MIN_PARMS(2);
2805     if (!(ni = get_nick_info(argv[1]))) {
2806         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
2807         return 0;
2808     }
2809     if (!oper_outranks(user, ni->owner))
2810         return 0;
2811     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2812     delete_nick(ni);
2813     return 1;
2814 }
2815
2816 static NICKSERV_FUNC(cmd_unregister)
2817 {
2818     struct handle_info *hi;
2819     char *passwd;
2820
2821     NICKSERV_MIN_PARMS(2);
2822     hi = user->handle_info;
2823     passwd = argv[1];
2824     argv[1] = "****";
2825     if (checkpass(passwd, hi->passwd)) {
2826         nickserv_unregister_handle(hi, user);
2827         return 1;
2828     } else {
2829         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
2830         reply("NSMSG_PASSWORD_INVALID");
2831         return 0;
2832     }
2833 }
2834
2835 static NICKSERV_FUNC(cmd_ounregister)
2836 {
2837     struct handle_info *hi;
2838     char reason[MAXLEN];
2839     int force;
2840
2841     NICKSERV_MIN_PARMS(2);
2842     if (!(hi = get_victim_oper(user, argv[1])))
2843         return 0;
2844
2845     if (HANDLE_FLAGGED(hi, NODELETE)) {
2846         reply("NSMSG_UNREGISTER_NODELETE", hi->handle);
2847         return 0;
2848     }
2849
2850     force = IsOper(user) && (argc > 2) && !irccasecmp(argv[2], "force");
2851     if (!force &&
2852         ((hi->flags & nickserv_conf.ounregister_flags)
2853          || hi->users
2854          || (hi->last_quit_host[0] && ((unsigned)(now - hi->lastseen) < nickserv_conf.ounregister_inactive)))) {
2855         reply((IsOper(user) ? "NSMSG_UNREGISTER_MUST_FORCE" : "NSMSG_UNREGISTER_CANNOT_FORCE"), hi->handle);
2856         return 0;
2857     }
2858
2859     snprintf(reason, sizeof(reason), "%s unregistered account %s.", user->handle_info->handle, hi->handle);
2860     global_message(MESSAGE_RECIPIENT_STAFF, reason);
2861     nickserv_unregister_handle(hi, user);
2862     return 1;
2863 }
2864
2865 static NICKSERV_FUNC(cmd_status)
2866 {
2867     if (nickserv_conf.disable_nicks) {
2868         reply("NSMSG_GLOBAL_STATS_NONICK",
2869                         dict_size(nickserv_handle_dict));
2870     } else {
2871         if (user->handle_info) {
2872             int cnt=0;
2873             struct nick_info *ni;
2874             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
2875             reply("NSMSG_HANDLE_STATS", cnt);
2876         } else {
2877             reply("NSMSG_HANDLE_NONE");
2878         }
2879         reply("NSMSG_GLOBAL_STATS",
2880               dict_size(nickserv_handle_dict),
2881               dict_size(nickserv_nick_dict));
2882     }
2883     return 1;
2884 }
2885
2886 static NICKSERV_FUNC(cmd_ghost)
2887 {
2888     struct userNode *target;
2889     char reason[MAXLEN];
2890
2891     NICKSERV_MIN_PARMS(2);
2892     if (!(target = GetUserH(argv[1]))) {
2893         reply("MSG_NICK_UNKNOWN", argv[1]);
2894         return 0;
2895     }
2896     if (target == user) {
2897         reply("NSMSG_CANNOT_GHOST_SELF");
2898         return 0;
2899     }
2900     if (!target->handle_info || (target->handle_info != user->handle_info)) {
2901         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
2902         return 0;
2903     }
2904     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
2905     DelUser(target, nickserv, 1, reason);
2906     reply("NSMSG_GHOST_KILLED", argv[1]);
2907     return 1;
2908 }
2909
2910 static NICKSERV_FUNC(cmd_vacation)
2911 {
2912     HANDLE_SET_FLAG(user->handle_info, FROZEN);
2913     reply("NSMSG_ON_VACATION");
2914     return 1;
2915 }
2916
2917 static NICKSERV_FUNC(cmd_addnote)
2918 {
2919     struct handle_info *hi;
2920     unsigned long duration;
2921     char text[MAXLEN];
2922     unsigned int id;
2923     struct handle_note *prev;
2924     struct handle_note *note;
2925
2926     /* Parse parameters and figure out values for note's fields. */
2927     NICKSERV_MIN_PARMS(4);
2928     hi = get_victim_oper(user, argv[1]);
2929     if (!hi)
2930         return 0;
2931     if(!strcmp(argv[2], "0"))
2932         duration = 0;
2933     else if(!(duration = ParseInterval(argv[2])))
2934     {
2935         reply("MSG_INVALID_DURATION", argv[2]);
2936         return 0;
2937     }
2938     if (duration > 2*365*86400) {
2939         reply("NSMSG_EXCESSIVE_DURATION", argv[2]);
2940         return 0;
2941     }
2942     unsplit_string(argv + 3, argc - 3, text);
2943     WALK_NOTES(hi, prev, note) {}
2944     id = prev ? (prev->id + 1) : 1;
2945
2946     /* Create the new note structure. */
2947     note = calloc(1, sizeof(*note) + strlen(text));
2948     note->next = NULL;
2949     note->expires = duration ? (now + duration) : 0;
2950     note->set = now;
2951     note->id = id;
2952     safestrncpy(note->setter, user->handle_info->handle, sizeof(note->setter));
2953     strcpy(note->note, text);
2954     if (prev)
2955         prev->next = note;
2956     else
2957         hi->notes = note;
2958     reply("NSMSG_NOTE_ADDED", id, hi->handle);
2959     return 1;
2960 }
2961
2962 static NICKSERV_FUNC(cmd_delnote)
2963 {
2964     struct handle_info *hi;
2965     struct handle_note *prev;
2966     struct handle_note *note;
2967     int id;
2968
2969     NICKSERV_MIN_PARMS(3);
2970     hi = get_victim_oper(user, argv[1]);
2971     if (!hi)
2972         return 0;
2973     id = strtoul(argv[2], NULL, 10);
2974     WALK_NOTES(hi, prev, note) {
2975         if (id == note->id) {
2976             if (prev)
2977                 prev->next = note->next;
2978             else
2979                 hi->notes = note->next;
2980             free(note);
2981             reply("NSMSG_NOTE_REMOVED", id, hi->handle);
2982             return 1;
2983         }
2984     }
2985     reply("NSMSG_NO_SUCH_NOTE", hi->handle, id);
2986     return 0;
2987 }
2988
2989 static int
2990 nickserv_saxdb_write(struct saxdb_context *ctx) {
2991     dict_iterator_t it;
2992     struct handle_info *hi;
2993     char flags[33];
2994
2995     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2996         hi = iter_data(it);
2997         assert(hi->id != 0);
2998         saxdb_start_record(ctx, iter_key(it), 0);
2999         if (hi->cookie) {
3000             struct handle_cookie *cookie = hi->cookie;
3001             char *type;
3002
3003             switch (cookie->type) {
3004             case ACTIVATION: type = KEY_ACTIVATION; break;
3005             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
3006             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
3007             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
3008             default: type = NULL; break;
3009             }
3010             if (type) {
3011                 saxdb_start_record(ctx, KEY_COOKIE, 0);
3012                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
3013                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
3014                 if (cookie->data)
3015                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
3016                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
3017                 saxdb_end_record(ctx);
3018             }
3019         }
3020         if (hi->notes) {
3021             struct handle_note *prev, *note;
3022             saxdb_start_record(ctx, KEY_NOTES, 0);
3023             WALK_NOTES(hi, prev, note) {
3024                 snprintf(flags, sizeof(flags), "%d", note->id);
3025                 saxdb_start_record(ctx, flags, 0);
3026                 if (note->expires)
3027                     saxdb_write_int(ctx, KEY_NOTE_EXPIRES, note->expires);
3028                 saxdb_write_int(ctx, KEY_NOTE_SET, note->set);
3029                 saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
3030                 saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
3031                 saxdb_end_record(ctx);
3032             }
3033             saxdb_end_record(ctx);
3034         }
3035         if (hi->email_addr)
3036             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
3037         if (hi->epithet)
3038             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
3039         if (hi->fakehost)
3040             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
3041         if (hi->fakeident)
3042             saxdb_write_string(ctx, KEY_FAKEIDENT, hi->fakeident);
3043         if (hi->flags) {
3044             int ii, flen;
3045
3046             for (ii=flen=0; handle_flags[ii]; ++ii)
3047                 if (hi->flags & (1 << ii))
3048                     flags[flen++] = handle_flags[ii];
3049             flags[flen] = 0;
3050             saxdb_write_string(ctx, KEY_FLAGS, flags);
3051         }
3052         saxdb_write_int(ctx, KEY_ID, hi->id);
3053         if (hi->infoline)
3054             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
3055         if (hi->last_quit_host[0])
3056             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
3057         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
3058         if (hi->karma != 0)
3059             saxdb_write_sint(ctx, KEY_KARMA, hi->karma);
3060         if (hi->masks->used)
3061             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
3062         if (hi->maxlogins)
3063             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
3064         if (hi->nicks) {
3065             struct string_list *slist;
3066             struct nick_info *ni;
3067
3068             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
3069             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
3070             saxdb_write_string_list(ctx, KEY_NICKS, slist);
3071             free(slist->list);
3072             free(slist);
3073         }
3074         if (hi->opserv_level)
3075             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
3076         if (hi->language != lang_C)
3077             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
3078         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
3079         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
3080         if (hi->screen_width)
3081             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
3082         if (hi->table_width)
3083             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
3084         flags[0] = hi->userlist_style;
3085         flags[1] = 0;
3086         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
3087         saxdb_end_record(ctx);
3088     }
3089     return 0;
3090 }
3091
3092 static handle_merge_func_t *handle_merge_func_list;
3093 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
3094
3095 void
3096 reg_handle_merge_func(handle_merge_func_t func)
3097 {
3098     if (handle_merge_func_used == handle_merge_func_size) {
3099         if (handle_merge_func_size) {
3100             handle_merge_func_size <<= 1;
3101             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
3102         } else {
3103             handle_merge_func_size = 8;
3104             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
3105         }
3106     }
3107     handle_merge_func_list[handle_merge_func_used++] = func;
3108 }
3109
3110 static NICKSERV_FUNC(cmd_merge)
3111 {
3112     struct handle_info *hi_from, *hi_to;
3113     struct userNode *last_user;
3114     struct userData *cList, *cListNext;
3115     unsigned int ii, jj, n;
3116     char buffer[MAXLEN];
3117
3118     NICKSERV_MIN_PARMS(3);
3119
3120     if (!(hi_from = get_victim_oper(user, argv[1])))
3121         return 0;
3122     if (!(hi_to = get_victim_oper(user, argv[2])))
3123         return 0;
3124     if (hi_to == hi_from) {
3125         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
3126         return 0;
3127     }
3128
3129     for (n=0; n<handle_merge_func_used; n++)
3130         handle_merge_func_list[n](user, hi_to, hi_from);
3131
3132     /* Append "from" handle's nicks to "to" handle's nick list. */
3133     if (hi_to->nicks) {
3134         struct nick_info *last_ni;
3135         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
3136         last_ni->next = hi_from->nicks;
3137     }
3138     while (hi_from->nicks) {
3139         hi_from->nicks->owner = hi_to;
3140         hi_from->nicks = hi_from->nicks->next;
3141     }
3142
3143     /* Merge the hostmasks. */
3144     for (ii=0; ii<hi_from->masks->used; ii++) {
3145         char *mask = hi_from->masks->list[ii];
3146         for (jj=0; jj<hi_to->masks->used; jj++)
3147             if (match_ircglobs(hi_to->masks->list[jj], mask))
3148                 break;
3149         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
3150             string_list_append(hi_to->masks, strdup(mask));
3151     }
3152
3153     /* Merge the lists of authed users. */
3154     if (hi_to->users) {
3155         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
3156         last_user->next_authed = hi_from->users;
3157     } else {
3158         hi_to->users = hi_from->users;
3159     }
3160     /* Repoint the old "from" handle's users. */
3161     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
3162         last_user->handle_info = hi_to;
3163     }
3164     hi_from->users = NULL;
3165
3166     /* Merge channel userlists. */
3167     for (cList=hi_from->channels; cList; cList=cListNext) {
3168         struct userData *cList2;
3169         cListNext = cList->u_next;
3170         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
3171             if (cList->channel == cList2->channel)
3172                 break;
3173         if (cList2 && (cList2->access >= cList->access)) {
3174             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);
3175             /* keep cList2 in hi_to; remove cList from hi_from */
3176             del_channel_user(cList, 1);
3177         } else {
3178             if (cList2) {
3179                 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);
3180                 /* remove the lower-ranking cList2 from hi_to */
3181                 del_channel_user(cList2, 1);
3182             } else {
3183                 log_module(NS_LOG, LOG_INFO, "Merge: %s had no access in %s", hi_to->handle, cList->channel->channel->name);
3184             }
3185             /* cList needs to be moved from hi_from to hi_to */
3186             cList->handle = hi_to;
3187             /* Remove from linked list for hi_from */
3188             assert(!cList->u_prev);
3189             hi_from->channels = cList->u_next;
3190             if (cList->u_next)
3191                 cList->u_next->u_prev = cList->u_prev;
3192             /* Add to linked list for hi_to */
3193             cList->u_prev = NULL;
3194             cList->u_next = hi_to->channels;
3195             if (hi_to->channels)
3196                 hi_to->channels->u_prev = cList;
3197             hi_to->channels = cList;
3198         }
3199     }
3200
3201     /* Do they get an OpServ level promotion? */
3202     if (hi_from->opserv_level > hi_to->opserv_level)
3203         hi_to->opserv_level = hi_from->opserv_level;
3204
3205     /* What about last seen time? */
3206     if (hi_from->lastseen > hi_to->lastseen)
3207         hi_to->lastseen = hi_from->lastseen;
3208
3209     /* New karma is the sum of the two original karmas. */
3210     hi_to->karma += hi_from->karma;
3211
3212     /* Does a fakehost carry over?  (This intentionally doesn't set it
3213      * for users previously attached to hi_to.  They'll just have to
3214      * reconnect.)
3215      */
3216     if (hi_from->fakehost && !hi_to->fakehost)
3217         hi_to->fakehost = strdup(hi_from->fakehost);
3218     if (hi_from->fakeident && !hi_to->fakeident)
3219         hi_to->fakeident = strdup(hi_from->fakeident);
3220
3221     /* Notify of success. */
3222     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
3223     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
3224     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
3225
3226     /* Unregister the "from" handle. */
3227     nickserv_unregister_handle(hi_from, NULL);
3228
3229     return 1;
3230 }
3231
3232 struct nickserv_discrim {
3233     unsigned long flags_on, flags_off;
3234     unsigned long min_registered, max_registered;
3235     unsigned long lastseen;
3236     unsigned int limit;
3237     int min_level, max_level;
3238     int min_karma, max_karma;
3239     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
3240     const char *nickmask;
3241     const char *hostmask;
3242     const char *fakehostmask;
3243     const char *fakeidentmask;
3244     const char *handlemask;
3245     const char *emailmask;
3246 };
3247
3248 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
3249
3250 struct discrim_apply_info {
3251     struct nickserv_discrim *discrim;
3252     discrim_search_func func;
3253     struct userNode *source;
3254     unsigned int matched;
3255 };
3256
3257 static struct nickserv_discrim *
3258 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
3259 {
3260     unsigned int i;
3261     struct nickserv_discrim *discrim;
3262
3263     discrim = malloc(sizeof(*discrim));
3264     memset(discrim, 0, sizeof(*discrim));
3265     discrim->min_level = 0;
3266     discrim->max_level = INT_MAX;
3267     discrim->limit = 50;
3268     discrim->min_registered = 0;
3269     discrim->max_registered = ULONG_MAX;
3270     discrim->lastseen = ULONG_MAX;
3271     discrim->min_karma = INT_MIN;
3272     discrim->max_karma = INT_MAX;
3273
3274     for (i=0; i<argc; i++) {
3275         if (i == argc - 1) {
3276             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3277             goto fail;
3278         }
3279         if (!irccasecmp(argv[i], "limit")) {
3280             discrim->limit = strtoul(argv[++i], NULL, 0);
3281         } else if (!irccasecmp(argv[i], "flags")) {
3282             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
3283         } else if (!irccasecmp(argv[i], "registered")) {
3284             const char *cmp = argv[++i];
3285             if (cmp[0] == '<') {
3286                 if (cmp[1] == '=') {
3287                     discrim->min_registered = now - ParseInterval(cmp+2);
3288                 } else {
3289                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
3290                 }
3291             } else if (cmp[0] == '=') {
3292                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
3293             } else if (cmp[0] == '>') {
3294                 if (cmp[1] == '=') {
3295                     discrim->max_registered = now - ParseInterval(cmp+2);
3296                 } else {
3297                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
3298                 }
3299             } else {
3300                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3301             }
3302         } else if (!irccasecmp(argv[i], "seen")) {
3303             discrim->lastseen = now - ParseInterval(argv[++i]);
3304         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
3305             discrim->nickmask = argv[++i];
3306         } else if (!irccasecmp(argv[i], "hostmask")) {
3307             i++;
3308             if (!irccasecmp(argv[i], "exact")) {
3309                 if (i == argc - 1) {
3310                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3311                     goto fail;
3312                 }
3313                 discrim->hostmask_type = EXACT;
3314             } else if (!irccasecmp(argv[i], "subset")) {
3315                 if (i == argc - 1) {
3316                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3317                     goto fail;
3318                 }
3319                 discrim->hostmask_type = SUBSET;
3320             } else if (!irccasecmp(argv[i], "superset")) {
3321                 if (i == argc - 1) {
3322                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3323                     goto fail;
3324                 }
3325                 discrim->hostmask_type = SUPERSET;
3326             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
3327                 if (i == argc - 1) {
3328                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3329                     goto fail;
3330                 }
3331                 discrim->hostmask_type = LASTQUIT;
3332             } else {
3333                 i--;
3334                 discrim->hostmask_type = SUPERSET;
3335             }
3336             discrim->hostmask = argv[++i];
3337         } else if (!irccasecmp(argv[i], "fakehost")) {
3338             if (!irccasecmp(argv[++i], "*")) {
3339                 discrim->fakehostmask = 0;
3340             } else {
3341                 discrim->fakehostmask = argv[i];
3342             }
3343         } else if (!irccasecmp(argv[i], "fakeident")) {
3344             if (!irccasecmp(argv[++i], "*")) {
3345                 discrim->fakeidentmask = 0;
3346             } else {
3347                 discrim->fakeidentmask = argv[i];
3348             }
3349         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
3350             if (!irccasecmp(argv[++i], "*")) {
3351                 discrim->handlemask = 0;
3352             } else {
3353                 discrim->handlemask = argv[i];
3354             }
3355         } else if (!irccasecmp(argv[i], "email")) {
3356             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
3357                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
3358                 goto fail;
3359             } else if (!irccasecmp(argv[++i], "*")) {
3360                 discrim->emailmask = 0;
3361             } else {
3362                 discrim->emailmask = argv[i];
3363             }
3364         } else if (!irccasecmp(argv[i], "access")) {
3365             const char *cmp = argv[++i];
3366             if (cmp[0] == '<') {
3367                 if (discrim->min_level == 0) discrim->min_level = 1;
3368                 if (cmp[1] == '=') {
3369                     discrim->max_level = strtoul(cmp+2, NULL, 0);
3370                 } else {
3371                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
3372                 }
3373             } else if (cmp[0] == '=') {
3374                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
3375             } else if (cmp[0] == '>') {
3376                 if (cmp[1] == '=') {
3377                     discrim->min_level = strtoul(cmp+2, NULL, 0);
3378                 } else {
3379                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
3380                 }
3381             } else {
3382                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3383             }
3384         } else if (!irccasecmp(argv[i], "karma")) {
3385             const char *cmp = argv[++i];
3386             if (cmp[0] == '<') {
3387                 if (cmp[1] == '=') {
3388                     discrim->max_karma = strtoul(cmp+2, NULL, 0);
3389                 } else {
3390                     discrim->max_karma = strtoul(cmp+1, NULL, 0) - 1;
3391                 }
3392             } else if (cmp[0] == '=') {
3393                 discrim->min_karma = discrim->max_karma = strtoul(cmp+1, NULL, 0);
3394             } else if (cmp[0] == '>') {
3395                 if (cmp[1] == '=') {
3396                     discrim->min_karma = strtoul(cmp+2, NULL, 0);
3397                 } else {
3398                     discrim->min_karma = strtoul(cmp+1, NULL, 0) + 1;
3399                 }
3400             } else {
3401                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3402             }
3403         } else {
3404             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
3405             goto fail;
3406         }
3407     }
3408     return discrim;
3409   fail:
3410     free(discrim);
3411     return NULL;
3412 }
3413
3414 static int
3415 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
3416 {
3417     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
3418         || (discrim->flags_off & hi->flags)
3419         || (discrim->min_registered > hi->registered)
3420         || (discrim->max_registered < hi->registered)
3421         || (discrim->lastseen < (hi->users?now:hi->lastseen))
3422         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
3423         || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
3424         || (discrim->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask)))
3425         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
3426         || (discrim->min_level > hi->opserv_level)
3427         || (discrim->max_level < hi->opserv_level)
3428         || (discrim->min_karma > hi->karma)
3429         || (discrim->max_karma < hi->karma)
3430         ) {
3431         return 0;
3432     }
3433     if (discrim->hostmask) {
3434         unsigned int i;
3435         for (i=0; i<hi->masks->used; i++) {
3436             const char *mask = hi->masks->list[i];
3437             if ((discrim->hostmask_type == SUBSET)
3438                 && (match_ircglobs(discrim->hostmask, mask))) break;
3439             else if ((discrim->hostmask_type == EXACT)
3440                      && !irccasecmp(discrim->hostmask, mask)) break;
3441             else if ((discrim->hostmask_type == SUPERSET)
3442                      && (match_ircglobs(mask, discrim->hostmask))) break;
3443             else if ((discrim->hostmask_type == LASTQUIT)
3444                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
3445         }
3446         if (i==hi->masks->used) return 0;
3447     }
3448     if (discrim->nickmask) {
3449         struct nick_info *nick = hi->nicks;
3450         while (nick) {
3451             if (match_ircglob(nick->nick, discrim->nickmask)) break;
3452             nick = nick->next;
3453         }
3454         if (!nick) return 0;
3455     }
3456     return 1;
3457 }
3458
3459 static unsigned int
3460 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
3461 {
3462     dict_iterator_t it, next;
3463     unsigned int matched;
3464
3465     for (it = dict_first(nickserv_handle_dict), matched = 0;
3466          it && (matched < discrim->limit);
3467          it = next) {
3468         next = iter_next(it);
3469         if (nickserv_discrim_match(discrim, iter_data(it))) {
3470             dsf(source, iter_data(it));
3471             matched++;
3472         }
3473     }
3474     return matched;
3475 }
3476
3477 static void
3478 search_print_func(struct userNode *source, struct handle_info *match)
3479 {
3480     send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
3481 }
3482
3483 static void
3484 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
3485 {
3486 }
3487
3488 static void
3489 search_unregister_func (struct userNode *source, struct handle_info *match)
3490 {
3491     if (oper_has_access(source, nickserv, match->opserv_level, 0))
3492         nickserv_unregister_handle(match, source);
3493 }
3494
3495 static int
3496 nickserv_sort_accounts_by_access(const void *a, const void *b)
3497 {
3498     const struct handle_info *hi_a = *(const struct handle_info**)a;
3499     const struct handle_info *hi_b = *(const struct handle_info**)b;
3500     if (hi_a->opserv_level != hi_b->opserv_level)
3501         return hi_b->opserv_level - hi_a->opserv_level;
3502     return irccasecmp(hi_a->handle, hi_b->handle);
3503 }
3504
3505 void
3506 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
3507 {
3508     struct handle_info_list hil;
3509     struct helpfile_table tbl;
3510     unsigned int ii;
3511     dict_iterator_t it;
3512     const char **ary;
3513
3514     memset(&hil, 0, sizeof(hil));
3515     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3516         struct handle_info *hi = iter_data(it);
3517         if (hi->opserv_level)
3518             handle_info_list_append(&hil, hi);
3519     }
3520     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
3521     tbl.length = hil.used + 1;
3522     tbl.width = 2;
3523     tbl.flags = TABLE_NO_FREE;
3524     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
3525     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
3526     ary[0] = "Account";
3527     ary[1] = "Level";
3528     for (ii = 0; ii < hil.used; ) {
3529         ary = malloc(tbl.width * sizeof(ary[0]));
3530         ary[0] = hil.list[ii]->handle;
3531         ary[1] = strtab(hil.list[ii]->opserv_level);
3532         tbl.contents[++ii] = ary;
3533     }
3534     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3535     reply("MSG_MATCH_COUNT", hil.used);
3536     for (ii = 0; ii < hil.used; ii++)
3537         free(tbl.contents[ii]);
3538     free(tbl.contents);
3539     free(hil.list);
3540 }
3541
3542 static NICKSERV_FUNC(cmd_search)
3543 {
3544     struct nickserv_discrim *discrim;
3545     discrim_search_func action;
3546     struct svccmd *subcmd;
3547     unsigned int matches;
3548     char buf[MAXLEN];
3549
3550     NICKSERV_MIN_PARMS(3);
3551     sprintf(buf, "search %s", argv[1]);
3552     subcmd = dict_find(nickserv_service->commands, buf, NULL);
3553     if (!irccasecmp(argv[1], "print"))
3554         action = search_print_func;
3555     else if (!irccasecmp(argv[1], "count"))
3556         action = search_count_func;
3557     else if (!irccasecmp(argv[1], "unregister"))
3558         action = search_unregister_func;
3559     else {
3560         reply("NSMSG_INVALID_ACTION", argv[1]);
3561         return 0;
3562     }
3563
3564     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
3565         return 0;
3566
3567     discrim = nickserv_discrim_create(user, argc-2, argv+2);
3568     if (!discrim)
3569         return 0;
3570
3571     if (action == search_print_func)
3572         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
3573     else if (action == search_count_func)
3574         discrim->limit = INT_MAX;
3575
3576     matches = nickserv_discrim_search(discrim, action, user);
3577
3578     if (matches)
3579         reply("MSG_MATCH_COUNT", matches);
3580     else
3581         reply("MSG_NO_MATCHES");
3582
3583     free(discrim);
3584     return 0;
3585 }
3586
3587 static MODCMD_FUNC(cmd_checkpass)
3588 {
3589     struct handle_info *hi;
3590
3591     NICKSERV_MIN_PARMS(3);
3592     if (!(hi = get_handle_info(argv[1]))) {
3593         reply("MSG_HANDLE_UNKNOWN", argv[1]);
3594         return 0;
3595     }
3596     if (checkpass(argv[2], hi->passwd))
3597         reply("CHECKPASS_YES");
3598     else
3599         reply("CHECKPASS_NO");
3600     argv[2] = "****";
3601     return 1;
3602 }
3603
3604 static MODCMD_FUNC(cmd_checkemail)
3605 {
3606     struct handle_info *hi;
3607
3608     NICKSERV_MIN_PARMS(3);
3609     if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
3610         return 0;
3611     }
3612     if (!hi->email_addr)
3613         reply("CHECKEMAIL_NOT_SET");
3614     else if (!irccasecmp(argv[2], hi->email_addr))
3615         reply("CHECKEMAIL_YES");
3616     else
3617         reply("CHECKEMAIL_NO");
3618     return 1;
3619 }
3620
3621
3622 static void
3623 nickserv_db_read_handle(const char *handle, dict_t obj)
3624 {
3625     const char *str;
3626     struct string_list *masks, *slist;
3627     struct handle_info *hi;
3628     struct userNode *authed_users;
3629     struct userData *channel_list;
3630     unsigned long id;
3631     unsigned int ii;
3632     dict_t subdb;
3633
3634     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
3635     id = str ? strtoul(str, NULL, 0) : 0;
3636     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
3637     if (!str) {
3638         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
3639         return;
3640     }
3641     if ((hi = get_handle_info(handle))) {
3642         authed_users = hi->users;
3643         channel_list = hi->channels;
3644         hi->users = NULL;
3645         hi->channels = NULL;
3646         dict_remove(nickserv_handle_dict, hi->handle);
3647     } else {
3648         authed_users = NULL;
3649         channel_list = NULL;
3650     }
3651     hi = register_handle(handle, str, id);
3652     if (authed_users) {
3653         hi->users = authed_users;
3654         while (authed_users) {
3655             authed_users->handle_info = hi;
3656             authed_users = authed_users->next_authed;
3657         }
3658     }
3659     hi->channels = channel_list;
3660     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
3661     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
3662     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
3663     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
3664     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
3665     hi->language = language_find(str ? str : "C");
3666     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
3667     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
3668     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
3669     if (str)
3670         hi->infoline = strdup(str);
3671     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
3672     hi->registered = str ? strtoul(str, NULL, 0) : now;
3673     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
3674     hi->lastseen = str ? strtoul(str, NULL, 0) : hi->registered;
3675     str = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
3676     hi->karma = str ? strtoul(str, NULL, 0) : 0;
3677     /* We want to read the nicks even if disable_nicks is set.  This is so
3678      * that we don't lose the nick data entirely. */
3679     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
3680     if (slist) {
3681         for (ii=0; ii<slist->used; ii++)
3682             register_nick(slist->list[ii], hi);
3683     }
3684     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
3685     if (str) {
3686         for (ii=0; str[ii]; ii++)
3687             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
3688     }
3689     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
3690     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
3691     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
3692     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
3693     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
3694     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
3695     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
3696     if (!str)
3697         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
3698     if (str)
3699         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
3700     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
3701     if (str)
3702         nickserv_set_email_addr(hi, str);
3703     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
3704     if (str)
3705         hi->epithet = strdup(str);
3706     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
3707     if (str)
3708         hi->fakehost = strdup(str);
3709     str = database_get_data(obj, KEY_FAKEIDENT, RECDB_QSTRING);
3710     if (str)
3711         hi->fakeident = strdup(str);
3712     /* Read the "cookie" sub-database (if it exists). */
3713     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
3714     if (subdb) {
3715         const char *data, *type, *expires, *cookie_str;
3716         struct handle_cookie *cookie;
3717
3718         cookie = calloc(1, sizeof(*cookie));
3719         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
3720         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
3721         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
3722         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
3723         if (!type || !expires || !cookie_str) {
3724             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
3725             goto cookie_out;
3726         }
3727         if (!irccasecmp(type, KEY_ACTIVATION))
3728             cookie->type = ACTIVATION;
3729         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
3730             cookie->type = PASSWORD_CHANGE;
3731         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
3732             cookie->type = EMAIL_CHANGE;
3733         else if (!irccasecmp(type, KEY_ALLOWAUTH))
3734             cookie->type = ALLOWAUTH;
3735         else {
3736             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
3737             goto cookie_out;
3738         }
3739         cookie->expires = strtoul(expires, NULL, 0);
3740         if (cookie->expires < now)
3741             goto cookie_out;
3742         if (data)
3743             cookie->data = strdup(data);
3744         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
3745         cookie->hi = hi;
3746       cookie_out:
3747         if (cookie->hi)
3748             nickserv_bake_cookie(cookie);
3749         else
3750             nickserv_free_cookie(cookie);
3751     }
3752     /* Read the "notes" sub-database (if it exists). */
3753     subdb = database_get_data(obj, KEY_NOTES, RECDB_OBJECT);
3754     if (subdb) {
3755         dict_iterator_t it;
3756         struct handle_note *last_note;
3757         struct handle_note *note;
3758
3759         last_note = NULL;
3760         for (it = dict_first(subdb); it; it = iter_next(it)) {
3761             const char *expires;
3762             const char *setter;
3763             const char *text;
3764             const char *set;
3765             const char *note_id;
3766             dict_t notedb;
3767
3768             note_id = iter_key(it);
3769             notedb = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
3770             if (!notedb) {
3771                 log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", note_id, hi->handle);
3772                 continue;
3773             }
3774             expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
3775             setter = database_get_data(notedb, KEY_NOTE_SETTER, RECDB_QSTRING);
3776             text = database_get_data(notedb, KEY_NOTE_NOTE, RECDB_QSTRING);
3777             set = database_get_data(notedb, KEY_NOTE_SET, RECDB_QSTRING);
3778             if (!setter || !text || !set) {
3779                 log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", note_id, hi->handle);
3780                 continue;
3781             }
3782             note = calloc(1, sizeof(*note) + strlen(text));
3783             note->next = NULL;
3784             note->expires = expires ? strtoul(expires, NULL, 10) : 0;
3785             note->set = strtoul(set, NULL, 10);
3786             note->id = strtoul(note_id, NULL, 10);
3787             safestrncpy(note->setter, setter, sizeof(note->setter));
3788             strcpy(note->note, text);
3789             if (last_note)
3790                 last_note->next = note;
3791             else
3792                 hi->notes = note;
3793             last_note = note;
3794         }
3795     }
3796 }
3797
3798 static int
3799 nickserv_saxdb_read(dict_t db) {
3800     dict_iterator_t it;
3801     struct record_data *rd;
3802
3803     for (it=dict_first(db); it; it=iter_next(it)) {
3804         rd = iter_data(it);
3805         nickserv_db_read_handle(iter_key(it), rd->d.object);
3806     }
3807     return 0;
3808 }
3809
3810 static NICKSERV_FUNC(cmd_mergedb)
3811 {
3812     struct timeval start, stop;
3813     dict_t db;
3814
3815     NICKSERV_MIN_PARMS(2);
3816     gettimeofday(&start, NULL);
3817     if (!(db = parse_database(argv[1]))) {
3818         reply("NSMSG_DB_UNREADABLE", argv[1]);
3819         return 0;
3820     }
3821     nickserv_saxdb_read(db);
3822     free_database(db);
3823     gettimeofday(&stop, NULL);
3824     stop.tv_sec -= start.tv_sec;
3825     stop.tv_usec -= start.tv_usec;
3826     if (stop.tv_usec < 0) {
3827         stop.tv_sec -= 1;
3828         stop.tv_usec += 1000000;
3829     }
3830     reply("NSMSG_DB_MERGED", argv[1], (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
3831     return 1;
3832 }
3833
3834 static void
3835 expire_handles(UNUSED_ARG(void *data))
3836 {
3837     dict_iterator_t it, next;
3838     unsigned long expiry;
3839     struct handle_info *hi;
3840
3841     for (it=dict_first(nickserv_handle_dict); it; it=next) {
3842         next = iter_next(it);
3843         hi = iter_data(it);
3844         if ((hi->opserv_level > 0)
3845             || hi->users
3846             || HANDLE_FLAGGED(hi, FROZEN)
3847             || HANDLE_FLAGGED(hi, NODELETE)) {
3848             continue;
3849         }
3850         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
3851         if ((now - hi->lastseen) > expiry) {
3852             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
3853             nickserv_unregister_handle(hi, NULL);
3854         }
3855     }
3856
3857     if (nickserv_conf.handle_expire_frequency)
3858         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3859 }
3860
3861 static void
3862 nickserv_load_dict(const char *fname)
3863 {
3864     FILE *file;
3865     char line[128];
3866     if (!(file = fopen(fname, "r"))) {
3867         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
3868         return;
3869     }
3870     while (fgets(line, sizeof(line), file)) {
3871         if (!line[0])
3872             continue;
3873         if (line[strlen(line)-1] == '\n')
3874             line[strlen(line)-1] = 0;
3875         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
3876     }
3877     fclose(file);
3878     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
3879 }
3880
3881 static enum reclaim_action
3882 reclaim_action_from_string(const char *str) {
3883     if (!str)
3884         return RECLAIM_NONE;
3885     else if (!irccasecmp(str, "warn"))
3886         return RECLAIM_WARN;
3887     else if (!irccasecmp(str, "svsnick"))
3888         return RECLAIM_SVSNICK;
3889     else if (!irccasecmp(str, "kill"))
3890         return RECLAIM_KILL;
3891     else
3892         return RECLAIM_NONE;
3893 }
3894
3895 static void
3896 nickserv_conf_read(void)
3897 {
3898     dict_t conf_node, child;
3899     const char *str;
3900     dict_iterator_t it;
3901
3902     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
3903         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
3904         return;
3905     }
3906     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
3907     if (!str)
3908         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
3909     if (nickserv_conf.valid_handle_regex_set)
3910         regfree(&nickserv_conf.valid_handle_regex);
3911     if (str) {
3912         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3913         nickserv_conf.valid_handle_regex_set = !err;
3914         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
3915     } else {
3916         nickserv_conf.valid_handle_regex_set = 0;
3917     }
3918     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
3919     if (nickserv_conf.valid_nick_regex_set)
3920         regfree(&nickserv_conf.valid_nick_regex);
3921     if (str) {
3922         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3923         nickserv_conf.valid_nick_regex_set = !err;
3924         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
3925     } else {
3926         nickserv_conf.valid_nick_regex_set = 0;
3927     }
3928     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
3929     if (!str)
3930         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
3931     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
3932     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
3933     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
3934     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
3935     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
3936     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
3937     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
3938     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
3939     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
3940     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
3941     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
3942     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
3943     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
3944     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
3945     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3946     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
3947     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
3948     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
3949     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
3950     str = database_get_data(conf_node, KEY_SET_TITLE_LEVEL, RECDB_QSTRING);
3951     nickserv_conf.set_title_level = str ? strtoul(str, NULL, 0) : 900;
3952     str = database_get_data(conf_node, KEY_SET_FAKEHOST_LEVEL, RECDB_QSTRING);
3953     nickserv_conf.set_fakehost_level = str ? strtoul(str, NULL, 0) : 1000;
3954     str = database_get_data(conf_node, KEY_SET_FAKEIDENT_LEVEL, RECDB_QSTRING);
3955     nickserv_conf.set_fakeident_level = str ? strtoul(str, NULL, 0) : 1000;
3956     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
3957     if (!str)
3958         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
3959     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
3960     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3961     if (!str)
3962         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3963     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
3964     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3965     if (!str)
3966         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3967     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
3968     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
3969     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
3970     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
3971     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
3972     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
3973     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
3974     str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
3975     nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
3976     str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
3977     if (!str)
3978         str = "ShgsfnHbu";
3979     nickserv_conf.ounregister_flags = 0;
3980     while(*str) {
3981         unsigned int pos = handle_inverse_flags[(unsigned char)*str];
3982         str++;
3983         if(pos)
3984             nickserv_conf.ounregister_flags |= 1 << (pos - 1);
3985     }
3986     str = database_get_data(conf_node, KEY_HANDLE_TS_MODE, RECDB_QSTRING);
3987     if (!str)
3988         nickserv_conf.handle_ts_mode = TS_IGNORE;
3989     else if (!irccasecmp(str, "ircu"))
3990         nickserv_conf.handle_ts_mode = TS_IRCU;
3991     else
3992         nickserv_conf.handle_ts_mode = TS_IGNORE;
3993     if (!nickserv_conf.disable_nicks) {
3994         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
3995         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3996         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
3997         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
3998         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
3999         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
4000         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
4001         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
4002     }
4003     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
4004     for (it=dict_first(child); it; it=iter_next(it)) {
4005         const char *key = iter_key(it), *value;
4006         unsigned char flag;
4007         int pos;
4008
4009         if (!strncasecmp(key, "uc_", 3))
4010             flag = toupper(key[3]);
4011         else if (!strncasecmp(key, "lc_", 3))
4012             flag = tolower(key[3]);
4013         else
4014             flag = key[0];
4015
4016         if ((pos = handle_inverse_flags[flag])) {
4017             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
4018             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
4019         }
4020     }
4021     if (nickserv_conf.weak_password_dict)
4022         dict_delete(nickserv_conf.weak_password_dict);
4023     nickserv_conf.weak_password_dict = dict_new();
4024     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
4025     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
4026     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
4027     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
4028     if (str)
4029         nickserv_load_dict(str);
4030     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
4031     if (nickserv && str)
4032         NickChange(nickserv, str, 0);
4033     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
4034     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
4035     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
4036     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
4037     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
4038     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
4039     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
4040     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
4041     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
4042     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
4043     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
4044     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
4045     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
4046     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
4047     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
4048     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
4049     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
4050     nickserv_conf.titlehost_suffix = str ? str : "example.net";
4051     str = conf_get_data("server/network", RECDB_QSTRING);
4052     nickserv_conf.network_name = str ? str : "some IRC network";
4053     if (!nickserv_conf.auth_policer_params) {
4054         nickserv_conf.auth_policer_params = policer_params_new();
4055         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
4056         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
4057     }
4058     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
4059     for (it=dict_first(child); it; it=iter_next(it))
4060         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
4061 }
4062
4063 static void
4064 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
4065     const char *msg;
4066     char newnick[NICKLEN+1];
4067
4068     assert(user);
4069     assert(ni);
4070     switch (action) {
4071     case RECLAIM_NONE:
4072         /* do nothing */
4073         break;
4074     case RECLAIM_WARN:
4075         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
4076         break;
4077     case RECLAIM_SVSNICK:
4078         do {
4079             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
4080         } while (GetUserH(newnick));
4081         irc_svsnick(nickserv, user, newnick);
4082         break;
4083     case RECLAIM_KILL:
4084         msg = user_find_message(user, "NSMSG_RECLAIM_KILL");
4085         DelUser(user, nickserv, 1, msg);
4086         break;
4087     }
4088 }
4089
4090 static void
4091 nickserv_reclaim_p(void *data) {
4092     struct userNode *user = data;
4093     struct nick_info *ni = get_nick_info(user->nick);
4094     if (ni)
4095         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
4096 }
4097
4098 static void
4099 check_user_nick(struct userNode *user) {
4100     struct nick_info *ni;
4101     user->modes &= ~FLAGS_REGNICK;
4102     if (!(ni = get_nick_info(user->nick)))
4103         return;
4104     if (user->handle_info == ni->owner) {
4105         user->modes |= FLAGS_REGNICK;
4106         irc_regnick(user);
4107         return;
4108     }
4109     if (nickserv_conf.warn_nick_owned)
4110         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
4111     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
4112         return;
4113     if (nickserv_conf.auto_reclaim_delay)
4114         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
4115     else
4116         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
4117 }
4118
4119 void
4120 handle_account(struct userNode *user, const char *stamp, unsigned long timestamp, unsigned long serial)
4121 {
4122     struct handle_info *hi = NULL;
4123
4124     if (stamp != NULL)
4125         hi = dict_find(nickserv_handle_dict, stamp, NULL);
4126     if ((hi == NULL) && (serial != 0)) {
4127         char id[IDLEN + 1];
4128         inttobase64(id, serial, IDLEN);
4129         hi = dict_find(nickserv_id_dict, id, NULL);
4130     }
4131
4132     if (hi) {
4133         if ((nickserv_conf.handle_ts_mode == TS_IRCU)
4134             && (timestamp != hi->registered)) {
4135             return;
4136         }
4137         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
4138             return;
4139         }
4140         set_user_handle_info(user, hi, 0);
4141     } else {
4142         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s:%lu:%lu.", user->nick, stamp, timestamp, serial);
4143     }
4144 }
4145
4146 void
4147 handle_nick_change(struct userNode *user, const char *old_nick)
4148 {
4149     struct handle_info *hi;
4150
4151     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
4152         dict_remove(nickserv_allow_auth_dict, old_nick);
4153         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
4154     }
4155     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4156     check_user_nick(user);
4157 }
4158
4159 void
4160 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
4161 {
4162     dict_remove(nickserv_allow_auth_dict, user->nick);
4163     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4164     set_user_handle_info(user, NULL, 0);
4165 }
4166
4167 static struct modcmd *
4168 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
4169 {
4170     if (min_level > 0) {
4171         char buf[16];
4172         sprintf(buf, "%u", min_level);
4173         if (must_be_qualified) {
4174             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
4175         } else {
4176             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
4177         }
4178     } else if (min_level == 0) {
4179         if (must_be_qualified) {
4180             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4181         } else {
4182             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4183         }
4184     } else {
4185         if (must_be_qualified) {
4186             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
4187         } else {
4188             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
4189         }
4190     }
4191 }
4192
4193 static void
4194 nickserv_db_cleanup(void)
4195 {
4196     unreg_del_user_func(nickserv_remove_user);
4197     userList_clean(&curr_helpers);
4198     policer_params_delete(nickserv_conf.auth_policer_params);
4199     dict_delete(nickserv_handle_dict);
4200     dict_delete(nickserv_nick_dict);
4201     dict_delete(nickserv_opt_dict);
4202     dict_delete(nickserv_allow_auth_dict);
4203     dict_delete(nickserv_email_dict);
4204     dict_delete(nickserv_id_dict);
4205     dict_delete(nickserv_conf.weak_password_dict);
4206     free(auth_func_list);
4207     free(unreg_func_list);
4208     free(rf_list);
4209     free(allowauth_func_list);
4210     free(handle_merge_func_list);
4211     free(failpw_func_list);
4212     if (nickserv_conf.valid_handle_regex_set)
4213         regfree(&nickserv_conf.valid_handle_regex);
4214     if (nickserv_conf.valid_nick_regex_set)
4215         regfree(&nickserv_conf.valid_nick_regex);
4216 }
4217
4218 void
4219 init_nickserv(const char *nick)
4220 {
4221     unsigned int i;
4222     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
4223     reg_new_user_func(check_user_nick);
4224     reg_nick_change_func(handle_nick_change);
4225     reg_del_user_func(nickserv_remove_user);
4226     reg_account_func(handle_account);
4227
4228     /* set up handle_inverse_flags */
4229     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
4230     for (i=0; handle_flags[i]; i++) {
4231         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
4232         flag_access_levels[i] = 0;
4233     }
4234
4235     conf_register_reload(nickserv_conf_read);
4236     nickserv_opt_dict = dict_new();
4237     nickserv_email_dict = dict_new();
4238     dict_set_free_keys(nickserv_email_dict, free);
4239     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
4240
4241     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
4242     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
4243     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
4244     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
4245     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
4246     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
4247     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
4248     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
4249     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
4250     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
4251     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
4252     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
4253     nickserv_define_func("SET", cmd_set, -1, 1, 0);
4254     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
4255     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
4256     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
4257     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
4258     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
4259     nickserv_define_func("MERGE", cmd_merge, 750, 1, 0);
4260     nickserv_define_func("ADDNOTE", cmd_addnote, 0, 1, 0);
4261     nickserv_define_func("DELNOTE", cmd_delnote, 0, 1, 0);
4262     nickserv_define_func("NOTES", cmd_notes, 0, 1, 0);
4263     if (!nickserv_conf.disable_nicks) {
4264         /* nick management commands */
4265         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
4266         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
4267         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
4268         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
4269         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
4270         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
4271     }
4272     if (nickserv_conf.email_enabled) {
4273         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
4274         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
4275         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
4276         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
4277         nickserv_define_func("ODELCOOKIE", cmd_odelcookie, 0, 1, 0);
4278         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
4279     }
4280     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
4281     /* miscellaneous commands */
4282     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
4283     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
4284     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
4285     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
4286     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
4287     nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0);
4288     /* other options */
4289     dict_insert(nickserv_opt_dict, "INFO", opt_info);
4290     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
4291     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
4292     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
4293     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
4294     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
4295     dict_insert(nickserv_opt_dict, "PASS", opt_password);
4296     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
4297     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
4298     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
4299     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
4300     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
4301     if (nickserv_conf.titlehost_suffix) {
4302         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
4303         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
4304         dict_insert(nickserv_opt_dict, "FAKEIDENT", opt_fakeident);
4305     }
4306     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
4307     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
4308     dict_insert(nickserv_opt_dict, "KARMA", opt_karma);
4309     nickserv_define_func("OSET KARMA", NULL, 0, 1, 0);
4310
4311     nickserv_handle_dict = dict_new();
4312     dict_set_free_keys(nickserv_handle_dict, free);
4313     dict_set_free_data(nickserv_handle_dict, free_handle_info);
4314
4315     nickserv_id_dict = dict_new();
4316     dict_set_free_keys(nickserv_id_dict, free);
4317
4318     nickserv_nick_dict = dict_new();
4319     dict_set_free_data(nickserv_nick_dict, free);
4320
4321     nickserv_allow_auth_dict = dict_new();
4322
4323     userList_init(&curr_helpers);
4324
4325     if (nick) {
4326         const char *modes = conf_get_data("services/nickserv/modes", RECDB_QSTRING);
4327         nickserv = AddLocalUser(nick, nick, NULL, "Nick Services", modes);
4328         nickserv_service = service_register(nickserv);
4329     }
4330     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
4331     reg_exit_func(nickserv_db_cleanup);
4332     if(nickserv_conf.handle_expire_frequency)
4333         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
4334     message_register_table(msgtab);
4335 }