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