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