Reject invalid durations in addnote.
[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 "FMT_TIME_T".%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))
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
1317     if (argc < 2) {
1318         if (!(hi = user->handle_info)) {
1319             reply("NSMSG_MUST_AUTH");
1320             return 0;
1321         }
1322     } else if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
1323         return 0;
1324     }
1325
1326     nsmsg_none = handle_find_message(hi, "MSG_NONE");
1327     reply("NSMSG_HANDLEINFO_ON", hi->handle);
1328 #ifdef WITH_PROTOCOL_BAHAMUT
1329     reply("NSMSG_HANDLEINFO_ID", hi->id);
1330 #endif
1331     reply("NSMSG_HANDLEINFO_REGGED", ctime(&hi->registered));
1332
1333     if (!hi->users) {
1334         intervalString(buff, now - hi->lastseen, user->handle_info);
1335         reply("NSMSG_HANDLEINFO_LASTSEEN", buff);
1336     } else {
1337         reply("NSMSG_HANDLEINFO_LASTSEEN_NOW");
1338     }
1339
1340     reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none));
1341     if (HANDLE_FLAGGED(hi, FROZEN))
1342         reply("NSMSG_HANDLEINFO_VACATION");
1343
1344     if (oper_has_access(user, cmd->parent->bot, 0, 1)) {
1345         struct do_not_register *dnr;
1346         if ((dnr = chanserv_is_dnr(NULL, hi)))
1347             reply("NSMSG_HANDLEINFO_DNR", dnr->setter, dnr->reason);
1348         if ((user->handle_info->opserv_level < 900) && !oper_outranks(user, hi))
1349             return 1;
1350     } else if (hi != user->handle_info)
1351         return 1;
1352
1353     if (IsOper(user))
1354         reply("NSMSG_HANDLEINFO_KARMA", hi->karma);
1355
1356     if (nickserv_conf.email_enabled)
1357         reply("NSMSG_HANDLEINFO_EMAIL_ADDR", visible_email_addr(user, hi));
1358
1359     if (hi->cookie) {
1360         const char *type;
1361         switch (hi->cookie->type) {
1362         case ACTIVATION: type = "NSMSG_HANDLEINFO_COOKIE_ACTIVATION"; break;
1363         case PASSWORD_CHANGE: type = "NSMSG_HANDLEINFO_COOKIE_PASSWORD"; break;
1364         case EMAIL_CHANGE: type = "NSMSG_HANDLEINFO_COOKIE_EMAIL"; break;
1365         case ALLOWAUTH: type = "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH"; break;
1366         default: type = "NSMSG_HANDLEINFO_COOKIE_UNKNOWN"; break;
1367         }
1368         reply(type);
1369     }
1370
1371     if (oper_has_access(user, cmd->parent->bot, 0, 1) || IsSupport(user)) {
1372         if (!hi->notes) {
1373             reply("NSMSG_HANDLEINFO_NO_NOTES");
1374         } else {
1375             struct handle_note *prev, *note;
1376
1377             WALK_NOTES(hi, prev, note) {
1378                 char set_time[INTERVALLEN];
1379                 intervalString(set_time, now - note->set, user->handle_info);
1380                 if (note->expires) {
1381                     char exp_time[INTERVALLEN];
1382                     intervalString(exp_time, note->expires - now, user->handle_info);
1383                     reply("NSMSG_HANDLEINFO_NOTE_EXPIRES", note->id, set_time, note->setter, exp_time, note->note);
1384                 } else {
1385                     reply("NSMSG_HANDLEINFO_NOTE", note->id, set_time, note->setter, note->note);
1386                 }
1387             }
1388         }
1389     }
1390
1391     if (hi->flags) {
1392         unsigned long flen = 1;
1393         char flags[34]; /* 32 bits possible plus '+' and '\0' */
1394         flags[0] = '+';
1395         for (i=0, flen=1; handle_flags[i]; i++)
1396             if (hi->flags & 1 << i)
1397                 flags[flen++] = handle_flags[i];
1398         flags[flen] = 0;
1399         reply("NSMSG_HANDLEINFO_FLAGS", flags);
1400     } else {
1401         reply("NSMSG_HANDLEINFO_FLAGS", nsmsg_none);
1402     }
1403
1404     if (HANDLE_FLAGGED(hi, SUPPORT_HELPER)
1405         || HANDLE_FLAGGED(hi, NETWORK_HELPER)
1406         || (hi->opserv_level > 0)) {
1407         reply("NSMSG_HANDLEINFO_EPITHET", (hi->epithet ? hi->epithet : nsmsg_none));
1408     }
1409
1410     if (hi->fakehost)
1411         reply("NSMSG_HANDLEINFO_FAKEHOST", (hi->fakehost ? hi->fakehost : handle_find_message(hi, "MSG_NONE")));
1412
1413     if (hi->last_quit_host[0])
1414         reply("NSMSG_HANDLEINFO_LAST_HOST", hi->last_quit_host);
1415     else
1416         reply("NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN");
1417
1418     if (nickserv_conf.disable_nicks) {
1419         /* nicks disabled; don't show anything about registered nicks */
1420     } else if (hi->nicks) {
1421         struct nick_info *ni, *next_ni;
1422         for (ni = hi->nicks; ni; ni = next_ni) {
1423             herelen = strlen(ni->nick);
1424             if (pos + herelen + 1 > ArrayLength(buff)) {
1425                 next_ni = ni;
1426                 goto print_nicks_buff;
1427             } else {
1428                 next_ni = ni->next;
1429             }
1430             memcpy(buff+pos, ni->nick, herelen);
1431             pos += herelen; buff[pos++] = ' ';
1432             if (!next_ni) {
1433               print_nicks_buff:
1434                 buff[pos-1] = 0;
1435                 reply("NSMSG_HANDLEINFO_NICKS", buff);
1436                 pos = 0;
1437             }
1438         }
1439     } else {
1440         reply("NSMSG_HANDLEINFO_NICKS", nsmsg_none);
1441     }
1442
1443     if (hi->masks->used) {
1444         for (i=0; i < hi->masks->used; i++) {
1445             herelen = strlen(hi->masks->list[i]);
1446             if (pos + herelen + 1 > ArrayLength(buff)) {
1447                 i--;
1448                 goto print_mask_buff;
1449             }
1450             memcpy(buff+pos, hi->masks->list[i], herelen);
1451             pos += herelen; buff[pos++] = ' ';
1452             if (i+1 == hi->masks->used) {
1453               print_mask_buff:
1454                 buff[pos-1] = 0;
1455                 reply("NSMSG_HANDLEINFO_MASKS", buff);
1456                 pos = 0;
1457             }
1458         }
1459     } else {
1460         reply("NSMSG_HANDLEINFO_MASKS", nsmsg_none);
1461     }
1462
1463     if (hi->channels) {
1464         struct userData *channel, *next;
1465         char *name;
1466
1467         for (channel = hi->channels; channel; channel = next) {
1468             next = channel->u_next;
1469             name = channel->channel->channel->name;
1470             herelen = strlen(name);
1471             if (pos + herelen + 7 > ArrayLength(buff)) {
1472                 next = channel;
1473                 goto print_chans_buff;
1474             }
1475             if (IsUserSuspended(channel))
1476                 buff[pos++] = '-';
1477             pos += sprintf(buff+pos, "%d:%s ", channel->access, name);
1478             if (next == NULL) {
1479               print_chans_buff:
1480                 buff[pos-1] = 0;
1481                 reply("NSMSG_HANDLEINFO_CHANNELS", buff);
1482                 pos = 0;
1483             }
1484         }
1485     } else {
1486         reply("NSMSG_HANDLEINFO_CHANNELS", nsmsg_none);
1487     }
1488
1489     for (target = hi->users; target; target = next_un) {
1490         herelen = strlen(target->nick);
1491         if (pos + herelen + 1 > ArrayLength(buff)) {
1492             next_un = target;
1493             goto print_cnick_buff;
1494         } else {
1495             next_un = target->next_authed;
1496         }
1497         memcpy(buff+pos, target->nick, herelen);
1498         pos += herelen; buff[pos++] = ' ';
1499         if (!next_un) {
1500           print_cnick_buff:
1501             buff[pos-1] = 0;
1502             reply("NSMSG_HANDLEINFO_CURRENT", buff);
1503             pos = 0;
1504         }
1505     }
1506
1507     return 1;
1508 }
1509
1510 static NICKSERV_FUNC(cmd_userinfo)
1511 {
1512     struct userNode *target;
1513
1514     NICKSERV_MIN_PARMS(2);
1515     if (!(target = GetUserH(argv[1]))) {
1516         reply("MSG_NICK_UNKNOWN", argv[1]);
1517         return 0;
1518     }
1519     if (target->handle_info)
1520         reply("NSMSG_USERINFO_AUTHED_AS", target->nick, target->handle_info->handle);
1521     else
1522         reply("NSMSG_USERINFO_NOT_AUTHED", target->nick);
1523     return 1;
1524 }
1525
1526 static NICKSERV_FUNC(cmd_nickinfo)
1527 {
1528     struct nick_info *ni;
1529
1530     NICKSERV_MIN_PARMS(2);
1531     if (!(ni = get_nick_info(argv[1]))) {
1532         reply("MSG_NICK_UNKNOWN", argv[1]);
1533         return 0;
1534     }
1535     reply("NSMSG_NICKINFO_OWNER", ni->nick, ni->owner->handle);
1536     return 1;
1537 }
1538
1539 static NICKSERV_FUNC(cmd_notes)
1540 {
1541     struct handle_info *hi;
1542     struct handle_note *prev, *note;
1543     unsigned int hits;
1544
1545     NICKSERV_MIN_PARMS(2);
1546     if (!(hi = get_victim_oper(user, argv[1])))
1547         return 0;
1548     hits = 0;
1549     WALK_NOTES(hi, prev, note) {
1550         char set_time[INTERVALLEN];
1551         intervalString(set_time, now - note->set, user->handle_info);
1552         if (note->expires) {
1553             char exp_time[INTERVALLEN];
1554             intervalString(exp_time, note->expires - now, user->handle_info);
1555             reply("NSMSG_NOTE_EXPIRES", note->id, set_time, note->setter, exp_time, note->note);
1556         } else {
1557             reply("NSMSG_NOTE", note->id, set_time, note->setter, note->note);
1558         }
1559         ++hits;
1560     }
1561     reply("NSMSG_NOTE_COUNT", hits, argv[1]);
1562     return 1;
1563 }
1564
1565 static NICKSERV_FUNC(cmd_rename_handle)
1566 {
1567     struct handle_info *hi;
1568     char msgbuf[MAXLEN], *old_handle;
1569     unsigned int nn;
1570
1571     NICKSERV_MIN_PARMS(3);
1572     if (!(hi = get_victim_oper(user, argv[1])))
1573         return 0;
1574     if (!is_valid_handle(argv[2])) {
1575         reply("NSMSG_FAIL_RENAME", argv[1], argv[2]);
1576         return 0;
1577     }
1578     if (get_handle_info(argv[2])) {
1579         reply("NSMSG_HANDLE_EXISTS", argv[2]);
1580         return 0;
1581     }
1582
1583     dict_remove2(nickserv_handle_dict, old_handle = hi->handle, 1);
1584     hi->handle = strdup(argv[2]);
1585     dict_insert(nickserv_handle_dict, hi->handle, hi);
1586     for (nn=0; nn<rf_list_used; nn++)
1587         rf_list[nn](hi, old_handle);
1588     snprintf(msgbuf, sizeof(msgbuf), "%s renamed account %s to %s.", user->handle_info->handle, old_handle, hi->handle);
1589     reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
1590     global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
1591     free(old_handle);
1592     return 1;
1593 }
1594
1595 static failpw_func_t *failpw_func_list;
1596 static unsigned int failpw_func_size = 0, failpw_func_used = 0;
1597
1598 void
1599 reg_failpw_func(failpw_func_t func)
1600 {
1601     if (failpw_func_used == failpw_func_size) {
1602         if (failpw_func_size) {
1603             failpw_func_size <<= 1;
1604             failpw_func_list = realloc(failpw_func_list, failpw_func_size*sizeof(failpw_func_t));
1605         } else {
1606             failpw_func_size = 8;
1607             failpw_func_list = malloc(failpw_func_size*sizeof(failpw_func_t));
1608         }
1609     }
1610     failpw_func_list[failpw_func_used++] = func;
1611 }
1612
1613 static NICKSERV_FUNC(cmd_auth)
1614 {
1615     int pw_arg, used, maxlogins;
1616     struct handle_info *hi;
1617     const char *passwd;
1618     struct userNode *other;
1619
1620     if (user->handle_info) {
1621         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1622         return 0;
1623     }
1624     if (IsStamped(user)) {
1625         /* Unauthenticated users might still have been stamped
1626            previously and could therefore have a hidden host;
1627            do not allow them to authenticate. */
1628         reply("NSMSG_STAMPED_AUTH");
1629         return 0;
1630     }
1631     if (argc == 3) {
1632         hi = dict_find(nickserv_handle_dict, argv[1], NULL);
1633         pw_arg = 2;
1634     } else if (argc == 2) {
1635         if (nickserv_conf.disable_nicks) {
1636             if (!(hi = get_handle_info(user->nick))) {
1637                 reply("NSMSG_HANDLE_NOT_FOUND");
1638                 return 0;
1639             }
1640         } else {
1641             /* try to look up their handle from their nick */
1642             struct nick_info *ni;
1643             ni = get_nick_info(user->nick);
1644             if (!ni) {
1645                 reply("NSMSG_NICK_NOT_REGISTERED", user->nick);
1646                 return 0;
1647             }
1648             hi = ni->owner;
1649         }
1650         pw_arg = 1;
1651     } else {
1652         reply("MSG_MISSING_PARAMS", argv[0]);
1653         svccmd_send_help(user, nickserv, cmd);
1654         return 0;
1655     }
1656     if (!hi) {
1657         reply("NSMSG_HANDLE_NOT_FOUND");
1658         return 0;
1659     }
1660     /* Responses from here on look up the language used by the handle they asked about. */
1661     passwd = argv[pw_arg];
1662     if (!valid_user_for(user, hi)) {
1663         if (hi->email_addr && nickserv_conf.email_enabled)
1664             send_message_type(4, user, cmd->parent->bot,
1665                               handle_find_message(hi, "NSMSG_USE_AUTHCOOKIE"),
1666                               hi->handle);
1667         else
1668             send_message_type(4, user, cmd->parent->bot,
1669                               handle_find_message(hi, "NSMSG_HOSTMASK_INVALID"),
1670                               hi->handle);
1671         argv[pw_arg] = "BADMASK";
1672         return 1;
1673     }
1674     if (!checkpass(passwd, hi->passwd)) {
1675         unsigned int n;
1676         send_message_type(4, user, cmd->parent->bot,
1677                           handle_find_message(hi, "NSMSG_PASSWORD_INVALID"));
1678         argv[pw_arg] = "BADPASS";
1679         for (n=0; n<failpw_func_used; n++) failpw_func_list[n](user, hi);
1680         if (nickserv_conf.autogag_enabled) {
1681             if (!user->auth_policer.params) {
1682                 user->auth_policer.last_req = now;
1683                 user->auth_policer.params = nickserv_conf.auth_policer_params;
1684             }
1685             if (!policer_conforms(&user->auth_policer, now, 1.0)) {
1686                 char *hostmask;
1687                 hostmask = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_BYIP|GENMASK_NO_HIDING);
1688                 log_module(NS_LOG, LOG_INFO, "%s auto-gagged for repeated password guessing.", hostmask);
1689                 gag_create(hostmask, nickserv->nick, "Repeated password guessing.", now+nickserv_conf.autogag_duration);
1690                 free(hostmask);
1691                 argv[pw_arg] = "GAGGED";
1692             }
1693         }
1694         return 1;
1695     }
1696     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1697         send_message_type(4, user, cmd->parent->bot,
1698                           handle_find_message(hi, "NSMSG_HANDLE_SUSPENDED"));
1699         argv[pw_arg] = "SUSPENDED";
1700         return 1;
1701     }
1702     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
1703     for (used = 0, other = hi->users; other; other = other->next_authed) {
1704         if (++used >= maxlogins) {
1705             send_message_type(4, user, cmd->parent->bot,
1706                               handle_find_message(hi, "NSMSG_MAX_LOGINS"),
1707                               maxlogins);
1708             argv[pw_arg] = "MAXLOGINS";
1709             return 1;
1710         }
1711     }
1712
1713     set_user_handle_info(user, hi, 1);
1714     if (nickserv_conf.email_required && !hi->email_addr)
1715         reply("NSMSG_PLEASE_SET_EMAIL");
1716     if (!is_secure_password(hi->handle, passwd, NULL))
1717         reply("NSMSG_WEAK_PASSWORD");
1718     if (hi->passwd[0] != '$')
1719         cryptpass(passwd, hi->passwd);
1720     if (!hi->masks->used) {
1721         irc_in_addr_t ip;
1722         string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1723         if (irc_in_addr_is_valid(user->ip) && irc_pton(&ip, NULL, user->hostname))
1724             string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1725     }
1726     argv[pw_arg] = "****";
1727     reply("NSMSG_AUTH_SUCCESS");
1728     return 1;
1729 }
1730
1731 static allowauth_func_t *allowauth_func_list;
1732 static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
1733
1734 void
1735 reg_allowauth_func(allowauth_func_t func)
1736 {
1737     if (allowauth_func_used == allowauth_func_size) {
1738         if (allowauth_func_size) {
1739             allowauth_func_size <<= 1;
1740             allowauth_func_list = realloc(allowauth_func_list, allowauth_func_size*sizeof(allowauth_func_t));
1741         } else {
1742             allowauth_func_size = 8;
1743             allowauth_func_list = malloc(allowauth_func_size*sizeof(allowauth_func_t));
1744         }
1745     }
1746     allowauth_func_list[allowauth_func_used++] = func;
1747 }
1748
1749 static NICKSERV_FUNC(cmd_allowauth)
1750 {
1751     struct userNode *target;
1752     struct handle_info *hi;
1753     unsigned int n;
1754
1755     NICKSERV_MIN_PARMS(2);
1756     if (!(target = GetUserH(argv[1]))) {
1757         reply("MSG_NICK_UNKNOWN", argv[1]);
1758         return 0;
1759     }
1760     if (target->handle_info) {
1761         reply("NSMSG_USER_PREV_AUTH", target->nick);
1762         return 0;
1763     }
1764     if (IsStamped(target)) {
1765         /* Unauthenticated users might still have been stamped
1766            previously and could therefore have a hidden host;
1767            do not allow them to authenticate to an account. */
1768         reply("NSMSG_USER_PREV_STAMP", target->nick);
1769         return 0;
1770     }
1771     if (argc == 2)
1772         hi = NULL;
1773     else if (!(hi = get_handle_info(argv[2]))) {
1774         reply("MSG_HANDLE_UNKNOWN", argv[2]);
1775         return 0;
1776     }
1777     if (hi) {
1778         if (hi->opserv_level > user->handle_info->opserv_level) {
1779             reply("MSG_USER_OUTRANKED", hi->handle);
1780             return 0;
1781         }
1782         if (((hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER))
1783              || (hi->opserv_level > 0))
1784             && ((argc < 4) || irccasecmp(argv[3], "staff"))) {
1785             reply("NSMSG_ALLOWAUTH_STAFF", hi->handle);
1786             return 0;
1787         }
1788         dict_insert(nickserv_allow_auth_dict, target->nick, hi);
1789         reply("NSMSG_AUTH_ALLOWED", target->nick, hi->handle);
1790         send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_MSG", hi->handle, hi->handle);
1791         if (nickserv_conf.email_enabled)
1792             send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_EMAIL");
1793     } else {
1794         if (dict_remove(nickserv_allow_auth_dict, target->nick))
1795             reply("NSMSG_AUTH_NORMAL_ONLY", target->nick);
1796         else
1797             reply("NSMSG_AUTH_UNSPECIAL", target->nick);
1798     }
1799     for (n=0; n<allowauth_func_used; n++)
1800         allowauth_func_list[n](user, target, hi);
1801     return 1;
1802 }
1803
1804 static NICKSERV_FUNC(cmd_authcookie)
1805 {
1806     struct handle_info *hi;
1807
1808     NICKSERV_MIN_PARMS(2);
1809     if (user->handle_info) {
1810         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1811         return 0;
1812     }
1813     if (IsStamped(user)) {
1814         /* Unauthenticated users might still have been stamped
1815            previously and could therefore have a hidden host;
1816            do not allow them to authenticate to an account. */
1817         reply("NSMSG_STAMPED_AUTHCOOKIE");
1818         return 0;
1819     }
1820     if (!(hi = get_handle_info(argv[1]))) {
1821         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1822         return 0;
1823     }
1824     if (!hi->email_addr) {
1825         reply("MSG_SET_EMAIL_ADDR");
1826         return 0;
1827     }
1828     nickserv_make_cookie(user, hi, ALLOWAUTH, NULL);
1829     return 1;
1830 }
1831
1832 static NICKSERV_FUNC(cmd_delcookie)
1833 {
1834     struct handle_info *hi;
1835
1836     hi = user->handle_info;
1837     if (!hi->cookie) {
1838         reply("NSMSG_NO_COOKIE");
1839         return 0;
1840     }
1841     switch (hi->cookie->type) {
1842     case ACTIVATION:
1843     case EMAIL_CHANGE:
1844         reply("NSMSG_MUST_TIME_OUT");
1845         break;
1846     default:
1847         nickserv_eat_cookie(hi->cookie);
1848         reply("NSMSG_ATE_COOKIE");
1849         break;
1850     }
1851     return 1;
1852 }
1853
1854 static NICKSERV_FUNC(cmd_resetpass)
1855 {
1856     struct handle_info *hi;
1857     char crypted[MD5_CRYPT_LENGTH];
1858
1859     NICKSERV_MIN_PARMS(3);
1860     if (user->handle_info) {
1861         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1862         return 0;
1863     }
1864     if (IsStamped(user)) {
1865         /* Unauthenticated users might still have been stamped
1866            previously and could therefore have a hidden host;
1867            do not allow them to activate an account. */
1868         reply("NSMSG_STAMPED_RESETPASS");
1869         return 0;
1870     }
1871     if (!(hi = get_handle_info(argv[1]))) {
1872         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1873         return 0;
1874     }
1875     if (!hi->email_addr) {
1876         reply("MSG_SET_EMAIL_ADDR");
1877         return 0;
1878     }
1879     cryptpass(argv[2], crypted);
1880     argv[2] = "****";
1881     nickserv_make_cookie(user, hi, PASSWORD_CHANGE, crypted);
1882     return 1;
1883 }
1884
1885 static NICKSERV_FUNC(cmd_cookie)
1886 {
1887     struct handle_info *hi;
1888     const char *cookie;
1889
1890     if ((argc == 2) && (hi = user->handle_info) && hi->cookie && (hi->cookie->type == EMAIL_CHANGE)) {
1891         cookie = argv[1];
1892     } else {
1893         NICKSERV_MIN_PARMS(3);
1894         if (!(hi = get_handle_info(argv[1]))) {
1895             reply("MSG_HANDLE_UNKNOWN", argv[1]);
1896             return 0;
1897         }
1898         cookie = argv[2];
1899     }
1900
1901     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1902         reply("NSMSG_HANDLE_SUSPENDED");
1903         return 0;
1904     }
1905
1906     if (!hi->cookie) {
1907         reply("NSMSG_NO_COOKIE");
1908         return 0;
1909     }
1910
1911     /* Check validity of operation before comparing cookie to
1912      * prohibit guessing by authed users. */
1913     if (user->handle_info
1914         && (hi->cookie->type != EMAIL_CHANGE)
1915         && (hi->cookie->type != PASSWORD_CHANGE)) {
1916         reply("NSMSG_CANNOT_COOKIE");
1917         return 0;
1918     }
1919
1920     if (strcmp(cookie, hi->cookie->cookie)) {
1921         reply("NSMSG_BAD_COOKIE");
1922         return 0;
1923     }
1924
1925     switch (hi->cookie->type) {
1926     case ACTIVATION:
1927         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1928         set_user_handle_info(user, hi, 1);
1929         reply("NSMSG_HANDLE_ACTIVATED");
1930         break;
1931     case PASSWORD_CHANGE:
1932         set_user_handle_info(user, hi, 1);
1933         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1934         reply("NSMSG_PASSWORD_CHANGED");
1935         break;
1936     case EMAIL_CHANGE:
1937         nickserv_set_email_addr(hi, hi->cookie->data);
1938         reply("NSMSG_EMAIL_CHANGED");
1939         break;
1940     case ALLOWAUTH: {
1941         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
1942         set_user_handle_info(user, hi, 1);
1943         nickserv_addmask(user, hi, mask);
1944         reply("NSMSG_AUTH_SUCCESS");
1945         free(mask);
1946         break;
1947     }
1948     default:
1949         reply("NSMSG_BAD_COOKIE_TYPE", hi->cookie->type);
1950         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d for account %s.", hi->cookie->type, hi->handle);
1951         break;
1952     }
1953
1954     nickserv_eat_cookie(hi->cookie);
1955
1956     return 1;
1957 }
1958
1959 static NICKSERV_FUNC(cmd_oregnick) {
1960     const char *nick;
1961     struct handle_info *target;
1962     struct nick_info *ni;
1963
1964     NICKSERV_MIN_PARMS(3);
1965     if (!(target = modcmd_get_handle_info(user, argv[1])))
1966         return 0;
1967     nick = argv[2];
1968     if (!is_registerable_nick(nick)) {
1969         reply("NSMSG_BAD_NICK", nick);
1970         return 0;
1971     }
1972     ni = dict_find(nickserv_nick_dict, nick, NULL);
1973     if (ni) {
1974         reply("NSMSG_NICK_EXISTS", nick);
1975         return 0;
1976     }
1977     register_nick(nick, target);
1978     reply("NSMSG_OREGNICK_SUCCESS", nick, target->handle);
1979     return 1;
1980 }
1981
1982 static NICKSERV_FUNC(cmd_regnick) {
1983     unsigned n;
1984     struct nick_info *ni;
1985
1986     if (!is_registerable_nick(user->nick)) {
1987         reply("NSMSG_BAD_NICK", user->nick);
1988         return 0;
1989     }
1990     /* count their nicks, see if it's too many */
1991     for (n=0,ni=user->handle_info->nicks; ni; n++,ni=ni->next) ;
1992     if (n >= nickserv_conf.nicks_per_handle) {
1993         reply("NSMSG_TOO_MANY_NICKS");
1994         return 0;
1995     }
1996     ni = dict_find(nickserv_nick_dict, user->nick, NULL);
1997     if (ni) {
1998         reply("NSMSG_NICK_EXISTS", user->nick);
1999         return 0;
2000     }
2001     register_nick(user->nick, user->handle_info);
2002     reply("NSMSG_REGNICK_SUCCESS", user->nick);
2003     return 1;
2004 }
2005
2006 static NICKSERV_FUNC(cmd_pass)
2007 {
2008     struct handle_info *hi;
2009     const char *old_pass, *new_pass;
2010
2011     NICKSERV_MIN_PARMS(3);
2012     hi = user->handle_info;
2013     old_pass = argv[1];
2014     new_pass = argv[2];
2015     argv[2] = "****";
2016     if (!is_secure_password(hi->handle, new_pass, user)) return 0;
2017     if (!checkpass(old_pass, hi->passwd)) {
2018         argv[1] = "BADPASS";
2019         reply("NSMSG_PASSWORD_INVALID");
2020         return 0;
2021     }
2022     cryptpass(new_pass, hi->passwd);
2023     argv[1] = "****";
2024     reply("NSMSG_PASS_SUCCESS");
2025     return 1;
2026 }
2027
2028 static int
2029 nickserv_addmask(struct userNode *user, struct handle_info *hi, const char *mask)
2030 {
2031     unsigned int i;
2032     char *new_mask = canonicalize_hostmask(strdup(mask));
2033     for (i=0; i<hi->masks->used; i++) {
2034         if (!irccasecmp(new_mask, hi->masks->list[i])) {
2035             send_message(user, nickserv, "NSMSG_ADDMASK_ALREADY", new_mask);
2036             free(new_mask);
2037             return 0;
2038         }
2039     }
2040     string_list_append(hi->masks, new_mask);
2041     send_message(user, nickserv, "NSMSG_ADDMASK_SUCCESS", new_mask);
2042     return 1;
2043 }
2044
2045 static NICKSERV_FUNC(cmd_addmask)
2046 {
2047     if (argc < 2) {
2048         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
2049         int res = nickserv_addmask(user, user->handle_info, mask);
2050         free(mask);
2051         return res;
2052     } else {
2053         if (!is_gline(argv[1])) {
2054             reply("NSMSG_MASK_INVALID", argv[1]);
2055             return 0;
2056         }
2057         return nickserv_addmask(user, user->handle_info, argv[1]);
2058     }
2059 }
2060
2061 static NICKSERV_FUNC(cmd_oaddmask)
2062 {
2063     struct handle_info *hi;
2064
2065     NICKSERV_MIN_PARMS(3);
2066     if (!(hi = get_victim_oper(user, argv[1])))
2067         return 0;
2068     return nickserv_addmask(user, hi, argv[2]);
2069 }
2070
2071 static int
2072 nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask, int force)
2073 {
2074     unsigned int i;
2075     for (i=0; i<hi->masks->used; i++) {
2076         if (!strcmp(del_mask, hi->masks->list[i])) {
2077             char *old_mask = hi->masks->list[i];
2078             if (hi->masks->used == 1 && !force) {
2079                 send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
2080                 return 0;
2081             }
2082             hi->masks->list[i] = hi->masks->list[--hi->masks->used];
2083             send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
2084             free(old_mask);
2085             return 1;
2086         }
2087     }
2088     send_message(user, nickserv, "NSMSG_DELMASK_NOT_FOUND");
2089     return 0;
2090 }
2091
2092 static NICKSERV_FUNC(cmd_delmask)
2093 {
2094     NICKSERV_MIN_PARMS(2);
2095     return nickserv_delmask(user, user->handle_info, argv[1], 0);
2096 }
2097
2098 static NICKSERV_FUNC(cmd_odelmask)
2099 {
2100     struct handle_info *hi;
2101     NICKSERV_MIN_PARMS(3);
2102     if (!(hi = get_victim_oper(user, argv[1])))
2103         return 0;
2104     return nickserv_delmask(user, hi, argv[2], 1);
2105 }
2106
2107 int
2108 nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *padded, unsigned long *premoved) {
2109     unsigned int nn, add = 1, pos;
2110     unsigned long added, removed, flag;
2111
2112     for (added=removed=nn=0; str[nn]; nn++) {
2113         switch (str[nn]) {
2114         case '+': add = 1; break;
2115         case '-': add = 0; break;
2116         default:
2117             if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
2118                 send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
2119                 return 0;
2120             }
2121             if (user && (user->handle_info->opserv_level < flag_access_levels[pos-1])) {
2122                 /* cheesy avoidance of looking up the flag name.. */
2123                 send_message(user, bot, "NSMSG_FLAG_PRIVILEGED", str[nn]);
2124                 return 0;
2125             }
2126             flag = 1 << (pos - 1);
2127             if (add)
2128                 added |= flag, removed &= ~flag;
2129             else
2130                 removed |= flag, added &= ~flag;
2131             break;
2132         }
2133     }
2134     *padded = added;
2135     *premoved = removed;
2136     return 1;
2137 }
2138
2139 static int
2140 nickserv_apply_flags(struct userNode *user, struct handle_info *hi, const char *flags)
2141 {
2142     unsigned long before, after, added, removed;
2143     struct userNode *uNode;
2144
2145     before = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2146     if (!nickserv_modify_handle_flags(user, nickserv, flags, &added, &removed))
2147         return 0;
2148     hi->flags = (hi->flags | added) & ~removed;
2149     after = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2150
2151     /* Strip helping flag if they're only a support helper and not
2152      * currently in #support. */
2153     if (HANDLE_FLAGGED(hi, HELPING) && (after == HI_FLAG_SUPPORT_HELPER)) {
2154         struct channelList *schannels;
2155         unsigned int ii;
2156         schannels = chanserv_support_channels();
2157         for (uNode = hi->users; uNode; uNode = uNode->next_authed) {
2158             for (ii = 0; ii < schannels->used; ++ii)
2159                 if (GetUserMode(schannels->list[ii], uNode))
2160                     break;
2161             if (ii < schannels->used)
2162                 break;
2163         }
2164         if (!uNode)
2165             HANDLE_CLEAR_FLAG(hi, HELPING);
2166     }
2167
2168     if (after && !before) {
2169         /* Add user to current helper list. */
2170         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2171             userList_append(&curr_helpers, uNode);
2172     } else if (!after && before) {
2173         /* Remove user from current helper list. */
2174         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2175             userList_remove(&curr_helpers, uNode);
2176     }
2177
2178     return 1;
2179 }
2180
2181 static void
2182 set_list(struct userNode *user, struct handle_info *hi, int override)
2183 {
2184     option_func_t *opt;
2185     unsigned int i;
2186     char *set_display[] = {
2187         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
2188         "EMAIL", "MAXLOGINS", "LANGUAGE"
2189     };
2190
2191     send_message(user, nickserv, "NSMSG_SETTING_LIST");
2192
2193     /* Do this so options are presented in a consistent order. */
2194     for (i = 0; i < ArrayLength(set_display); ++i)
2195         if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
2196             opt(user, hi, override, 0, NULL);
2197 }
2198
2199 static NICKSERV_FUNC(cmd_set)
2200 {
2201     struct handle_info *hi;
2202     option_func_t *opt;
2203
2204     hi = user->handle_info;
2205     if (argc < 2) {
2206         set_list(user, hi, 0);
2207         return 1;
2208     }
2209     if (!(opt = dict_find(nickserv_opt_dict, argv[1], NULL))) {
2210         reply("NSMSG_INVALID_OPTION", argv[1]);
2211         return 0;
2212     }
2213     return opt(user, hi, 0, argc-1, argv+1);
2214 }
2215
2216 static NICKSERV_FUNC(cmd_oset)
2217 {
2218     struct handle_info *hi;
2219     struct svccmd *subcmd;
2220     option_func_t *opt;
2221     char cmdname[MAXLEN];
2222
2223     NICKSERV_MIN_PARMS(2);
2224
2225     if (!(hi = get_victim_oper(user, argv[1])))
2226         return 0;
2227
2228     if (argc < 3) {
2229         set_list(user, hi, 0);
2230         return 1;
2231     }
2232
2233     if (!(opt = dict_find(nickserv_opt_dict, argv[2], NULL))) {
2234         reply("NSMSG_INVALID_OPTION", argv[2]);
2235         return 0;
2236     }
2237
2238     sprintf(cmdname, "%s %s", cmd->name, argv[2]);
2239     subcmd = dict_find(cmd->parent->commands, cmdname, NULL);
2240     if (subcmd && !svccmd_can_invoke(user, cmd->parent->bot, subcmd, NULL, SVCCMD_NOISY))
2241         return 0;
2242
2243     return opt(user, hi, 1, argc-2, argv+2);
2244 }
2245
2246 static OPTION_FUNC(opt_info)
2247 {
2248     const char *info;
2249     if (argc > 1) {
2250         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2251             free(hi->infoline);
2252             hi->infoline = NULL;
2253         } else {
2254             hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
2255         }
2256     }
2257
2258     info = hi->infoline ? hi->infoline : user_find_message(user, "MSG_NONE");
2259     send_message(user, nickserv, "NSMSG_SET_INFO", info);
2260     return 1;
2261 }
2262
2263 static OPTION_FUNC(opt_width)
2264 {
2265     if (argc > 1)
2266         hi->screen_width = strtoul(argv[1], NULL, 0);
2267
2268     if ((hi->screen_width > 0) && (hi->screen_width < MIN_LINE_SIZE))
2269         hi->screen_width = MIN_LINE_SIZE;
2270     else if (hi->screen_width > MAX_LINE_SIZE)
2271         hi->screen_width = MAX_LINE_SIZE;
2272
2273     send_message(user, nickserv, "NSMSG_SET_WIDTH", hi->screen_width);
2274     return 1;
2275 }
2276
2277 static OPTION_FUNC(opt_tablewidth)
2278 {
2279     if (argc > 1)
2280         hi->table_width = strtoul(argv[1], NULL, 0);
2281
2282     if ((hi->table_width > 0) && (hi->table_width < MIN_LINE_SIZE))
2283         hi->table_width = MIN_LINE_SIZE;
2284     else if (hi->screen_width > MAX_LINE_SIZE)
2285         hi->table_width = MAX_LINE_SIZE;
2286
2287     send_message(user, nickserv, "NSMSG_SET_TABLEWIDTH", hi->table_width);
2288     return 1;
2289 }
2290
2291 static OPTION_FUNC(opt_color)
2292 {
2293     if (argc > 1) {
2294         if (enabled_string(argv[1]))
2295             HANDLE_SET_FLAG(hi, MIRC_COLOR);
2296         else if (disabled_string(argv[1]))
2297             HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
2298         else {
2299             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2300             return 0;
2301         }
2302     }
2303
2304     send_message(user, nickserv, "NSMSG_SET_COLOR", user_find_message(user, HANDLE_FLAGGED(hi, MIRC_COLOR) ? "MSG_ON" : "MSG_OFF"));
2305     return 1;
2306 }
2307
2308 static OPTION_FUNC(opt_privmsg)
2309 {
2310     if (argc > 1) {
2311         if (enabled_string(argv[1]))
2312             HANDLE_SET_FLAG(hi, USE_PRIVMSG);
2313         else if (disabled_string(argv[1]))
2314             HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
2315         else {
2316             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2317             return 0;
2318         }
2319     }
2320
2321     send_message(user, nickserv, "NSMSG_SET_PRIVMSG", user_find_message(user, HANDLE_FLAGGED(hi, USE_PRIVMSG) ? "MSG_ON" : "MSG_OFF"));
2322     return 1;
2323 }
2324
2325 static OPTION_FUNC(opt_style)
2326 {
2327     char *style;
2328
2329     if (argc > 1) {
2330         if (!irccasecmp(argv[1], "Zoot"))
2331             hi->userlist_style = HI_STYLE_ZOOT;
2332         else if (!irccasecmp(argv[1], "def"))
2333             hi->userlist_style = HI_STYLE_DEF;
2334     }
2335
2336     switch (hi->userlist_style) {
2337     case HI_STYLE_DEF:
2338         style = "def";
2339         break;
2340     case HI_STYLE_ZOOT:
2341     default:
2342         style = "Zoot";
2343     }
2344
2345     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
2346     return 1;
2347 }
2348
2349 static OPTION_FUNC(opt_password)
2350 {
2351     if (!override) {
2352         send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
2353         return 0;
2354     }
2355
2356     if (argc > 1)
2357         cryptpass(argv[1], hi->passwd);
2358
2359     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
2360     return 1;
2361 }
2362
2363 static OPTION_FUNC(opt_flags)
2364 {
2365     char flags[33];
2366     unsigned int ii, flen;
2367
2368     if (!override) {
2369         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2370         return 0;
2371     }
2372
2373     if (argc > 1)
2374         nickserv_apply_flags(user, hi, argv[1]);
2375
2376     for (ii = flen = 0; handle_flags[ii]; ii++)
2377         if (hi->flags & (1 << ii))
2378             flags[flen++] = handle_flags[ii];
2379     flags[flen] = '\0';
2380     if (hi->flags)
2381         send_message(user, nickserv, "NSMSG_SET_FLAGS", flags);
2382     else
2383         send_message(user, nickserv, "NSMSG_SET_FLAGS", user_find_message(user, "MSG_NONE"));
2384     return 1;
2385 }
2386
2387 static OPTION_FUNC(opt_email)
2388 {
2389     if (argc > 1) {
2390         const char *str;
2391         if (!is_valid_email_addr(argv[1])) {
2392             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
2393             return 0;
2394         }
2395         if ((str = mail_prohibited_address(argv[1]))) {
2396             send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", argv[1], str);
2397             return 0;
2398         }
2399         if (hi->email_addr && !irccasecmp(hi->email_addr, argv[1]))
2400             send_message(user, nickserv, "NSMSG_EMAIL_SAME");
2401         else if (!override)
2402                 nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1]);
2403         else {
2404             nickserv_set_email_addr(hi, argv[1]);
2405             if (hi->cookie)
2406                 nickserv_eat_cookie(hi->cookie);
2407             send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2408         }
2409     } else
2410         send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2411     return 1;
2412 }
2413
2414 static OPTION_FUNC(opt_maxlogins)
2415 {
2416     unsigned char maxlogins;
2417     if (argc > 1) {
2418         maxlogins = strtoul(argv[1], NULL, 0);
2419         if ((maxlogins > nickserv_conf.hard_maxlogins) && !override) {
2420             send_message(user, nickserv, "NSMSG_BAD_MAX_LOGINS", nickserv_conf.hard_maxlogins);
2421             return 0;
2422         }
2423         hi->maxlogins = maxlogins;
2424     }
2425     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
2426     send_message(user, nickserv, "NSMSG_SET_MAXLOGINS", maxlogins);
2427     return 1;
2428 }
2429
2430 static OPTION_FUNC(opt_language)
2431 {
2432     struct language *lang;
2433     if (argc > 1) {
2434         lang = language_find(argv[1]);
2435         if (irccasecmp(lang->name, argv[1]))
2436             send_message(user, nickserv, "NSMSG_LANGUAGE_NOT_FOUND", argv[1], lang->name);
2437         hi->language = lang;
2438     }
2439     send_message(user, nickserv, "NSMSG_SET_LANGUAGE", hi->language->name);
2440     return 1;
2441 }
2442
2443 static OPTION_FUNC(opt_karma)
2444 {
2445     if (!override) {
2446         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2447         return 0;
2448     }
2449
2450     if (argc > 1) {
2451         if (argv[1][0] == '+' && isdigit(argv[1][1])) {
2452             hi->karma += strtoul(argv[1] + 1, NULL, 10);
2453         } else if (argv[1][0] == '-' && isdigit(argv[1][1])) {
2454             hi->karma -= strtoul(argv[1] + 1, NULL, 10);
2455         } else {
2456             send_message(user, nickserv, "NSMSG_INVALID_KARMA", argv[1]);
2457         }
2458     }
2459
2460     send_message(user, nickserv, "NSMSG_SET_KARMA", hi->karma);
2461     return 1;
2462 }
2463
2464 int
2465 oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
2466     if (!oper_has_access(user, bot, nickserv_conf.modoper_level, 0))
2467         return 0;
2468     if ((user->handle_info->opserv_level < target->opserv_level)
2469         || ((user->handle_info->opserv_level == target->opserv_level)
2470             && (user->handle_info->opserv_level < 1000))) {
2471         send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
2472         return 0;
2473     }
2474     if ((user->handle_info->opserv_level < new_level)
2475         || ((user->handle_info->opserv_level == new_level)
2476             && (user->handle_info->opserv_level < 1000))) {
2477         send_message(user, bot, "NSMSG_OPSERV_LEVEL_BAD");
2478         return 0;
2479     }
2480     if (user->handle_info == target) {
2481         send_message(user, bot, "MSG_STUPID_ACCESS_CHANGE");
2482         return 0;
2483     }
2484     if (target->opserv_level == new_level)
2485         return 0;
2486     log_module(NS_LOG, LOG_INFO, "Account %s setting oper level for account %s to %d (from %d).",
2487         user->handle_info->handle, target->handle, new_level, target->opserv_level);
2488     target->opserv_level = new_level;
2489     return 1;
2490 }
2491
2492 static OPTION_FUNC(opt_level)
2493 {
2494     int res;
2495
2496     if (!override) {
2497         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2498         return 0;
2499     }
2500
2501     res = (argc > 1) ? oper_try_set_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
2502     send_message(user, nickserv, "NSMSG_SET_LEVEL", hi->opserv_level);
2503     return res;
2504 }
2505
2506 static OPTION_FUNC(opt_epithet)
2507 {
2508     if (!override) {
2509         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2510         return 0;
2511     }
2512
2513     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_epithet_level, 0)) {
2514         char *epithet = unsplit_string(argv+1, argc-1, NULL);
2515         if (hi->epithet)
2516             free(hi->epithet);
2517         if ((epithet[0] == '*') && !epithet[1])
2518             hi->epithet = NULL;
2519         else
2520             hi->epithet = strdup(epithet);
2521     }
2522
2523     if (hi->epithet)
2524         send_message(user, nickserv, "NSMSG_SET_EPITHET", hi->epithet);
2525     else
2526         send_message(user, nickserv, "NSMSG_SET_EPITHET", user_find_message(user, "MSG_NONE"));
2527     return 1;
2528 }
2529
2530 static OPTION_FUNC(opt_title)
2531 {
2532     const char *title;
2533
2534     if (!override) {
2535         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2536         return 0;
2537     }
2538
2539     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_title_level, 0)) {
2540         title = argv[1];
2541         if (strchr(title, '.')) {
2542             send_message(user, nickserv, "NSMSG_TITLE_INVALID");
2543             return 0;
2544         }
2545         if ((strlen(user->handle_info->handle) + strlen(title) +
2546              strlen(nickserv_conf.titlehost_suffix) + 2) > HOSTLEN) {
2547             send_message(user, nickserv, "NSMSG_TITLE_TRUNCATED");
2548             return 0;
2549         }
2550
2551         free(hi->fakehost);
2552         if (!strcmp(title, "*")) {
2553             hi->fakehost = NULL;
2554         } else {
2555             hi->fakehost = malloc(strlen(title)+2);
2556             hi->fakehost[0] = '.';
2557             strcpy(hi->fakehost+1, title);
2558         }
2559         apply_fakehost(hi);
2560     } else if (hi->fakehost && (hi->fakehost[0] == '.'))
2561         title = hi->fakehost + 1;
2562     else
2563         title = NULL;
2564     if (!title)
2565         title = user_find_message(user, "MSG_NONE");
2566     send_message(user, nickserv, "NSMSG_SET_TITLE", title);
2567     return 1;
2568 }
2569
2570 static OPTION_FUNC(opt_fakehost)
2571 {
2572     const char *fake;
2573
2574     if (!override) {
2575         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2576         return 0;
2577     }
2578
2579     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakehost_level, 0)) {
2580         fake = argv[1];
2581         if ((strlen(fake) > HOSTLEN) || (fake[0] == '.')) {
2582             send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
2583             return 0;
2584         }
2585         free(hi->fakehost);
2586         if (!strcmp(fake, "*"))
2587             hi->fakehost = NULL;
2588         else
2589             hi->fakehost = strdup(fake);
2590         fake = hi->fakehost;
2591         apply_fakehost(hi);
2592     } else
2593         fake = generate_fakehost(hi);
2594     if (!fake)
2595         fake = user_find_message(user, "MSG_NONE");
2596     send_message(user, nickserv, "NSMSG_SET_FAKEHOST", fake);
2597     return 1;
2598 }
2599
2600 static NICKSERV_FUNC(cmd_reclaim)
2601 {
2602     struct handle_info *hi;
2603     struct nick_info *ni;
2604     struct userNode *victim;
2605
2606     NICKSERV_MIN_PARMS(2);
2607     hi = user->handle_info;
2608     ni = dict_find(nickserv_nick_dict, argv[1], 0);
2609     if (!ni) {
2610         reply("NSMSG_UNKNOWN_NICK", argv[1]);
2611         return 0;
2612     }
2613     if (ni->owner != user->handle_info) {
2614         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
2615         return 0;
2616     }
2617     victim = GetUserH(ni->nick);
2618     if (!victim) {
2619         reply("MSG_NICK_UNKNOWN", ni->nick);
2620         return 0;
2621     }
2622     if (victim == user) {
2623         reply("NSMSG_NICK_USER_YOU");
2624         return 0;
2625     }
2626     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
2627     switch (nickserv_conf.reclaim_action) {
2628     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
2629     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
2630     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
2631     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
2632     }
2633     return 1;
2634 }
2635
2636 static NICKSERV_FUNC(cmd_unregnick)
2637 {
2638     const char *nick;
2639     struct handle_info *hi;
2640     struct nick_info *ni;
2641
2642     hi = user->handle_info;
2643     nick = (argc < 2) ? user->nick : (const char*)argv[1];
2644     ni = dict_find(nickserv_nick_dict, nick, NULL);
2645     if (!ni) {
2646         reply("NSMSG_UNKNOWN_NICK", nick);
2647         return 0;
2648     }
2649     if (hi != ni->owner) {
2650         reply("NSMSG_NOT_YOUR_NICK", nick);
2651         return 0;
2652     }
2653     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2654     delete_nick(ni);
2655     return 1;
2656 }
2657
2658 static NICKSERV_FUNC(cmd_ounregnick)
2659 {
2660     struct nick_info *ni;
2661
2662     NICKSERV_MIN_PARMS(2);
2663     if (!(ni = get_nick_info(argv[1]))) {
2664         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
2665         return 0;
2666     }
2667     if (!oper_outranks(user, ni->owner))
2668         return 0;
2669     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2670     delete_nick(ni);
2671     return 1;
2672 }
2673
2674 static NICKSERV_FUNC(cmd_unregister)
2675 {
2676     struct handle_info *hi;
2677     char *passwd;
2678
2679     NICKSERV_MIN_PARMS(2);
2680     hi = user->handle_info;
2681     passwd = argv[1];
2682     argv[1] = "****";
2683     if (checkpass(passwd, hi->passwd)) {
2684         nickserv_unregister_handle(hi, user);
2685         return 1;
2686     } else {
2687         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
2688         reply("NSMSG_PASSWORD_INVALID");
2689         return 0;
2690     }
2691 }
2692
2693 static NICKSERV_FUNC(cmd_ounregister)
2694 {
2695     struct handle_info *hi;
2696     char reason[MAXLEN];
2697     int force;
2698
2699     NICKSERV_MIN_PARMS(2);
2700     if (!(hi = get_victim_oper(user, argv[1])))
2701         return 0;
2702
2703     if (HANDLE_FLAGGED(hi, NODELETE)) {
2704         reply("NSMSG_UNREGISTER_NODELETE", hi->handle);
2705         return 0;
2706     }
2707
2708     force = IsOper(user) && (argc > 2) && !irccasecmp(argv[2], "force");
2709     if (!force &&
2710         ((hi->flags & nickserv_conf.ounregister_flags)
2711          || hi->users
2712          || (hi->last_quit_host[0] && ((unsigned)(now - hi->lastseen) < nickserv_conf.ounregister_inactive)))) {
2713         reply((IsOper(user) ? "NSMSG_UNREGISTER_MUST_FORCE" : "NSMSG_UNREGISTER_CANNOT_FORCE"), hi->handle);
2714         return 0;
2715     }
2716
2717     snprintf(reason, sizeof(reason), "%s unregistered account %s.", user->handle_info->handle, hi->handle);
2718     global_message(MESSAGE_RECIPIENT_STAFF, reason);
2719     nickserv_unregister_handle(hi, user);
2720     return 1;
2721 }
2722
2723 static NICKSERV_FUNC(cmd_status)
2724 {
2725     if (nickserv_conf.disable_nicks) {
2726         reply("NSMSG_GLOBAL_STATS_NONICK",
2727                         dict_size(nickserv_handle_dict));
2728     } else {
2729         if (user->handle_info) {
2730             int cnt=0;
2731             struct nick_info *ni;
2732             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
2733             reply("NSMSG_HANDLE_STATS", cnt);
2734         } else {
2735             reply("NSMSG_HANDLE_NONE");
2736         }
2737         reply("NSMSG_GLOBAL_STATS",
2738               dict_size(nickserv_handle_dict),
2739               dict_size(nickserv_nick_dict));
2740     }
2741     return 1;
2742 }
2743
2744 static NICKSERV_FUNC(cmd_ghost)
2745 {
2746     struct userNode *target;
2747     char reason[MAXLEN];
2748
2749     NICKSERV_MIN_PARMS(2);
2750     if (!(target = GetUserH(argv[1]))) {
2751         reply("MSG_NICK_UNKNOWN", argv[1]);
2752         return 0;
2753     }
2754     if (target == user) {
2755         reply("NSMSG_CANNOT_GHOST_SELF");
2756         return 0;
2757     }
2758     if (!target->handle_info || (target->handle_info != user->handle_info)) {
2759         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
2760         return 0;
2761     }
2762     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
2763     DelUser(target, nickserv, 1, reason);
2764     reply("NSMSG_GHOST_KILLED", argv[1]);
2765     return 1;
2766 }
2767
2768 static NICKSERV_FUNC(cmd_vacation)
2769 {
2770     HANDLE_SET_FLAG(user->handle_info, FROZEN);
2771     reply("NSMSG_ON_VACATION");
2772     return 1;
2773 }
2774
2775 static NICKSERV_FUNC(cmd_addnote)
2776 {
2777     struct handle_info *hi;
2778     unsigned long duration;
2779     char text[MAXLEN];
2780     unsigned int id;
2781     struct handle_note *prev;
2782     struct handle_note *note;
2783
2784     /* Parse parameters and figure out values for note's fields. */
2785     NICKSERV_MIN_PARMS(4);
2786     hi = get_victim_oper(user, argv[1]);
2787     if (!hi)
2788         return 0;
2789     if(!strcmp(argv[2], "0"))
2790         duration = 0;
2791     else if(!(duration = ParseInterval(argv[2])))
2792     {
2793         reply("MSG_INVALID_DURATION", argv[2]);
2794         return 0;
2795     }
2796     if (duration > 2*365*86400) {
2797         reply("NSMSG_EXCESSIVE_DURATION", argv[2]);
2798         return 0;
2799     }
2800     unsplit_string(argv + 3, argc - 3, text);
2801     WALK_NOTES(hi, prev, note) {}
2802     id = prev ? (prev->id + 1) : 1;
2803
2804     /* Create the new note structure. */
2805     note = calloc(1, sizeof(*note) + strlen(text));
2806     note->next = NULL;
2807     note->expires = duration ? (now + duration) : 0;
2808     note->set = now;
2809     note->id = id;
2810     safestrncpy(note->setter, user->handle_info->handle, sizeof(note->setter));
2811     strcpy(note->note, text);
2812     if (prev)
2813         prev->next = note;
2814     else
2815         hi->notes = note;
2816     reply("NSMSG_NOTE_ADDED", id, hi->handle);
2817     return 1;
2818 }
2819
2820 static NICKSERV_FUNC(cmd_delnote)
2821 {
2822     struct handle_info *hi;
2823     struct handle_note *prev;
2824     struct handle_note *note;
2825     int id;
2826
2827     NICKSERV_MIN_PARMS(3);
2828     hi = get_victim_oper(user, argv[1]);
2829     if (!hi)
2830         return 0;
2831     id = strtoul(argv[2], NULL, 10);
2832     WALK_NOTES(hi, prev, note) {
2833         if (id == note->id) {
2834             if (prev)
2835                 prev->next = note->next;
2836             else
2837                 hi->notes = note->next;
2838             free(note);
2839             reply("NSMSG_NOTE_REMOVED", id, hi->handle);
2840             return 1;
2841         }
2842     }
2843     reply("NSMSG_NO_SUCH_NOTE", hi->handle, id);
2844     return 0;
2845 }
2846
2847 static int
2848 nickserv_saxdb_write(struct saxdb_context *ctx) {
2849     dict_iterator_t it;
2850     struct handle_info *hi;
2851     char flags[33];
2852
2853     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2854         hi = iter_data(it);
2855 #ifdef WITH_PROTOCOL_BAHAMUT
2856         assert(hi->id);
2857 #endif
2858         saxdb_start_record(ctx, iter_key(it), 0);
2859         if (hi->cookie) {
2860             struct handle_cookie *cookie = hi->cookie;
2861             char *type;
2862
2863             switch (cookie->type) {
2864             case ACTIVATION: type = KEY_ACTIVATION; break;
2865             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
2866             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
2867             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
2868             default: type = NULL; break;
2869             }
2870             if (type) {
2871                 saxdb_start_record(ctx, KEY_COOKIE, 0);
2872                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
2873                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
2874                 if (cookie->data)
2875                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
2876                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
2877                 saxdb_end_record(ctx);
2878             }
2879         }
2880         if (hi->notes) {
2881             struct handle_note *prev, *note;
2882             saxdb_start_record(ctx, KEY_NOTES, 0);
2883             WALK_NOTES(hi, prev, note) {
2884                 snprintf(flags, sizeof(flags), "%d", note->id);
2885                 saxdb_start_record(ctx, flags, 0);
2886                 if (note->expires)
2887                     saxdb_write_int(ctx, KEY_NOTE_EXPIRES, note->expires);
2888                 saxdb_write_int(ctx, KEY_NOTE_SET, note->set);
2889                 saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
2890                 saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
2891                 saxdb_end_record(ctx);
2892             }
2893             saxdb_end_record(ctx);
2894         }
2895         if (hi->email_addr)
2896             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
2897         if (hi->epithet)
2898             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
2899         if (hi->fakehost)
2900             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
2901         if (hi->flags) {
2902             int ii, flen;
2903
2904             for (ii=flen=0; handle_flags[ii]; ++ii)
2905                 if (hi->flags & (1 << ii))
2906                     flags[flen++] = handle_flags[ii];
2907             flags[flen] = 0;
2908             saxdb_write_string(ctx, KEY_FLAGS, flags);
2909         }
2910 #ifdef WITH_PROTOCOL_BAHAMUT
2911         saxdb_write_int(ctx, KEY_ID, hi->id);
2912 #endif
2913         if (hi->infoline)
2914             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
2915         if (hi->last_quit_host[0])
2916             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
2917         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
2918         if (hi->karma != 0)
2919             saxdb_write_sint(ctx, KEY_KARMA, hi->karma);
2920         if (hi->masks->used)
2921             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
2922         if (hi->maxlogins)
2923             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
2924         if (hi->nicks) {
2925             struct string_list *slist;
2926             struct nick_info *ni;
2927
2928             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
2929             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
2930             saxdb_write_string_list(ctx, KEY_NICKS, slist);
2931             free(slist->list);
2932             free(slist);
2933         }
2934         if (hi->opserv_level)
2935             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
2936         if (hi->language != lang_C)
2937             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
2938         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
2939         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
2940         if (hi->screen_width)
2941             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
2942         if (hi->table_width)
2943             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
2944         flags[0] = hi->userlist_style;
2945         flags[1] = 0;
2946         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
2947         saxdb_end_record(ctx);
2948     }
2949     return 0;
2950 }
2951
2952 static handle_merge_func_t *handle_merge_func_list;
2953 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
2954
2955 void
2956 reg_handle_merge_func(handle_merge_func_t func)
2957 {
2958     if (handle_merge_func_used == handle_merge_func_size) {
2959         if (handle_merge_func_size) {
2960             handle_merge_func_size <<= 1;
2961             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
2962         } else {
2963             handle_merge_func_size = 8;
2964             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
2965         }
2966     }
2967     handle_merge_func_list[handle_merge_func_used++] = func;
2968 }
2969
2970 static NICKSERV_FUNC(cmd_merge)
2971 {
2972     struct handle_info *hi_from, *hi_to;
2973     struct userNode *last_user;
2974     struct userData *cList, *cListNext;
2975     unsigned int ii, jj, n;
2976     char buffer[MAXLEN];
2977
2978     NICKSERV_MIN_PARMS(3);
2979
2980     if (!(hi_from = get_victim_oper(user, argv[1])))
2981         return 0;
2982     if (!(hi_to = get_victim_oper(user, argv[2])))
2983         return 0;
2984     if (hi_to == hi_from) {
2985         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
2986         return 0;
2987     }
2988
2989     for (n=0; n<handle_merge_func_used; n++)
2990         handle_merge_func_list[n](user, hi_to, hi_from);
2991
2992     /* Append "from" handle's nicks to "to" handle's nick list. */
2993     if (hi_to->nicks) {
2994         struct nick_info *last_ni;
2995         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
2996         last_ni->next = hi_from->nicks;
2997     }
2998     while (hi_from->nicks) {
2999         hi_from->nicks->owner = hi_to;
3000         hi_from->nicks = hi_from->nicks->next;
3001     }
3002
3003     /* Merge the hostmasks. */
3004     for (ii=0; ii<hi_from->masks->used; ii++) {
3005         char *mask = hi_from->masks->list[ii];
3006         for (jj=0; jj<hi_to->masks->used; jj++)
3007             if (match_ircglobs(hi_to->masks->list[jj], mask))
3008                 break;
3009         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
3010             string_list_append(hi_to->masks, strdup(mask));
3011     }
3012
3013     /* Merge the lists of authed users. */
3014     if (hi_to->users) {
3015         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
3016         last_user->next_authed = hi_from->users;
3017     } else {
3018         hi_to->users = hi_from->users;
3019     }
3020     /* Repoint the old "from" handle's users. */
3021     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
3022         last_user->handle_info = hi_to;
3023     }
3024     hi_from->users = NULL;
3025
3026     /* Merge channel userlists. */
3027     for (cList=hi_from->channels; cList; cList=cListNext) {
3028         struct userData *cList2;
3029         cListNext = cList->u_next;
3030         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
3031             if (cList->channel == cList2->channel)
3032                 break;
3033         if (cList2 && (cList2->access >= cList->access)) {
3034             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);
3035             /* keep cList2 in hi_to; remove cList from hi_from */
3036             del_channel_user(cList, 1);
3037         } else {
3038             if (cList2) {
3039                 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);
3040                 /* remove the lower-ranking cList2 from hi_to */
3041                 del_channel_user(cList2, 1);
3042             } else {
3043                 log_module(NS_LOG, LOG_INFO, "Merge: %s had no access in %s", hi_to->handle, cList->channel->channel->name);
3044             }
3045             /* cList needs to be moved from hi_from to hi_to */
3046             cList->handle = hi_to;
3047             /* Remove from linked list for hi_from */
3048             assert(!cList->u_prev);
3049             hi_from->channels = cList->u_next;
3050             if (cList->u_next)
3051                 cList->u_next->u_prev = cList->u_prev;
3052             /* Add to linked list for hi_to */
3053             cList->u_prev = NULL;
3054             cList->u_next = hi_to->channels;
3055             if (hi_to->channels)
3056                 hi_to->channels->u_prev = cList;
3057             hi_to->channels = cList;
3058         }
3059     }
3060
3061     /* Do they get an OpServ level promotion? */
3062     if (hi_from->opserv_level > hi_to->opserv_level)
3063         hi_to->opserv_level = hi_from->opserv_level;
3064
3065     /* What about last seen time? */
3066     if (hi_from->lastseen > hi_to->lastseen)
3067         hi_to->lastseen = hi_from->lastseen;
3068
3069     /* New karma is the sum of the two original karmas. */
3070     hi_to->karma += hi_from->karma;
3071
3072     /* Does a fakehost carry over?  (This intentionally doesn't set it
3073      * for users previously attached to hi_to.  They'll just have to
3074      * reconnect.)
3075      */
3076     if (hi_from->fakehost && !hi_to->fakehost)
3077         hi_to->fakehost = strdup(hi_from->fakehost);
3078
3079     /* Notify of success. */
3080     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
3081     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
3082     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
3083
3084     /* Unregister the "from" handle. */
3085     nickserv_unregister_handle(hi_from, NULL);
3086
3087     return 1;
3088 }
3089
3090 struct nickserv_discrim {
3091     unsigned long flags_on, flags_off;
3092     time_t min_registered, max_registered;
3093     time_t lastseen;
3094     unsigned int limit;
3095     int min_level, max_level;
3096     int min_karma, max_karma;
3097     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
3098     const char *nickmask;
3099     const char *hostmask;
3100     const char *fakehostmask;
3101     const char *handlemask;
3102     const char *emailmask;
3103 };
3104
3105 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
3106
3107 struct discrim_apply_info {
3108     struct nickserv_discrim *discrim;
3109     discrim_search_func func;
3110     struct userNode *source;
3111     unsigned int matched;
3112 };
3113
3114 static struct nickserv_discrim *
3115 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
3116 {
3117     unsigned int i;
3118     struct nickserv_discrim *discrim;
3119
3120     discrim = malloc(sizeof(*discrim));
3121     memset(discrim, 0, sizeof(*discrim));
3122     discrim->min_level = 0;
3123     discrim->max_level = INT_MAX;
3124     discrim->limit = 50;
3125     discrim->min_registered = 0;
3126     discrim->max_registered = INT_MAX;
3127     discrim->lastseen = LONG_MAX;
3128     discrim->min_karma = INT_MIN;
3129     discrim->max_karma = INT_MAX;
3130
3131     for (i=0; i<argc; i++) {
3132         if (i == argc - 1) {
3133             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3134             goto fail;
3135         }
3136         if (!irccasecmp(argv[i], "limit")) {
3137             discrim->limit = strtoul(argv[++i], NULL, 0);
3138         } else if (!irccasecmp(argv[i], "flags")) {
3139             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
3140         } else if (!irccasecmp(argv[i], "registered")) {
3141             const char *cmp = argv[++i];
3142             if (cmp[0] == '<') {
3143                 if (cmp[1] == '=') {
3144                     discrim->min_registered = now - ParseInterval(cmp+2);
3145                 } else {
3146                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
3147                 }
3148             } else if (cmp[0] == '=') {
3149                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
3150             } else if (cmp[0] == '>') {
3151                 if (cmp[1] == '=') {
3152                     discrim->max_registered = now - ParseInterval(cmp+2);
3153                 } else {
3154                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
3155                 }
3156             } else {
3157                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3158             }
3159         } else if (!irccasecmp(argv[i], "seen")) {
3160             discrim->lastseen = now - ParseInterval(argv[++i]);
3161         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
3162             discrim->nickmask = argv[++i];
3163         } else if (!irccasecmp(argv[i], "hostmask")) {
3164             i++;
3165             if (!irccasecmp(argv[i], "exact")) {
3166                 if (i == argc - 1) {
3167                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3168                     goto fail;
3169                 }
3170                 discrim->hostmask_type = EXACT;
3171             } else if (!irccasecmp(argv[i], "subset")) {
3172                 if (i == argc - 1) {
3173                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3174                     goto fail;
3175                 }
3176                 discrim->hostmask_type = SUBSET;
3177             } else if (!irccasecmp(argv[i], "superset")) {
3178                 if (i == argc - 1) {
3179                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3180                     goto fail;
3181                 }
3182                 discrim->hostmask_type = SUPERSET;
3183             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
3184                if (i == argc - 1) {
3185                    send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3186                    goto fail;
3187                }
3188                discrim->hostmask_type = LASTQUIT;
3189             } else {
3190                 i--;
3191                 discrim->hostmask_type = SUPERSET;
3192             }
3193             discrim->hostmask = argv[++i];
3194         } else if (!irccasecmp(argv[i], "fakehost")) {
3195             if (!irccasecmp(argv[++i], "*")) {
3196                 discrim->fakehostmask = 0;
3197             } else {
3198                 discrim->fakehostmask = argv[i];
3199             }
3200         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
3201             if (!irccasecmp(argv[++i], "*")) {
3202                 discrim->handlemask = 0;
3203             } else {
3204                 discrim->handlemask = argv[i];
3205             }
3206         } else if (!irccasecmp(argv[i], "email")) {
3207             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
3208                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
3209                 goto fail;
3210             } else if (!irccasecmp(argv[++i], "*")) {
3211                 discrim->emailmask = 0;
3212             } else {
3213                 discrim->emailmask = argv[i];
3214             }
3215         } else if (!irccasecmp(argv[i], "access")) {
3216             const char *cmp = argv[++i];
3217             if (cmp[0] == '<') {
3218                 if (discrim->min_level == 0) discrim->min_level = 1;
3219                 if (cmp[1] == '=') {
3220                     discrim->max_level = strtoul(cmp+2, NULL, 0);
3221                 } else {
3222                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
3223                 }
3224             } else if (cmp[0] == '=') {
3225                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
3226             } else if (cmp[0] == '>') {
3227                 if (cmp[1] == '=') {
3228                     discrim->min_level = strtoul(cmp+2, NULL, 0);
3229                 } else {
3230                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
3231                 }
3232             } else {
3233                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3234             }
3235         } else if (!irccasecmp(argv[i], "karma")) {
3236             const char *cmp = argv[++i];
3237             if (cmp[0] == '<') {
3238                 if (cmp[1] == '=') {
3239                     discrim->max_karma = strtoul(cmp+2, NULL, 0);
3240                 } else {
3241                     discrim->max_karma = strtoul(cmp+1, NULL, 0) - 1;
3242                 }
3243             } else if (cmp[0] == '=') {
3244                 discrim->min_karma = discrim->max_karma = strtoul(cmp+1, NULL, 0);
3245             } else if (cmp[0] == '>') {
3246                 if (cmp[1] == '=') {
3247                     discrim->min_karma = strtoul(cmp+2, NULL, 0);
3248                 } else {
3249                     discrim->min_karma = strtoul(cmp+1, NULL, 0) + 1;
3250                 }
3251             } else {
3252                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3253             }
3254         } else {
3255             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
3256             goto fail;
3257         }
3258     }
3259     return discrim;
3260   fail:
3261     free(discrim);
3262     return NULL;
3263 }
3264
3265 static int
3266 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
3267 {
3268     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
3269         || (discrim->flags_off & hi->flags)
3270         || (discrim->min_registered > hi->registered)
3271         || (discrim->max_registered < hi->registered)
3272         || (discrim->lastseen < (hi->users?now:hi->lastseen))
3273         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
3274         || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
3275         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
3276         || (discrim->min_level > hi->opserv_level)
3277         || (discrim->max_level < hi->opserv_level)
3278         || (discrim->min_karma > hi->karma)
3279         || (discrim->max_karma < hi->karma)
3280         ) {
3281         return 0;
3282     }
3283     if (discrim->hostmask) {
3284         unsigned int i;
3285         for (i=0; i<hi->masks->used; i++) {
3286             const char *mask = hi->masks->list[i];
3287             if ((discrim->hostmask_type == SUBSET)
3288                 && (match_ircglobs(discrim->hostmask, mask))) break;
3289             else if ((discrim->hostmask_type == EXACT)
3290                      && !irccasecmp(discrim->hostmask, mask)) break;
3291             else if ((discrim->hostmask_type == SUPERSET)
3292                      && (match_ircglobs(mask, discrim->hostmask))) break;
3293             else if ((discrim->hostmask_type == LASTQUIT)
3294                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
3295         }
3296         if (i==hi->masks->used) return 0;
3297     }
3298     if (discrim->nickmask) {
3299         struct nick_info *nick = hi->nicks;
3300         while (nick) {
3301             if (match_ircglob(nick->nick, discrim->nickmask)) break;
3302             nick = nick->next;
3303         }
3304         if (!nick) return 0;
3305     }
3306     return 1;
3307 }
3308
3309 static unsigned int
3310 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
3311 {
3312     dict_iterator_t it, next;
3313     unsigned int matched;
3314
3315     for (it = dict_first(nickserv_handle_dict), matched = 0;
3316          it && (matched < discrim->limit);
3317          it = next) {
3318         next = iter_next(it);
3319         if (nickserv_discrim_match(discrim, iter_data(it))) {
3320             dsf(source, iter_data(it));
3321             matched++;
3322         }
3323     }
3324     return matched;
3325 }
3326
3327 static void
3328 search_print_func(struct userNode *source, struct handle_info *match)
3329 {
3330     send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
3331 }
3332
3333 static void
3334 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
3335 {
3336 }
3337
3338 static void
3339 search_unregister_func (struct userNode *source, struct handle_info *match)
3340 {
3341     if (oper_has_access(source, nickserv, match->opserv_level, 0))
3342         nickserv_unregister_handle(match, source);
3343 }
3344
3345 static int
3346 nickserv_sort_accounts_by_access(const void *a, const void *b)
3347 {
3348     const struct handle_info *hi_a = *(const struct handle_info**)a;
3349     const struct handle_info *hi_b = *(const struct handle_info**)b;
3350     if (hi_a->opserv_level != hi_b->opserv_level)
3351         return hi_b->opserv_level - hi_a->opserv_level;
3352     return irccasecmp(hi_a->handle, hi_b->handle);
3353 }
3354
3355 void
3356 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
3357 {
3358     struct handle_info_list hil;
3359     struct helpfile_table tbl;
3360     unsigned int ii;
3361     dict_iterator_t it;
3362     const char **ary;
3363
3364     memset(&hil, 0, sizeof(hil));
3365     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3366         struct handle_info *hi = iter_data(it);
3367         if (hi->opserv_level)
3368             handle_info_list_append(&hil, hi);
3369     }
3370     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
3371     tbl.length = hil.used + 1;
3372     tbl.width = 2;
3373     tbl.flags = TABLE_NO_FREE;
3374     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
3375     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
3376     ary[0] = "Account";
3377     ary[1] = "Level";
3378     for (ii = 0; ii < hil.used; ) {
3379         ary = malloc(tbl.width * sizeof(ary[0]));
3380         ary[0] = hil.list[ii]->handle;
3381         ary[1] = strtab(hil.list[ii]->opserv_level);
3382         tbl.contents[++ii] = ary;
3383     }
3384     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3385     reply("MSG_MATCH_COUNT", hil.used);
3386     for (ii = 0; ii < hil.used; ii++)
3387         free(tbl.contents[ii]);
3388     free(tbl.contents);
3389     free(hil.list);
3390 }
3391
3392 static NICKSERV_FUNC(cmd_search)
3393 {
3394     struct nickserv_discrim *discrim;
3395     discrim_search_func action;
3396     struct svccmd *subcmd;
3397     unsigned int matches;
3398     char buf[MAXLEN];
3399
3400     NICKSERV_MIN_PARMS(3);
3401     sprintf(buf, "search %s", argv[1]);
3402     subcmd = dict_find(nickserv_service->commands, buf, NULL);
3403     if (!irccasecmp(argv[1], "print"))
3404         action = search_print_func;
3405     else if (!irccasecmp(argv[1], "count"))
3406         action = search_count_func;
3407     else if (!irccasecmp(argv[1], "unregister"))
3408         action = search_unregister_func;
3409     else {
3410         reply("NSMSG_INVALID_ACTION", argv[1]);
3411         return 0;
3412     }
3413
3414     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
3415         return 0;
3416
3417     discrim = nickserv_discrim_create(user, argc-2, argv+2);
3418     if (!discrim)
3419         return 0;
3420
3421     if (action == search_print_func)
3422         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
3423     else if (action == search_count_func)
3424         discrim->limit = INT_MAX;
3425
3426     matches = nickserv_discrim_search(discrim, action, user);
3427
3428     if (matches)
3429         reply("MSG_MATCH_COUNT", matches);
3430     else
3431         reply("MSG_NO_MATCHES");
3432
3433     free(discrim);
3434     return 0;
3435 }
3436
3437 static MODCMD_FUNC(cmd_checkpass)
3438 {
3439     struct handle_info *hi;
3440
3441     NICKSERV_MIN_PARMS(3);
3442     if (!(hi = get_handle_info(argv[1]))) {
3443         reply("MSG_HANDLE_UNKNOWN", argv[1]);
3444         return 0;
3445     }
3446     if (checkpass(argv[2], hi->passwd))
3447         reply("CHECKPASS_YES");
3448     else
3449         reply("CHECKPASS_NO");
3450     argv[2] = "****";
3451     return 1;
3452 }
3453
3454 static void
3455 nickserv_db_read_handle(const char *handle, dict_t obj)
3456 {
3457     const char *str;
3458     struct string_list *masks, *slist;
3459     struct handle_info *hi;
3460     struct userNode *authed_users;
3461     struct userData *channels;
3462     unsigned long int id;
3463     unsigned int ii;
3464     dict_t subdb;
3465
3466     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
3467     id = str ? strtoul(str, NULL, 0) : 0;
3468     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
3469     if (!str) {
3470         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
3471         return;
3472     }
3473     if ((hi = get_handle_info(handle))) {
3474         authed_users = hi->users;
3475         channels = hi->channels;
3476         hi->users = NULL;
3477         hi->channels = NULL;
3478         dict_remove(nickserv_handle_dict, hi->handle);
3479     } else {
3480         authed_users = NULL;
3481         channels = NULL;
3482     }
3483     hi = register_handle(handle, str, id);
3484     if (authed_users) {
3485         hi->users = authed_users;
3486         while (authed_users) {
3487             authed_users->handle_info = hi;
3488             authed_users = authed_users->next_authed;
3489         }
3490     }
3491     hi->channels = channels;
3492     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
3493     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
3494     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
3495     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
3496     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
3497     hi->language = language_find(str ? str : "C");
3498     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
3499     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
3500     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
3501     if (str)
3502         hi->infoline = strdup(str);
3503     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
3504     hi->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
3505     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
3506     hi->lastseen = str ? (time_t)strtoul(str, NULL, 0) : hi->registered;
3507     str = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
3508     hi->karma = str ? strtoul(str, NULL, 0) : 0;
3509     /* We want to read the nicks even if disable_nicks is set.  This is so
3510      * that we don't lose the nick data entirely. */
3511     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
3512     if (slist) {
3513         for (ii=0; ii<slist->used; ii++)
3514             register_nick(slist->list[ii], hi);
3515     }
3516     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
3517     if (str) {
3518         for (ii=0; str[ii]; ii++)
3519             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
3520     }
3521     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
3522     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
3523     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
3524     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
3525     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
3526     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
3527     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
3528     if (!str)
3529         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
3530     if (str)
3531         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
3532     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
3533     if (str)
3534         nickserv_set_email_addr(hi, str);
3535     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
3536     if (str)
3537         hi->epithet = strdup(str);
3538     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
3539     if (str)
3540         hi->fakehost = strdup(str);
3541     /* Read the "cookie" sub-database (if it exists). */
3542     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
3543     if (subdb) {
3544         const char *data, *type, *expires, *cookie_str;
3545         struct handle_cookie *cookie;
3546
3547         cookie = calloc(1, sizeof(*cookie));
3548         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
3549         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
3550         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
3551         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
3552         if (!type || !expires || !cookie_str) {
3553             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
3554             goto cookie_out;
3555         }
3556         if (!irccasecmp(type, KEY_ACTIVATION))
3557             cookie->type = ACTIVATION;
3558         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
3559             cookie->type = PASSWORD_CHANGE;
3560         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
3561             cookie->type = EMAIL_CHANGE;
3562         else if (!irccasecmp(type, KEY_ALLOWAUTH))
3563             cookie->type = ALLOWAUTH;
3564         else {
3565             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
3566             goto cookie_out;
3567         }
3568         cookie->expires = strtoul(expires, NULL, 0);
3569         if (cookie->expires < now)
3570             goto cookie_out;
3571         if (data)
3572             cookie->data = strdup(data);
3573         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
3574         cookie->hi = hi;
3575       cookie_out:
3576         if (cookie->hi)
3577             nickserv_bake_cookie(cookie);
3578         else
3579             nickserv_free_cookie(cookie);
3580     }
3581     /* Read the "notes" sub-database (if it exists). */
3582     subdb = database_get_data(obj, KEY_NOTES, RECDB_OBJECT);
3583     if (subdb) {
3584         dict_iterator_t it;
3585         struct handle_note *last_note;
3586         struct handle_note *note;
3587
3588         last_note = NULL;
3589         for (it = dict_first(subdb); it; it = iter_next(it)) {
3590             const char *expires;
3591             const char *setter;
3592             const char *text;
3593             const char *set;
3594             const char *id;
3595             dict_t notedb;
3596
3597             id = iter_key(it);
3598             notedb = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
3599             if (!notedb) {
3600                 log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", id, hi->handle);
3601                 continue;
3602             }
3603             expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
3604             setter = database_get_data(notedb, KEY_NOTE_SETTER, RECDB_QSTRING);
3605             text = database_get_data(notedb, KEY_NOTE_NOTE, RECDB_QSTRING);
3606             set = database_get_data(notedb, KEY_NOTE_SET, RECDB_QSTRING);
3607             if (!setter || !text || !set) {
3608                 log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", id, hi->handle);
3609                 continue;
3610             }
3611             note = calloc(1, sizeof(*note) + strlen(text));
3612             note->next = NULL;
3613             note->expires = expires ? strtoul(expires, NULL, 10) : 0;
3614             note->set = strtoul(set, NULL, 10);
3615             note->id = strtoul(id, NULL, 10);
3616             safestrncpy(note->setter, setter, sizeof(note->setter));
3617             strcpy(note->note, text);
3618             if (last_note)
3619                 last_note->next = note;
3620             else
3621                 hi->notes = note;
3622             last_note = note;
3623         }
3624     }
3625 }
3626
3627 static int
3628 nickserv_saxdb_read(dict_t db) {
3629     dict_iterator_t it;
3630     struct record_data *rd;
3631
3632     for (it=dict_first(db); it; it=iter_next(it)) {
3633         rd = iter_data(it);
3634         nickserv_db_read_handle(iter_key(it), rd->d.object);
3635     }
3636     return 0;
3637 }
3638
3639 static NICKSERV_FUNC(cmd_mergedb)
3640 {
3641     struct timeval start, stop;
3642     dict_t db;
3643
3644     NICKSERV_MIN_PARMS(2);
3645     gettimeofday(&start, NULL);
3646     if (!(db = parse_database(argv[1]))) {
3647         reply("NSMSG_DB_UNREADABLE", argv[1]);
3648         return 0;
3649     }
3650     nickserv_saxdb_read(db);
3651     free_database(db);
3652     gettimeofday(&stop, NULL);
3653     stop.tv_sec -= start.tv_sec;
3654     stop.tv_usec -= start.tv_usec;
3655     if (stop.tv_usec < 0) {
3656         stop.tv_sec -= 1;
3657         stop.tv_usec += 1000000;
3658     }
3659     reply("NSMSG_DB_MERGED", argv[1], stop.tv_sec, stop.tv_usec/1000);
3660     return 1;
3661 }
3662
3663 static void
3664 expire_handles(UNUSED_ARG(void *data))
3665 {
3666     dict_iterator_t it, next;
3667     time_t expiry;
3668     struct handle_info *hi;
3669
3670     for (it=dict_first(nickserv_handle_dict); it; it=next) {
3671         next = iter_next(it);
3672         hi = iter_data(it);
3673         if ((hi->opserv_level > 0)
3674             || hi->users
3675             || HANDLE_FLAGGED(hi, FROZEN)
3676             || HANDLE_FLAGGED(hi, NODELETE)) {
3677             continue;
3678         }
3679         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
3680         if ((now - hi->lastseen) > expiry) {
3681             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
3682             nickserv_unregister_handle(hi, NULL);
3683         }
3684     }
3685
3686     if (nickserv_conf.handle_expire_frequency)
3687         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3688 }
3689
3690 static void
3691 nickserv_load_dict(const char *fname)
3692 {
3693     FILE *file;
3694     char line[128];
3695     if (!(file = fopen(fname, "r"))) {
3696         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
3697         return;
3698     }
3699     while (!feof(file)) {
3700         fgets(line, sizeof(line), file);
3701         if (!line[0])
3702             continue;
3703         if (line[strlen(line)-1] == '\n')
3704             line[strlen(line)-1] = 0;
3705         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
3706     }
3707     fclose(file);
3708     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
3709 }
3710
3711 static enum reclaim_action
3712 reclaim_action_from_string(const char *str) {
3713     if (!str)
3714         return RECLAIM_NONE;
3715     else if (!irccasecmp(str, "warn"))
3716         return RECLAIM_WARN;
3717     else if (!irccasecmp(str, "svsnick"))
3718         return RECLAIM_SVSNICK;
3719     else if (!irccasecmp(str, "kill"))
3720         return RECLAIM_KILL;
3721     else
3722         return RECLAIM_NONE;
3723 }
3724
3725 static void
3726 nickserv_conf_read(void)
3727 {
3728     dict_t conf_node, child;
3729     const char *str;
3730     dict_iterator_t it;
3731
3732     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
3733         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
3734         return;
3735     }
3736     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
3737     if (!str)
3738         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
3739     if (nickserv_conf.valid_handle_regex_set)
3740         regfree(&nickserv_conf.valid_handle_regex);
3741     if (str) {
3742         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3743         nickserv_conf.valid_handle_regex_set = !err;
3744         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
3745     } else {
3746         nickserv_conf.valid_handle_regex_set = 0;
3747     }
3748     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
3749     if (nickserv_conf.valid_nick_regex_set)
3750         regfree(&nickserv_conf.valid_nick_regex);
3751     if (str) {
3752         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3753         nickserv_conf.valid_nick_regex_set = !err;
3754         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
3755     } else {
3756         nickserv_conf.valid_nick_regex_set = 0;
3757     }
3758     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
3759     if (!str)
3760         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
3761     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
3762     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
3763     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
3764     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
3765     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
3766     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
3767     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
3768     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
3769     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
3770     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
3771     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
3772     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
3773     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
3774     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
3775     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3776     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
3777     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
3778     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
3779     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
3780     str = database_get_data(conf_node, KEY_SET_TITLE_LEVEL, RECDB_QSTRING);
3781     nickserv_conf.set_title_level = str ? strtoul(str, NULL, 0) : 900;
3782     str = database_get_data(conf_node, KEY_SET_FAKEHOST_LEVEL, RECDB_QSTRING);
3783     nickserv_conf.set_fakehost_level = str ? strtoul(str, NULL, 0) : 1000;
3784     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
3785     if (!str)
3786         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
3787     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
3788     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3789     if (!str)
3790         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3791     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
3792     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3793     if (!str)
3794         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3795     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
3796     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
3797     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
3798     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
3799     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
3800     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
3801     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
3802     str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
3803     nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
3804     str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
3805     if (!str)
3806         str = "ShgsfnHbu";
3807     nickserv_conf.ounregister_flags = 0;
3808     while(*str) {
3809         unsigned int pos = handle_inverse_flags[(unsigned char)*str];
3810         str++;
3811         if(pos)
3812             nickserv_conf.ounregister_flags |= 1 << (pos - 1);
3813     }
3814     if (!nickserv_conf.disable_nicks) {
3815         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
3816         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3817         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
3818         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
3819         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
3820         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3821         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
3822         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
3823     }
3824     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
3825     for (it=dict_first(child); it; it=iter_next(it)) {
3826         const char *key = iter_key(it), *value;
3827         unsigned char flag;
3828         int pos;
3829
3830         if (!strncasecmp(key, "uc_", 3))
3831             flag = toupper(key[3]);
3832         else if (!strncasecmp(key, "lc_", 3))
3833             flag = tolower(key[3]);
3834         else
3835             flag = key[0];
3836
3837         if ((pos = handle_inverse_flags[flag])) {
3838             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
3839             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
3840         }
3841     }
3842     if (nickserv_conf.weak_password_dict)
3843         dict_delete(nickserv_conf.weak_password_dict);
3844     nickserv_conf.weak_password_dict = dict_new();
3845     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
3846     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
3847     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
3848     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
3849     if (str)
3850         nickserv_load_dict(str);
3851     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
3852     if (nickserv && str)
3853         NickChange(nickserv, str, 0);
3854     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
3855     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
3856     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
3857     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
3858     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
3859     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
3860     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
3861     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
3862     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
3863     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
3864     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
3865     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
3866     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
3867     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
3868     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
3869     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
3870     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
3871     nickserv_conf.titlehost_suffix = str ? str : "example.net";
3872     str = conf_get_data("server/network", RECDB_QSTRING);
3873     nickserv_conf.network_name = str ? str : "some IRC network";
3874     if (!nickserv_conf.auth_policer_params) {
3875         nickserv_conf.auth_policer_params = policer_params_new();
3876         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
3877         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
3878     }
3879     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
3880     for (it=dict_first(child); it; it=iter_next(it))
3881         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
3882 }
3883
3884 static void
3885 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
3886     const char *msg;
3887     char newnick[NICKLEN+1];
3888
3889     assert(user);
3890     assert(ni);
3891     switch (action) {
3892     case RECLAIM_NONE:
3893         /* do nothing */
3894         break;
3895     case RECLAIM_WARN:
3896         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3897         break;
3898     case RECLAIM_SVSNICK:
3899         do {
3900             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
3901         } while (GetUserH(newnick));
3902         irc_svsnick(nickserv, user, newnick);
3903         break;
3904     case RECLAIM_KILL:
3905         msg = user_find_message(user, "NSMSG_RECLAIM_KILL");
3906         DelUser(user, nickserv, 1, msg);
3907         break;
3908     }
3909 }
3910
3911 static void
3912 nickserv_reclaim_p(void *data) {
3913     struct userNode *user = data;
3914     struct nick_info *ni = get_nick_info(user->nick);
3915     if (ni)
3916         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3917 }
3918
3919 static int
3920 check_user_nick(struct userNode *user) {
3921     struct nick_info *ni;
3922     user->modes &= ~FLAGS_REGNICK;
3923     if (!(ni = get_nick_info(user->nick)))
3924         return 0;
3925     if (user->handle_info == ni->owner) {
3926         user->modes |= FLAGS_REGNICK;
3927         irc_regnick(user);
3928         return 0;
3929     }
3930     if (nickserv_conf.warn_nick_owned)
3931         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3932     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
3933         return 0;
3934     if (nickserv_conf.auto_reclaim_delay)
3935         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
3936     else
3937         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3938     return 0;
3939 }
3940
3941 int
3942 handle_new_user(struct userNode *user)
3943 {
3944     return check_user_nick(user);
3945 }
3946
3947 void
3948 handle_account(struct userNode *user, const char *stamp)
3949 {
3950     struct handle_info *hi;
3951
3952 #ifdef WITH_PROTOCOL_P10
3953     hi = dict_find(nickserv_handle_dict, stamp, NULL);
3954 #else
3955     hi = dict_find(nickserv_id_dict, stamp, NULL);
3956 #endif
3957
3958     if (hi) {
3959         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
3960             return;
3961         }
3962         set_user_handle_info(user, hi, 0);
3963     } else {
3964         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s.", user->nick, stamp);
3965     }
3966 }
3967
3968 void
3969 handle_nick_change(struct userNode *user, const char *old_nick)
3970 {
3971     struct handle_info *hi;
3972
3973     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
3974         dict_remove(nickserv_allow_auth_dict, old_nick);
3975         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
3976     }
3977     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
3978     check_user_nick(user);
3979 }
3980
3981 void
3982 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
3983 {
3984     dict_remove(nickserv_allow_auth_dict, user->nick);
3985     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
3986     set_user_handle_info(user, NULL, 0);
3987 }
3988
3989 static struct modcmd *
3990 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
3991 {
3992     if (min_level > 0) {
3993         char buf[16];
3994         sprintf(buf, "%u", min_level);
3995         if (must_be_qualified) {
3996             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
3997         } else {
3998             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
3999         }
4000     } else if (min_level == 0) {
4001         if (must_be_qualified) {
4002             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4003         } else {
4004             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4005         }
4006     } else {
4007         if (must_be_qualified) {
4008             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
4009         } else {
4010             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
4011         }
4012     }
4013 }
4014
4015 static void
4016 nickserv_db_cleanup(void)
4017 {
4018     unreg_del_user_func(nickserv_remove_user);
4019     userList_clean(&curr_helpers);
4020     policer_params_delete(nickserv_conf.auth_policer_params);
4021     dict_delete(nickserv_handle_dict);
4022     dict_delete(nickserv_nick_dict);
4023     dict_delete(nickserv_opt_dict);
4024     dict_delete(nickserv_allow_auth_dict);
4025     dict_delete(nickserv_email_dict);
4026     dict_delete(nickserv_id_dict);
4027     dict_delete(nickserv_conf.weak_password_dict);
4028     free(auth_func_list);
4029     free(unreg_func_list);
4030     free(rf_list);
4031     free(allowauth_func_list);
4032     free(handle_merge_func_list);
4033     free(failpw_func_list);
4034     if (nickserv_conf.valid_handle_regex_set)
4035         regfree(&nickserv_conf.valid_handle_regex);
4036     if (nickserv_conf.valid_nick_regex_set)
4037         regfree(&nickserv_conf.valid_nick_regex);
4038 }
4039
4040 void
4041 init_nickserv(const char *nick)
4042 {
4043     unsigned int i;
4044     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
4045     reg_new_user_func(handle_new_user);
4046     reg_nick_change_func(handle_nick_change);
4047     reg_del_user_func(nickserv_remove_user);
4048     reg_account_func(handle_account);
4049
4050     /* set up handle_inverse_flags */
4051     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
4052     for (i=0; handle_flags[i]; i++) {
4053         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
4054         flag_access_levels[i] = 0;
4055     }
4056
4057     conf_register_reload(nickserv_conf_read);
4058     nickserv_opt_dict = dict_new();
4059     nickserv_email_dict = dict_new();
4060     dict_set_free_keys(nickserv_email_dict, free);
4061     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
4062
4063     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
4064     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
4065     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
4066     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
4067     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
4068     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
4069     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
4070     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
4071     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
4072     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
4073     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
4074     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
4075     nickserv_define_func("SET", cmd_set, -1, 1, 0);
4076     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
4077     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
4078     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
4079     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
4080     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
4081     nickserv_define_func("MERGE", cmd_merge, 750, 1, 0);
4082     nickserv_define_func("ADDNOTE", cmd_addnote, 0, 1, 0);
4083     nickserv_define_func("DELNOTE", cmd_delnote, 0, 1, 0);
4084     nickserv_define_func("NOTES", cmd_notes, 0, 1, 0);
4085     if (!nickserv_conf.disable_nicks) {
4086         /* nick management commands */
4087         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
4088         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
4089         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
4090         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
4091         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
4092         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
4093     }
4094     if (nickserv_conf.email_enabled) {
4095         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
4096         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
4097         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
4098         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
4099         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
4100     }
4101     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
4102     /* miscellaneous commands */
4103     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
4104     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
4105     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
4106     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
4107     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
4108     /* other options */
4109     dict_insert(nickserv_opt_dict, "INFO", opt_info);
4110     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
4111     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
4112     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
4113     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
4114     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
4115     dict_insert(nickserv_opt_dict, "PASS", opt_password);
4116     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
4117     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
4118     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
4119     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
4120     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
4121     if (nickserv_conf.titlehost_suffix) {
4122         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
4123         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
4124     }
4125     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
4126     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
4127     dict_insert(nickserv_opt_dict, "KARMA", opt_karma);
4128     nickserv_define_func("OSET KARMA", NULL, 0, 1, 0);
4129
4130     nickserv_handle_dict = dict_new();
4131     dict_set_free_keys(nickserv_handle_dict, free);
4132     dict_set_free_data(nickserv_handle_dict, free_handle_info);
4133
4134     nickserv_id_dict = dict_new();
4135     dict_set_free_keys(nickserv_id_dict, free);
4136
4137     nickserv_nick_dict = dict_new();
4138     dict_set_free_data(nickserv_nick_dict, free);
4139
4140     nickserv_allow_auth_dict = dict_new();
4141
4142     userList_init(&curr_helpers);
4143
4144     if (nick) {
4145         const char *modes = conf_get_data("services/nickserv/modes", RECDB_QSTRING);
4146         nickserv = AddLocalUser(nick, nick, NULL, "Nick Services", modes);
4147         nickserv_service = service_register(nickserv);
4148     }
4149     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
4150     reg_exit_func(nickserv_db_cleanup);
4151     if(nickserv_conf.handle_expire_frequency)
4152         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
4153     message_register_table(msgtab);
4154 }