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