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