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