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