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