i18n fixes
[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%s$b.  Please use the $bauthcookie$b command to grant yourself access.  (/msg $S authcookie %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 = user_find_message(user, "NSEMAIL_ACTIVATION_SUBJECT");
978         snprintf(subject, sizeof(subject), fmt, netname);
979         fmt = user_find_message(user, "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 = user_find_message(user, "NSEMAIL_PASSWORD_CHANGE_SUBJECT");
986         snprintf(subject, sizeof(subject), fmt, netname);
987         fmt = user_find_message(user, "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 = user_find_message(user, "NSEMAIL_EMAIL_CHANGE_SUBJECT");
996             snprintf(subject, sizeof(subject), fmt, netname);
997             fmt = user_find_message(user, "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 = user_find_message(user, "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 = user_find_message(user, "NSEMAIL_EMAIL_VERIFY_SUBJECT");
1005             snprintf(subject, sizeof(subject), fmt, netname);
1006             fmt = user_find_message(user, "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 = user_find_message(user, "NSEMAIL_ALLOWAUTH_SUBJECT");
1015         snprintf(subject, sizeof(subject), fmt, netname);
1016         fmt = user_find_message(user, "NSEMAIL_ALLOWAUTH_BODY");
1017         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
1018         break;
1019     default:
1020         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d in nickserv_make_cookie.", cookie->type);
1021         break;
1022     }
1023     if (subject[0])
1024         sendmail(nickserv, hi, subject, body, first_time);
1025     nickserv_bake_cookie(cookie);
1026 }
1027
1028 static void
1029 nickserv_eat_cookie(struct handle_cookie *cookie)
1030 {
1031     cookie->hi->cookie = NULL;
1032     timeq_del(cookie->expires, nickserv_free_cookie, cookie, 0);
1033     nickserv_free_cookie(cookie);
1034 }
1035
1036 static void
1037 nickserv_free_email_addr(void *data)
1038 {
1039     handle_info_list_clean(data);
1040     free(data);
1041 }
1042
1043 static void
1044 nickserv_set_email_addr(struct handle_info *hi, const char *new_email_addr)
1045 {
1046     struct handle_info_list *hil;
1047     /* Remove from old handle_info_list ... */
1048     if (hi->email_addr && (hil = dict_find(nickserv_email_dict, hi->email_addr, 0))) {
1049         handle_info_list_remove(hil, hi);
1050         if (!hil->used) dict_remove(nickserv_email_dict, hil->tag);
1051         hi->email_addr = NULL;
1052     }
1053     /* Add to the new list.. */
1054     if (new_email_addr) {
1055         if (!(hil = dict_find(nickserv_email_dict, new_email_addr, 0))) {
1056             hil = calloc(1, sizeof(*hil));
1057             hil->tag = strdup(new_email_addr);
1058             handle_info_list_init(hil);
1059             dict_insert(nickserv_email_dict, hil->tag, hil);
1060         }
1061         handle_info_list_append(hil, hi);
1062         hi->email_addr = hil->tag;
1063     }
1064 }
1065
1066 static NICKSERV_FUNC(cmd_register)
1067 {
1068     struct handle_info *hi;
1069     const char *email_addr, *password;
1070     int no_auth;
1071
1072     if (!IsOper(user) && !dict_size(nickserv_handle_dict)) {
1073         /* Require the first handle registered to belong to someone +o. */
1074         reply("NSMSG_REQUIRE_OPER");
1075         return 0;
1076     }
1077
1078     if (user->handle_info) {
1079         reply("NSMSG_USE_RENAME", user->handle_info->handle);
1080         return 0;
1081     }
1082
1083     if (IsStamped(user)) {
1084         /* Unauthenticated users might still have been stamped
1085            previously and could therefore have a hidden host;
1086            do not allow them to register a new account. */
1087         reply("NSMSG_STAMPED_REGISTER");
1088         return 0;
1089     }
1090
1091     NICKSERV_MIN_PARMS((unsigned)3 + nickserv_conf.email_required);
1092
1093     if (!is_valid_handle(argv[1])) {
1094         reply("NSMSG_BAD_HANDLE", argv[1]);
1095         return 0;
1096     }
1097
1098     if ((argc >= 4) && nickserv_conf.email_enabled) {
1099         struct handle_info_list *hil;
1100         const char *str;
1101
1102         /* Remember email address. */
1103         email_addr = argv[3];
1104
1105         /* Check that the email address looks valid.. */
1106         if (!is_valid_email_addr(email_addr)) {
1107             reply("NSMSG_BAD_EMAIL_ADDR");
1108             return 0;
1109         }
1110
1111         /* .. and that we are allowed to send to it. */
1112         if ((str = sendmail_prohibited_address(email_addr))) {
1113             reply("NSMSG_EMAIL_PROHIBITED", email_addr, str);
1114             return 0;
1115         }
1116
1117         /* If we do email verify, make sure we don't spam the address. */
1118         if ((hil = dict_find(nickserv_email_dict, email_addr, NULL))) {
1119             unsigned int nn;
1120             for (nn=0; nn<hil->used; nn++) {
1121                 if (hil->list[nn]->cookie) {
1122                     reply("NSMSG_EMAIL_UNACTIVATED");
1123                     return 0;
1124                 }
1125             }
1126             if (hil->used >= nickserv_conf.handles_per_email) {
1127                 reply("NSMSG_EMAIL_OVERUSED");
1128                 return 0;
1129             }
1130         }
1131
1132         no_auth = 1;
1133     } else {
1134         email_addr = 0;
1135         no_auth = 0;
1136     }
1137
1138     password = argv[2];
1139     argv[2] = "****";
1140     if (!(hi = nickserv_register(user, user, argv[1], password, no_auth)))
1141         return 0;
1142     /* Add any masks they should get. */
1143     if (nickserv_conf.default_hostmask) {
1144         string_list_append(hi->masks, strdup("*@*"));
1145     } else {
1146         string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1147         if (user->ip.s_addr && user->hostname[strspn(user->hostname, "0123456789.")])
1148             string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1149     }
1150
1151     /* If they're the first to register, give them level 1000. */
1152     if (dict_size(nickserv_handle_dict) == 1) {
1153         hi->opserv_level = 1000;
1154         reply("NSMSG_ROOT_HANDLE", argv[1]);
1155     }
1156
1157     /* Set their email address. */
1158     if (email_addr)
1159         nickserv_set_email_addr(hi, email_addr);
1160
1161     /* If they need to do email verification, tell them. */
1162     if (no_auth)
1163         nickserv_make_cookie(user, hi, ACTIVATION, hi->passwd);
1164
1165     return 1;
1166 }
1167
1168 static NICKSERV_FUNC(cmd_oregister)
1169 {
1170     char *mask;
1171     struct userNode *settee;
1172     struct handle_info *hi;
1173
1174     NICKSERV_MIN_PARMS(4);
1175
1176     if (!is_valid_handle(argv[1])) {
1177         reply("NSMSG_BAD_HANDLE", argv[1]);
1178         return 0;
1179     }
1180
1181     if (strchr(argv[3], '@')) {
1182         mask = canonicalize_hostmask(strdup(argv[3]));
1183         if (argc > 4) {
1184             settee = GetUserH(argv[4]);
1185             if (!settee) {
1186                 reply("MSG_NICK_UNKNOWN", argv[4]);
1187                 free(mask);
1188                 return 0;
1189             }
1190         } else {
1191             settee = NULL;
1192         }
1193     } else if ((settee = GetUserH(argv[3]))) {
1194         mask = generate_hostmask(settee, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
1195     } else {
1196         reply("NSMSG_REGISTER_BAD_NICKMASK", argv[3]);
1197         return 0;
1198     }
1199     if (settee && settee->handle_info) {
1200         reply("NSMSG_USER_PREV_AUTH", settee->nick);
1201         free(mask);
1202         return 0;
1203     }
1204     if (!(hi = nickserv_register(user, settee, argv[1], argv[2], 0))) {
1205         free(mask);
1206         return 0;
1207     }
1208     string_list_append(hi->masks, mask);
1209     return 1;
1210 }
1211
1212 static NICKSERV_FUNC(cmd_handleinfo)
1213 {
1214     char buff[400];
1215     unsigned int i, pos=0, herelen;
1216     struct userNode *target, *next_un;
1217     struct handle_info *hi;
1218     const char *nsmsg_none;
1219
1220     if (argc < 2) {
1221         if (!(hi = user->handle_info)) {
1222             reply("NSMSG_MUST_AUTH");
1223             return 0;
1224         }
1225     } else if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
1226         return 0;
1227     }
1228
1229     nsmsg_none = handle_find_message(hi, "MSG_NONE");
1230     reply("NSMSG_HANDLEINFO_ON", hi->handle);
1231 #ifdef WITH_PROTOCOL_BAHAMUT
1232     reply("NSMSG_HANDLEINFO_ID", hi->id);
1233 #endif
1234     reply("NSMSG_HANDLEINFO_REGGED", ctime(&hi->registered));
1235
1236     if (!hi->users) {
1237         intervalString(buff, now - hi->lastseen);
1238         reply("NSMSG_HANDLEINFO_LASTSEEN", buff);
1239     } else {
1240         reply("NSMSG_HANDLEINFO_LASTSEEN_NOW");
1241     }
1242
1243     reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none));
1244     if (HANDLE_FLAGGED(hi, FROZEN))
1245         reply("NSMSG_HANDLEINFO_VACATION");
1246
1247     if (oper_has_access(user, cmd->parent->bot, 0, 1)) {
1248         struct do_not_register *dnr;
1249         if ((dnr = chanserv_is_dnr(NULL, hi)))
1250             reply("NSMSG_HANDLEINFO_DNR", dnr->setter, dnr->reason);
1251         if (!oper_outranks(user, hi))
1252             return 1;
1253     } else if (hi != user->handle_info)
1254         return 1;
1255
1256     if (nickserv_conf.email_enabled)
1257         reply("NSMSG_HANDLEINFO_EMAIL_ADDR", visible_email_addr(user, hi));
1258
1259     if (hi->cookie) {
1260         const char *type;
1261         switch (hi->cookie->type) {
1262         case ACTIVATION: type = "NSMSG_HANDLEINFO_COOKIE_ACTIVATION"; break;
1263         case PASSWORD_CHANGE: type = "NSMSG_HANDLEINFO_COOKIE_PASSWORD"; break;
1264         case EMAIL_CHANGE: type = "NSMSG_HANDLEINFO_COOKIE_EMAIL"; break;
1265         case ALLOWAUTH: type = "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH"; break;
1266         default: type = "NSMSG_HANDLEINFO_COOKIE_UNKNOWN"; break;
1267         }
1268         reply(type);
1269     }
1270
1271     if (hi->flags) {
1272         unsigned long flen = 1;
1273         char flags[34]; /* 32 bits possible plus '+' and '\0' */
1274         flags[0] = '+';
1275         for (i=0, flen=1; handle_flags[i]; i++)
1276             if (hi->flags & 1 << i)
1277                 flags[flen++] = handle_flags[i];
1278         flags[flen] = 0;
1279         reply("NSMSG_HANDLEINFO_FLAGS", flags);
1280     } else {
1281         reply("NSMSG_HANDLEINFO_FLAGS", nsmsg_none);
1282     }
1283
1284     if (HANDLE_FLAGGED(hi, SUPPORT_HELPER)
1285         || HANDLE_FLAGGED(hi, NETWORK_HELPER)
1286         || (hi->opserv_level > 0)) {
1287         reply("NSMSG_HANDLEINFO_EPITHET", (hi->epithet ? hi->epithet : nsmsg_none));
1288     }
1289
1290     if (hi->last_quit_host[0])
1291         reply("NSMSG_HANDLEINFO_LAST_HOST", hi->last_quit_host);
1292     else
1293         reply("NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN");
1294
1295     if (nickserv_conf.disable_nicks) {
1296         /* nicks disabled; don't show anything about registered nicks */
1297     } else if (hi->nicks) {
1298         struct nick_info *ni, *next_ni;
1299         for (ni = hi->nicks; ni; ni = next_ni) {
1300             herelen = strlen(ni->nick);
1301             if (pos + herelen + 1 > ArrayLength(buff)) {
1302                 next_ni = ni;
1303                 goto print_nicks_buff;
1304             } else {
1305                 next_ni = ni->next;
1306             }
1307             memcpy(buff+pos, ni->nick, herelen);
1308             pos += herelen; buff[pos++] = ' ';
1309             if (!next_ni) {
1310               print_nicks_buff:
1311                 buff[pos-1] = 0;
1312                 reply("NSMSG_HANDLEINFO_NICKS", buff);
1313                 pos = 0;
1314             }
1315         }
1316     } else {
1317         reply("NSMSG_HANDLEINFO_NICKS", nsmsg_none);
1318     }
1319
1320     if (hi->masks->used) {
1321         for (i=0; i < hi->masks->used; i++) {
1322             herelen = strlen(hi->masks->list[i]);
1323             if (pos + herelen + 1 > ArrayLength(buff)) {
1324                 i--;
1325                 goto print_mask_buff;
1326             }
1327             memcpy(buff+pos, hi->masks->list[i], herelen);
1328             pos += herelen; buff[pos++] = ' ';
1329             if (i+1 == hi->masks->used) {
1330               print_mask_buff:
1331                 buff[pos-1] = 0;
1332                 reply("NSMSG_HANDLEINFO_MASKS", buff);
1333                 pos = 0;
1334             }
1335         }
1336     } else {
1337         reply("NSMSG_HANDLEINFO_MASKS", nsmsg_none);
1338     }
1339
1340     if (hi->channels) {
1341         struct userData *channel, *next;
1342         char *name;
1343
1344         for (channel = hi->channels; channel; channel = next) {
1345             next = channel->u_next;
1346             name = channel->channel->channel->name;
1347             herelen = strlen(name);
1348             if (pos + herelen + 7 > ArrayLength(buff)) {
1349                 next = channel;
1350                 goto print_chans_buff;
1351             }
1352             if (IsUserSuspended(channel))
1353                 buff[pos++] = '-';
1354             pos += sprintf(buff+pos, "%d:%s ", channel->access, name);
1355             if (next == NULL) {
1356               print_chans_buff:
1357                 buff[pos-1] = 0;
1358                 reply("NSMSG_HANDLEINFO_CHANNELS", buff);
1359                 pos = 0;
1360             }
1361         }
1362     } else {
1363         reply("NSMSG_HANDLEINFO_CHANNELS", nsmsg_none);
1364     }
1365
1366     for (target = hi->users; target; target = next_un) {
1367         herelen = strlen(target->nick);
1368         if (pos + herelen + 1 > ArrayLength(buff)) {
1369             next_un = target;
1370             goto print_cnick_buff;
1371         } else {
1372             next_un = target->next_authed;
1373         }
1374         memcpy(buff+pos, target->nick, herelen);
1375         pos += herelen; buff[pos++] = ' ';
1376         if (!next_un) {
1377           print_cnick_buff:
1378             buff[pos-1] = 0;
1379             reply("NSMSG_HANDLEINFO_CURRENT", buff);
1380             pos = 0;
1381         }
1382     }
1383
1384     return 1;
1385 }
1386
1387 static NICKSERV_FUNC(cmd_userinfo)
1388 {
1389     struct userNode *target;
1390
1391     NICKSERV_MIN_PARMS(2);
1392     if (!(target = GetUserH(argv[1]))) {
1393         reply("MSG_NICK_UNKNOWN", argv[1]);
1394         return 0;
1395     }
1396     if (target->handle_info)
1397         reply("NSMSG_USERINFO_AUTHED_AS", target->nick, target->handle_info->handle);
1398     else
1399         reply("NSMSG_USERINFO_NOT_AUTHED", target->nick);
1400     return 1;
1401 }
1402
1403 static NICKSERV_FUNC(cmd_nickinfo)
1404 {
1405     struct nick_info *ni;
1406
1407     NICKSERV_MIN_PARMS(2);
1408     if (!(ni = get_nick_info(argv[1]))) {
1409         reply("MSG_NICK_UNKNOWN", argv[1]);
1410         return 0;
1411     }
1412     reply("NSMSG_NICKINFO_OWNER", ni->nick, ni->owner->handle);
1413     return 1;
1414 }
1415
1416 static NICKSERV_FUNC(cmd_rename_handle)
1417 {
1418     struct handle_info *hi;
1419     char msgbuf[MAXLEN], *old_handle;
1420     unsigned int nn;
1421
1422     NICKSERV_MIN_PARMS(3);
1423     if (!(hi = get_victim_oper(user, argv[1])))
1424         return 0;
1425     if (!is_valid_handle(argv[2])) {
1426         reply("NSMSG_FAIL_RENAME", argv[1], argv[2]);
1427         return 0;
1428     }
1429     if (get_handle_info(argv[2])) {
1430         reply("NSMSG_HANDLE_EXISTS", argv[2]);
1431         return 0;
1432     }
1433
1434     dict_remove2(nickserv_handle_dict, old_handle = hi->handle, 1);
1435     hi->handle = strdup(argv[2]);
1436     dict_insert(nickserv_handle_dict, hi->handle, hi);
1437     for (nn=0; nn<rf_list_used; nn++)
1438         rf_list[nn](hi, old_handle);
1439     snprintf(msgbuf, sizeof(msgbuf), "%s renamed account %s to %s.", user->handle_info->handle, old_handle, hi->handle);
1440     reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
1441     global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
1442     free(old_handle);
1443     return 1;
1444 }
1445
1446 static failpw_func_t *failpw_func_list;
1447 static unsigned int failpw_func_size = 0, failpw_func_used = 0;
1448
1449 void
1450 reg_failpw_func(failpw_func_t func)
1451 {
1452     if (failpw_func_used == failpw_func_size) {
1453         if (failpw_func_size) {
1454             failpw_func_size <<= 1;
1455             failpw_func_list = realloc(failpw_func_list, failpw_func_size*sizeof(failpw_func_t));
1456         } else {
1457             failpw_func_size = 8;
1458             failpw_func_list = malloc(failpw_func_size*sizeof(failpw_func_t));
1459         }
1460     }
1461     failpw_func_list[failpw_func_used++] = func;
1462 }
1463
1464 static NICKSERV_FUNC(cmd_auth)
1465 {
1466     int pw_arg, used, maxlogins;
1467     struct handle_info *hi;
1468     const char *passwd;
1469     struct userNode *other;
1470
1471     if (user->handle_info) {
1472         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1473         return 0;
1474     }
1475     if (IsStamped(user)) {
1476         /* Unauthenticated users might still have been stamped
1477            previously and could therefore have a hidden host;
1478            do not allow them to authenticate. */
1479         reply("NSMSG_STAMPED_AUTH");
1480         return 0;
1481     }
1482     if (argc == 3) {
1483         hi = dict_find(nickserv_handle_dict, argv[1], NULL);
1484         pw_arg = 2;
1485     } else if (argc == 2) {
1486         if (nickserv_conf.disable_nicks) {
1487             if (!(hi = get_handle_info(user->nick))) {
1488                 reply("NSMSG_HANDLE_NOT_FOUND");
1489                 return 0;
1490             }
1491         } else {
1492             /* try to look up their handle from their nick */
1493             struct nick_info *ni;
1494             ni = get_nick_info(user->nick);
1495             if (!ni) {
1496                 reply("NSMSG_NICK_NOT_REGISTERED", user->nick);
1497                 return 0;
1498             }
1499             hi = ni->owner;
1500         }
1501         pw_arg = 1;
1502     } else {
1503         reply("MSG_MISSING_PARAMS", argv[0]);
1504         svccmd_send_help(user, nickserv, cmd);
1505         return 0;
1506     }
1507     if (!hi) {
1508         reply("NSMSG_HANDLE_NOT_FOUND");
1509         return 0;
1510     }
1511     passwd = argv[pw_arg];
1512     if (!valid_user_for(user, hi)) {
1513         if (hi->email_addr && nickserv_conf.email_enabled)
1514             reply("NSMSG_USE_AUTHCOOKIE", hi->handle, hi->handle);
1515         else
1516             reply("NSMSG_HOSTMASK_INVALID", hi->handle);
1517         argv[pw_arg] = "BADMASK";
1518         return 1;
1519     }
1520     if (!checkpass(passwd, hi->passwd)) {
1521         unsigned int n;
1522         reply("NSMSG_PASSWORD_INVALID");
1523         argv[pw_arg] = "BADPASS";
1524         for (n=0; n<failpw_func_used; n++) failpw_func_list[n](user, hi);
1525         if (nickserv_conf.autogag_enabled) {
1526             if (!user->auth_policer.params) {
1527                 user->auth_policer.last_req = now;
1528                 user->auth_policer.params = nickserv_conf.auth_policer_params;
1529             }
1530             if (!policer_conforms(&user->auth_policer, now, 1.0)) {
1531                 char *hostmask;
1532                 hostmask = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_BYIP|GENMASK_NO_HIDING);
1533                 log_module(NS_LOG, LOG_INFO, "%s auto-gagged for repeated password guessing.", hostmask);
1534                 gag_create(hostmask, nickserv->nick, "Repeated password guessing.", now+nickserv_conf.autogag_duration);
1535                 free(hostmask);
1536                 argv[pw_arg] = "GAGGED";
1537             }
1538         }
1539         return 1;
1540     }
1541     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1542         reply("NSMSG_HANDLE_SUSPENDED");
1543         argv[pw_arg] = "SUSPENDED";
1544         return 1;
1545     }
1546     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
1547     for (used = 0, other = hi->users; other; other = other->next_authed) {
1548         if (++used >= maxlogins) {
1549             reply("NSMSG_MAX_LOGINS", maxlogins);
1550             argv[pw_arg] = "MAXLOGINS";
1551             return 1;
1552         }
1553     }
1554
1555     if (nickserv_conf.email_required && !hi->email_addr)
1556         reply("NSMSG_PLEASE_SET_EMAIL");
1557     if (!is_secure_password(hi->handle, passwd, NULL))
1558         reply("NSMSG_WEAK_PASSWORD");
1559     if (hi->passwd[0] != '$')
1560         cryptpass(passwd, hi->passwd);
1561
1562     reply("NSMSG_AUTH_SUCCESS");
1563     argv[pw_arg] = "****";
1564     set_user_handle_info(user, hi, 1);
1565     return 1;
1566 }
1567
1568 static allowauth_func_t *allowauth_func_list;
1569 static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
1570
1571 void
1572 reg_allowauth_func(allowauth_func_t func)
1573 {
1574     if (allowauth_func_used == allowauth_func_size) {
1575         if (allowauth_func_size) {
1576             allowauth_func_size <<= 1;
1577             allowauth_func_list = realloc(allowauth_func_list, allowauth_func_size*sizeof(allowauth_func_t));
1578         } else {
1579             allowauth_func_size = 8;
1580             allowauth_func_list = malloc(allowauth_func_size*sizeof(allowauth_func_t));
1581         }
1582     }
1583     allowauth_func_list[allowauth_func_used++] = func;
1584 }
1585
1586 static NICKSERV_FUNC(cmd_allowauth)
1587 {
1588     struct userNode *target;
1589     struct handle_info *hi;
1590     unsigned int n;
1591
1592     NICKSERV_MIN_PARMS(2);
1593     if (!(target = GetUserH(argv[1]))) {
1594         reply("MSG_NICK_UNKNOWN", argv[1]);
1595         return 0;
1596     }
1597     if (target->handle_info) {
1598         reply("NSMSG_USER_PREV_AUTH", target->nick);
1599         return 0;
1600     }
1601     if (IsStamped(target)) {
1602         /* Unauthenticated users might still have been stamped
1603            previously and could therefore have a hidden host;
1604            do not allow them to authenticate to an account. */
1605         send_message(target, nickserv, "NSMSG_USER_PREV_STAMP", target->nick);
1606         return 0;
1607     }
1608     if (argc == 2)
1609         hi = NULL;
1610     else if (!(hi = get_handle_info(argv[2]))) {
1611         reply("MSG_HANDLE_UNKNOWN", argv[2]);
1612         return 0;
1613     }
1614     if (hi) {
1615         if (hi->opserv_level > user->handle_info->opserv_level) {
1616             reply("MSG_USER_OUTRANKED", hi->handle);
1617             return 0;
1618         }
1619         if (((hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER))
1620              || (hi->opserv_level > 0))
1621             && ((argc < 4) || irccasecmp(argv[3], "staff"))) {
1622             reply("NSMSG_ALLOWAUTH_STAFF", hi->handle);
1623             return 0;
1624         }
1625         dict_insert(nickserv_allow_auth_dict, target->nick, hi);
1626         reply("NSMSG_AUTH_ALLOWED", target->nick, hi->handle);
1627         send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_MSG", hi->handle, hi->handle);
1628         if (nickserv_conf.email_enabled)
1629             send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_EMAIL");
1630     } else {
1631         if (dict_remove(nickserv_allow_auth_dict, target->nick))
1632             reply("NSMSG_AUTH_NORMAL_ONLY", target->nick);
1633         else
1634             reply("NSMSG_AUTH_UNSPECIAL", target->nick);
1635     }
1636     for (n=0; n<allowauth_func_used; n++)
1637         allowauth_func_list[n](user, target, hi);
1638     return 1;
1639 }
1640
1641 static NICKSERV_FUNC(cmd_authcookie)
1642 {
1643     struct handle_info *hi;
1644
1645     NICKSERV_MIN_PARMS(2);
1646     if (user->handle_info) {
1647         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1648         return 0;
1649     }
1650     if (IsStamped(user)) {
1651         /* Unauthenticated users might still have been stamped
1652            previously and could therefore have a hidden host;
1653            do not allow them to authenticate to an account. */
1654         reply("NSMSG_STAMPED_AUTHCOOKIE");
1655         return 0;
1656     }
1657     if (!(hi = get_handle_info(argv[1]))) {
1658         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1659         return 0;
1660     }
1661     if (!hi->email_addr) {
1662         reply("MSG_SET_EMAIL_ADDR");
1663         return 0;
1664     }
1665     nickserv_make_cookie(user, hi, ALLOWAUTH, NULL);
1666     reply("NSMSG_USE_COOKIE_AUTH");
1667     return 1;
1668 }
1669
1670 static NICKSERV_FUNC(cmd_delcookie)
1671 {
1672     struct handle_info *hi;
1673
1674     hi = user->handle_info;
1675     if (!hi->cookie) {
1676         reply("NSMSG_NO_COOKIE");
1677         return 0;
1678     }
1679     switch (hi->cookie->type) {
1680     case ACTIVATION:
1681     case EMAIL_CHANGE:
1682         reply("NSMSG_MUST_TIME_OUT");
1683         break;
1684     default:
1685         nickserv_eat_cookie(hi->cookie);
1686         reply("NSMSG_ATE_COOKIE");
1687         break;
1688     }
1689     return 1;
1690 }
1691
1692 static NICKSERV_FUNC(cmd_resetpass)
1693 {
1694     struct handle_info *hi;
1695     char crypted[MD5_CRYPT_LENGTH];
1696
1697     NICKSERV_MIN_PARMS(3);
1698     if (user->handle_info) {
1699         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1700         return 0;
1701     }
1702     if (IsStamped(user)) {
1703         /* Unauthenticated users might still have been stamped
1704            previously and could therefore have a hidden host;
1705            do not allow them to activate an account. */
1706         reply("NSMSG_STAMPED_RESETPASS");
1707         return 0;
1708     }
1709     if (!(hi = get_handle_info(argv[1]))) {
1710         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1711         return 0;
1712     }
1713     if (!hi->email_addr) {
1714         reply("MSG_SET_EMAIL_ADDR");
1715         return 0;
1716     }
1717     cryptpass(argv[2], crypted);
1718     argv[2] = "****";
1719     nickserv_make_cookie(user, hi, PASSWORD_CHANGE, crypted);
1720     return 1;
1721 }
1722
1723 static NICKSERV_FUNC(cmd_cookie)
1724 {
1725     struct handle_info *hi;
1726     const char *cookie;
1727
1728     if ((argc == 2) && (hi = user->handle_info) && hi->cookie && (hi->cookie->type == EMAIL_CHANGE)) {
1729         cookie = argv[1];
1730     } else {
1731         NICKSERV_MIN_PARMS(3);
1732         if (!(hi = get_handle_info(argv[1]))) {
1733             reply("MSG_HANDLE_UNKNOWN", argv[1]);
1734             return 0;
1735         }
1736         cookie = argv[2];
1737     }
1738
1739     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1740         reply("NSMSG_HANDLE_SUSPENDED");
1741         return 0;
1742     }
1743
1744     if (!hi->cookie) {
1745         reply("NSMSG_NO_COOKIE");
1746         return 0;
1747     }
1748
1749     /* Check validity of operation before comparing cookie to
1750      * prohibit guessing by authed users. */
1751     if (user->handle_info
1752         && (hi->cookie->type != EMAIL_CHANGE)
1753         && (hi->cookie->type != PASSWORD_CHANGE)) {
1754         reply("NSMSG_CANNOT_COOKIE");
1755         return 0;
1756     }
1757
1758     if (strcmp(cookie, hi->cookie->cookie)) {
1759         reply("NSMSG_BAD_COOKIE");
1760         return 0;
1761     }
1762
1763     switch (hi->cookie->type) {
1764     case ACTIVATION:
1765         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1766         set_user_handle_info(user, hi, 1);
1767         reply("NSMSG_HANDLE_ACTIVATED");
1768         break;
1769     case PASSWORD_CHANGE:
1770         set_user_handle_info(user, hi, 1);
1771         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1772         reply("NSMSG_PASSWORD_CHANGED");
1773         break;
1774     case EMAIL_CHANGE:
1775         nickserv_set_email_addr(hi, hi->cookie->data);
1776         reply("NSMSG_EMAIL_CHANGED");
1777         break;
1778     case ALLOWAUTH:
1779         set_user_handle_info(user, hi, 1);
1780         reply("NSMSG_AUTH_SUCCESS");
1781         break;
1782     default:
1783         reply("NSMSG_BAD_COOKIE_TYPE", hi->cookie->type);
1784         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d for account %s.", hi->cookie->type, hi->handle);
1785         break;
1786     }
1787
1788     nickserv_eat_cookie(hi->cookie);
1789
1790     return 1;
1791 }
1792
1793 static NICKSERV_FUNC(cmd_oregnick) {
1794     const char *nick;
1795     struct handle_info *target;
1796     struct nick_info *ni;
1797
1798     NICKSERV_MIN_PARMS(3);
1799     if (!(target = modcmd_get_handle_info(user, argv[1])))
1800         return 0;
1801     nick = argv[2];
1802     if (!is_registerable_nick(nick)) {
1803         reply("NSMSG_BAD_NICK", nick);
1804         return 0;
1805     }
1806     ni = dict_find(nickserv_nick_dict, nick, NULL);
1807     if (ni) {
1808         reply("NSMSG_NICK_EXISTS", nick);
1809         return 0;
1810     }
1811     register_nick(nick, target);
1812     reply("NSMSG_OREGNICK_SUCCESS", nick, target->handle);
1813     return 1;
1814 }
1815
1816 static NICKSERV_FUNC(cmd_regnick) {
1817     unsigned n;
1818     struct nick_info *ni;
1819
1820     if (!is_registerable_nick(user->nick)) {
1821         reply("NSMSG_BAD_NICK", user->nick);
1822         return 0;
1823     }
1824     /* count their nicks, see if it's too many */
1825     for (n=0,ni=user->handle_info->nicks; ni; n++,ni=ni->next) ;
1826     if (n >= nickserv_conf.nicks_per_handle) {
1827         reply("NSMSG_TOO_MANY_NICKS");
1828         return 0;
1829     }
1830     ni = dict_find(nickserv_nick_dict, user->nick, NULL);
1831     if (ni) {
1832         reply("NSMSG_NICK_EXISTS", user->nick);
1833         return 0;
1834     }
1835     register_nick(user->nick, user->handle_info);
1836     reply("NSMSG_REGNICK_SUCCESS", user->nick);
1837     return 1;
1838 }
1839
1840 static NICKSERV_FUNC(cmd_pass)
1841 {
1842     struct handle_info *hi;
1843     const char *old_pass, *new_pass;
1844
1845     NICKSERV_MIN_PARMS(3);
1846     hi = user->handle_info;
1847     old_pass = argv[1];
1848     new_pass = argv[2];
1849     argv[2] = "****";
1850     if (!is_secure_password(hi->handle, new_pass, user)) return 0;
1851     if (!checkpass(old_pass, hi->passwd)) {
1852         argv[1] = "BADPASS";
1853         reply("NSMSG_PASSWORD_INVALID");
1854         return 0;
1855     }
1856     cryptpass(new_pass, hi->passwd);
1857     argv[1] = "****";
1858     reply("NSMSG_PASS_SUCCESS");
1859     return 1;
1860 }
1861
1862 static int
1863 nickserv_addmask(struct userNode *user, struct handle_info *hi, const char *mask)
1864 {
1865     unsigned int i;
1866     char *new_mask = canonicalize_hostmask(strdup(mask));
1867     for (i=0; i<hi->masks->used; i++) {
1868         if (!irccasecmp(new_mask, hi->masks->list[i])) {
1869             send_message(user, nickserv, "NSMSG_ADDMASK_ALREADY", new_mask);
1870             free(new_mask);
1871             return 0;
1872         }
1873     }
1874     string_list_append(hi->masks, new_mask);
1875     send_message(user, nickserv, "NSMSG_ADDMASK_SUCCESS", new_mask);
1876     return 1;
1877 }
1878
1879 static NICKSERV_FUNC(cmd_addmask)
1880 {
1881     if (argc < 2) {
1882         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
1883         int res = nickserv_addmask(user, user->handle_info, mask);
1884         free(mask);
1885         return res;
1886     } else {
1887         if (!is_gline(argv[1])) {
1888             reply("NSMSG_MASK_INVALID", argv[1]);
1889             return 0;
1890         }
1891         return nickserv_addmask(user, user->handle_info, argv[1]);
1892     }
1893 }
1894
1895 static NICKSERV_FUNC(cmd_oaddmask)
1896 {
1897     struct handle_info *hi;
1898
1899     NICKSERV_MIN_PARMS(3);
1900     if (!(hi = get_victim_oper(user, argv[1])))
1901         return 0;
1902     return nickserv_addmask(user, hi, argv[2]);
1903 }
1904
1905 static int
1906 nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask)
1907 {
1908     unsigned int i;
1909     for (i=0; i<hi->masks->used; i++) {
1910         if (!strcmp(del_mask, hi->masks->list[i])) {
1911             char *old_mask = hi->masks->list[i];
1912             if (hi->masks->used == 1) {
1913                 send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
1914                 return 0;
1915             }
1916             hi->masks->list[i] = hi->masks->list[--hi->masks->used];
1917             send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
1918             free(old_mask);
1919             return 1;
1920         }
1921     }
1922     send_message(user, nickserv, "NSMSG_DELMASK_NOT_FOUND");
1923     return 0;
1924 }
1925
1926 static NICKSERV_FUNC(cmd_delmask)
1927 {
1928     NICKSERV_MIN_PARMS(2);
1929     return nickserv_delmask(user, user->handle_info, argv[1]);
1930 }
1931
1932 static NICKSERV_FUNC(cmd_odelmask)
1933 {
1934     struct handle_info *hi;
1935     NICKSERV_MIN_PARMS(3);
1936     if (!(hi = get_victim_oper(user, argv[1])))
1937         return 0;
1938     return nickserv_delmask(user, hi, argv[2]);
1939 }
1940
1941 int
1942 nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *padded, unsigned long *premoved) {
1943     unsigned int nn, add = 1, pos;
1944     unsigned long added, removed, flag;
1945
1946     for (added=removed=nn=0; str[nn]; nn++) {
1947         switch (str[nn]) {
1948         case '+': add = 1; break;
1949         case '-': add = 0; break;
1950         default:
1951             if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
1952                 send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
1953                 return 0;
1954             }
1955             if (user && (user->handle_info->opserv_level < flag_access_levels[pos-1])) {
1956                 /* cheesy avoidance of looking up the flag name.. */
1957                 send_message(user, bot, "NSMSG_FLAG_PRIVILEGED", str[nn]);
1958                 return 0;
1959             }
1960             flag = 1 << (pos - 1);
1961             if (add)
1962                 added |= flag, removed &= ~flag;
1963             else
1964                 removed |= flag, added &= ~flag;
1965             break;
1966         }
1967     }
1968     *padded = added;
1969     *premoved = removed;
1970     return 1;
1971 }
1972
1973 static int
1974 nickserv_apply_flags(struct userNode *user, struct handle_info *hi, const char *flags)
1975 {
1976     unsigned long before, after, added, removed;
1977     struct userNode *uNode;
1978
1979     before = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
1980     if (!nickserv_modify_handle_flags(user, nickserv, flags, &added, &removed))
1981         return 0;
1982     hi->flags = (hi->flags | added) & ~removed;
1983     after = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
1984
1985     /* Strip helping flag if they're only a support helper and not
1986      * currently in #support. */
1987     if (HANDLE_FLAGGED(hi, HELPING) && (after == HI_FLAG_SUPPORT_HELPER)) {
1988         struct channelList *schannels;
1989         unsigned int ii;
1990         schannels = chanserv_support_channels();
1991         for (uNode = hi->users; uNode; uNode = uNode->next_authed) {
1992             for (ii = 0; ii < schannels->used; ++ii)
1993                 if (GetUserMode(schannels->list[ii], uNode))
1994                     break;
1995             if (ii < schannels->used)
1996                 break;
1997         }
1998         if (!uNode)
1999             HANDLE_CLEAR_FLAG(hi, HELPING);
2000     }
2001
2002     if (after && !before) {
2003         /* Add user to current helper list. */
2004         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2005             userList_append(&curr_helpers, uNode);
2006     } else if (!after && before) {
2007         /* Remove user from current helper list. */
2008         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2009             userList_remove(&curr_helpers, uNode);
2010     }
2011
2012     return 1;
2013 }
2014
2015 static void
2016 set_list(struct userNode *user, struct handle_info *hi, int override)
2017 {
2018     option_func_t *opt;
2019     unsigned int i;
2020     char *set_display[] = {
2021         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
2022         "EMAIL", "ANNOUNCEMENTS", "MAXLOGINS", "LANGUAGE"
2023     };
2024
2025     send_message(user, nickserv, "NSMSG_SETTING_LIST");
2026
2027     /* Do this so options are presented in a consistent order. */
2028     for (i = 0; i < ArrayLength(set_display); ++i)
2029         if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
2030             opt(user, hi, override, 0, NULL);
2031 }
2032
2033 static NICKSERV_FUNC(cmd_set)
2034 {
2035     struct handle_info *hi;
2036     option_func_t *opt;
2037
2038     hi = user->handle_info;
2039     if (argc < 2) {
2040         set_list(user, hi, 0);
2041         return 1;
2042     }
2043     if (!(opt = dict_find(nickserv_opt_dict, argv[1], NULL))) {
2044         reply("NSMSG_INVALID_OPTION", argv[1]);
2045         return 0;
2046     }
2047     return opt(user, hi, 0, argc-1, argv+1);
2048 }
2049
2050 static NICKSERV_FUNC(cmd_oset)
2051 {
2052     struct handle_info *hi;
2053     option_func_t *opt;
2054
2055     NICKSERV_MIN_PARMS(2);
2056
2057     if (!(hi = get_victim_oper(user, argv[1])))
2058         return 0;
2059
2060     if (argc < 3) {
2061         set_list(user, hi, 0);
2062         return 1;
2063     }
2064
2065     if (!(opt = dict_find(nickserv_opt_dict, argv[2], NULL))) {
2066         reply("NSMSG_INVALID_OPTION", argv[2]);
2067         return 0;
2068     }
2069
2070     return opt(user, hi, 1, argc-2, argv+2);
2071 }
2072
2073 static OPTION_FUNC(opt_info)
2074 {
2075     const char *info;
2076     if (argc > 1) {
2077         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2078             free(hi->infoline);
2079             hi->infoline = NULL;
2080         } else {
2081             hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
2082         }
2083     }
2084
2085     info = hi->infoline ? hi->infoline : user_find_message(user, "MSG_NONE");
2086     send_message(user, nickserv, "NSMSG_SET_INFO", info);
2087     return 1;
2088 }
2089
2090 static OPTION_FUNC(opt_width)
2091 {
2092     if (argc > 1) {
2093         unsigned int new_width = strtoul(argv[1], NULL, 0);
2094         hi->screen_width = new_width;
2095     }
2096
2097     if ((hi->screen_width > 0) && (hi->screen_width < MIN_LINE_SIZE))
2098         hi->screen_width = MIN_LINE_SIZE;
2099     else if (hi->screen_width > MAX_LINE_SIZE)
2100         hi->screen_width = MAX_LINE_SIZE;
2101
2102     send_message(user, nickserv, "NSMSG_SET_WIDTH", hi->screen_width);
2103     return 1;
2104 }
2105
2106 static OPTION_FUNC(opt_tablewidth)
2107 {
2108     if (argc > 1) {
2109         unsigned int new_width = strtoul(argv[1], NULL, 0);
2110         hi->table_width = new_width;
2111     }
2112
2113     if ((hi->table_width > 0) && (hi->table_width < MIN_LINE_SIZE))
2114         hi->table_width = MIN_LINE_SIZE;
2115     else if (hi->screen_width > MAX_LINE_SIZE)
2116         hi->table_width = MAX_LINE_SIZE;
2117
2118     send_message(user, nickserv, "NSMSG_SET_TABLEWIDTH", hi->table_width);
2119     return 1;
2120 }
2121
2122 static OPTION_FUNC(opt_color)
2123 {
2124     if (argc > 1) {
2125         if (enabled_string(argv[1]))
2126             HANDLE_SET_FLAG(hi, MIRC_COLOR);
2127         else if (disabled_string(argv[1]))
2128             HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
2129         else {
2130             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2131             return 0;
2132         }
2133     }
2134
2135     send_message(user, nickserv, "NSMSG_SET_COLOR", user_find_message(user, HANDLE_FLAGGED(hi, MIRC_COLOR) ? "MSG_ON" : "MSG_OFF"));
2136     return 1;
2137 }
2138
2139 static OPTION_FUNC(opt_privmsg)
2140 {
2141     if (argc > 1) {
2142         if (enabled_string(argv[1]))
2143             HANDLE_SET_FLAG(hi, USE_PRIVMSG);
2144         else if (disabled_string(argv[1]))
2145             HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
2146         else {
2147             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2148             return 0;
2149         }
2150     }
2151
2152     send_message(user, nickserv, "NSMSG_SET_PRIVMSG", user_find_message(user, HANDLE_FLAGGED(hi, USE_PRIVMSG) ? "MSG_ON" : "MSG_OFF"));
2153     return 1;
2154 }
2155
2156 static OPTION_FUNC(opt_style)
2157 {
2158     char *style;
2159
2160     if (argc > 1) {
2161         if (!irccasecmp(argv[1], "Zoot"))
2162             hi->userlist_style = HI_STYLE_ZOOT;
2163         else if (!irccasecmp(argv[1], "def"))
2164             hi->userlist_style = HI_STYLE_DEF;
2165     }
2166
2167     switch (hi->userlist_style) {
2168     case HI_STYLE_DEF:
2169         style = "def";
2170         break;
2171     case HI_STYLE_ZOOT:
2172     default:
2173         style = "Zoot";
2174     }
2175
2176     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
2177     return 1;
2178 }
2179
2180 static OPTION_FUNC(opt_announcements)
2181 {
2182     const char *choice;
2183
2184     if (argc > 1) {
2185         if (enabled_string(argv[1]))
2186             hi->announcements = 'y';
2187         else if (disabled_string(argv[1]))
2188             hi->announcements = 'n';
2189         else if (!strcmp(argv[1], "?") || !irccasecmp(argv[1], "default"))
2190             hi->announcements = '?';
2191         else {
2192             send_message(user, nickserv, "NSMSG_INVALID_ANNOUNCE", argv[1]);
2193             return 0;
2194         }
2195     }
2196
2197     switch (hi->announcements) {
2198     case 'y': choice = user_find_message(user, "MSG_ON"); break;
2199     case 'n': choice = user_find_message(user, "MSG_OFF"); break;
2200     case '?': choice = "default"; break;
2201     default: choice = "unknown"; break;
2202     }
2203     send_message(user, nickserv, "NSMSG_SET_ANNOUNCEMENTS", choice);
2204     return 1;
2205 }
2206
2207 static OPTION_FUNC(opt_password)
2208 {
2209     if (!override) {
2210         send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
2211         return 0;
2212     }
2213
2214     if (argc > 1)
2215         cryptpass(argv[1], hi->passwd);
2216
2217     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
2218     return 1;
2219 }
2220
2221 static OPTION_FUNC(opt_flags)
2222 {
2223     char flags[33];
2224     unsigned int ii, flen;
2225
2226     if (!override) {
2227         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2228         return 0;
2229     }
2230
2231     if (argc > 1)
2232         nickserv_apply_flags(user, hi, argv[1]);
2233
2234     for (ii = flen = 0; handle_flags[ii]; ii++)
2235         if (hi->flags & (1 << ii))
2236             flags[flen++] = handle_flags[ii];
2237     flags[flen] = '\0';
2238     if (hi->flags)
2239         send_message(user, nickserv, "NSMSG_SET_FLAGS", flags);
2240     else
2241         send_message(user, nickserv, "NSMSG_SET_FLAGS", user_find_message(user, "MSG_NONE"));
2242     return 1;
2243 }
2244
2245 static OPTION_FUNC(opt_email)
2246 {
2247     if (argc > 1) {
2248         const char *str;
2249         if (!is_valid_email_addr(argv[1])) {
2250             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
2251             return 0;
2252         }
2253         if ((str = sendmail_prohibited_address(argv[1]))) {
2254             send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", argv[1], str);
2255             return 0;
2256         }
2257         if (hi->email_addr && !irccasecmp(hi->email_addr, argv[1]))
2258             send_message(user, nickserv, "NSMSG_EMAIL_SAME");
2259         else if (!override)
2260                 nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1]);
2261         else {
2262             nickserv_set_email_addr(hi, argv[1]);
2263             if (hi->cookie)
2264                 nickserv_eat_cookie(hi->cookie);
2265             send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2266         }
2267     } else
2268         send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2269     return 1;
2270 }
2271
2272 static OPTION_FUNC(opt_maxlogins)
2273 {
2274     char maxlogins;
2275     if (argc > 1) {
2276         maxlogins = strtoul(argv[1], NULL, 0);
2277         if ((maxlogins > nickserv_conf.hard_maxlogins) && !override) {
2278             send_message(user, nickserv, "NSMSG_BAD_MAX_LOGINS", nickserv_conf.hard_maxlogins);
2279             return 0;
2280         }
2281         hi->maxlogins = maxlogins;
2282     }
2283     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
2284     send_message(user, nickserv, "NSMSG_SET_MAXLOGINS", maxlogins);
2285     return 1;
2286 }
2287
2288 static OPTION_FUNC(opt_language)
2289 {
2290     struct language *lang;
2291     if (argc > 1) {
2292         lang = language_find(argv[1]);
2293         if (irccasecmp(lang->name, argv[1]))
2294             send_message(user, nickserv, "NSMSG_LANGUAGE_NOT_FOUND", argv[1], lang->name);
2295         hi->language = lang;
2296     }
2297     send_message(user, nickserv, "NSMSG_SET_LANGUAGE", hi->language->name);
2298     return 1;
2299 }
2300
2301 int
2302 oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
2303     if (!oper_has_access(user, bot, nickserv_conf.modoper_level, 0))
2304         return 0;
2305     if ((user->handle_info->opserv_level < target->opserv_level)
2306         || ((user->handle_info->opserv_level == target->opserv_level)
2307             && (user->handle_info->opserv_level < 1000))) {
2308         send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
2309         return 0;
2310     }
2311     if ((user->handle_info->opserv_level < new_level)
2312         || ((user->handle_info->opserv_level == new_level)
2313             && (user->handle_info->opserv_level < 1000))) {
2314         send_message(user, bot, "NSMSG_OPSERV_LEVEL_BAD");
2315         return 0;
2316     }
2317     if (user->handle_info == target) {
2318         send_message(user, bot, "MSG_STUPID_ACCESS_CHANGE");
2319         return 0;
2320     }
2321     if (target->opserv_level == new_level)
2322         return 0;
2323     log_module(NS_LOG, LOG_INFO, "Account %s setting oper level for account %s to %d (from %d).",
2324         user->handle_info->handle, target->handle, new_level, target->opserv_level);
2325     target->opserv_level = new_level;
2326     return 1;
2327 }
2328
2329 static OPTION_FUNC(opt_level)
2330 {
2331     int res;
2332
2333     if (!override) {
2334         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2335         return 0;
2336     }
2337
2338     res = (argc > 1) ? oper_try_set_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
2339     send_message(user, nickserv, "NSMSG_SET_LEVEL", hi->opserv_level);
2340     return res;
2341 }
2342
2343 static OPTION_FUNC(opt_epithet)
2344 {
2345     if (!override) {
2346         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2347         return 0;
2348     }
2349
2350     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_epithet_level, 0)) {
2351         char *epithet = unsplit_string(argv+1, argc-1, NULL);
2352         if (hi->epithet)
2353             free(hi->epithet);
2354         if ((epithet[0] == '*') && !epithet[1])
2355             hi->epithet = NULL;
2356         else
2357             hi->epithet = strdup(epithet);
2358     }
2359
2360     if (hi->epithet)
2361         send_message(user, nickserv, "NSMSG_SET_EPITHET", hi->epithet);
2362     else
2363         send_message(user, nickserv, "NSMSG_SET_EPITHET", user_find_message(user, "MSG_NONE"));
2364     return 1;
2365 }
2366
2367 static NICKSERV_FUNC(cmd_reclaim)
2368 {
2369     struct handle_info *hi;
2370     struct nick_info *ni;
2371     struct userNode *victim;
2372
2373     NICKSERV_MIN_PARMS(2);
2374     hi = user->handle_info;
2375     ni = dict_find(nickserv_nick_dict, argv[1], 0);
2376     if (!ni) {
2377         reply("NSMSG_UNKNOWN_NICK", argv[1]);
2378         return 0;
2379     }
2380     if (ni->owner != user->handle_info) {
2381         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
2382         return 0;
2383     }
2384     victim = GetUserH(ni->nick);
2385     if (!victim) {
2386         reply("MSG_NICK_UNKNOWN", ni->nick);
2387         return 0;
2388     }
2389     if (victim == user) {
2390         reply("NSMSG_NICK_USER_YOU");
2391         return 0;
2392     }
2393     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
2394     switch (nickserv_conf.reclaim_action) {
2395     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
2396     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
2397     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
2398     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
2399     }
2400     return 1;
2401 }
2402
2403 static NICKSERV_FUNC(cmd_unregnick)
2404 {
2405     const char *nick;
2406     struct handle_info *hi;
2407     struct nick_info *ni;
2408
2409     hi = user->handle_info;
2410     nick = (argc < 2) ? user->nick : (const char*)argv[1];
2411     ni = dict_find(nickserv_nick_dict, nick, NULL);
2412     if (!ni) {
2413         reply("NSMSG_UNKNOWN_NICK", nick);
2414         return 0;
2415     }
2416     if (hi != ni->owner) {
2417         reply("NSMSG_NOT_YOUR_NICK", nick);
2418         return 0;
2419     }
2420     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2421     delete_nick(ni);
2422     return 1;
2423 }
2424
2425 static NICKSERV_FUNC(cmd_ounregnick)
2426 {
2427     struct nick_info *ni;
2428
2429     NICKSERV_MIN_PARMS(2);
2430     if (!(ni = get_nick_info(argv[1]))) {
2431         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
2432         return 0;
2433     }
2434     if (ni->owner->opserv_level >= user->handle_info->opserv_level) {
2435         reply("MSG_USER_OUTRANKED", ni->nick);
2436         return 0;
2437     }
2438     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2439     delete_nick(ni);
2440     return 1;
2441 }
2442
2443 static NICKSERV_FUNC(cmd_unregister)
2444 {
2445     struct handle_info *hi;
2446     char *passwd;
2447
2448     NICKSERV_MIN_PARMS(2);
2449     hi = user->handle_info;
2450     passwd = argv[1];
2451     argv[1] = "****";
2452     if (checkpass(passwd, hi->passwd)) {
2453         nickserv_unregister_handle(hi, user);
2454         return 1;
2455     } else {
2456         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
2457         reply("NSMSG_PASSWORD_INVALID");
2458         return 0;
2459     }
2460 }
2461
2462 static NICKSERV_FUNC(cmd_ounregister)
2463 {
2464     struct handle_info *hi;
2465
2466     NICKSERV_MIN_PARMS(2);
2467     if (!(hi = get_victim_oper(user, argv[1])))
2468         return 0;
2469     nickserv_unregister_handle(hi, user);
2470     return 0;
2471 }
2472
2473 static NICKSERV_FUNC(cmd_status)
2474 {
2475     if (nickserv_conf.disable_nicks) {
2476         reply("NSMSG_GLOBAL_STATS_NONICK",
2477                         dict_size(nickserv_handle_dict));
2478     } else {
2479         if (user->handle_info) {
2480             int cnt=0;
2481             struct nick_info *ni;
2482             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
2483             reply("NSMSG_HANDLE_STATS", cnt);
2484         } else {
2485             reply("NSMSG_HANDLE_NONE");
2486         }
2487         reply("NSMSG_GLOBAL_STATS",
2488               dict_size(nickserv_handle_dict),
2489               dict_size(nickserv_nick_dict));
2490     }
2491     return 1;
2492 }
2493
2494 static NICKSERV_FUNC(cmd_ghost)
2495 {
2496     struct userNode *target;
2497     char reason[MAXLEN];
2498
2499     NICKSERV_MIN_PARMS(2);
2500     if (!(target = GetUserH(argv[1]))) {
2501         reply("MSG_NICK_UNKNOWN", argv[1]);
2502         return 0;
2503     }
2504     if (target == user) {
2505         reply("NSMSG_CANNOT_GHOST_SELF");
2506         return 0;
2507     }
2508     if (!target->handle_info || (target->handle_info != user->handle_info)) {
2509         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
2510         return 0;
2511     }
2512     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
2513     DelUser(target, nickserv, 1, reason);
2514     reply("NSMSG_GHOST_KILLED", argv[1]);
2515     return 1;
2516 }
2517
2518 static NICKSERV_FUNC(cmd_vacation)
2519 {
2520     HANDLE_SET_FLAG(user->handle_info, FROZEN);
2521     reply("NSMSG_ON_VACATION");
2522     return 1;
2523 }
2524
2525 static int
2526 nickserv_saxdb_write(struct saxdb_context *ctx) {
2527     dict_iterator_t it;
2528     struct handle_info *hi;
2529     char flags[33];
2530
2531     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2532         hi = iter_data(it);
2533 #ifdef WITH_PROTOCOL_BAHAMUT
2534         assert(hi->id);
2535 #endif
2536         saxdb_start_record(ctx, iter_key(it), 0);
2537         if (hi->announcements != '?') {
2538             flags[0] = hi->announcements;
2539             flags[1] = 0;
2540             saxdb_write_string(ctx, KEY_ANNOUNCEMENTS, flags);
2541         }
2542         if (hi->cookie) {
2543             struct handle_cookie *cookie = hi->cookie;
2544             char *type;
2545
2546             switch (cookie->type) {
2547             case ACTIVATION: type = KEY_ACTIVATION; break;
2548             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
2549             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
2550             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
2551             default: type = NULL; break;
2552             }
2553             if (type) {
2554                 saxdb_start_record(ctx, KEY_COOKIE, 0);
2555                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
2556                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
2557                 if (cookie->data)
2558                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
2559                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
2560                 saxdb_end_record(ctx);
2561             }
2562         }
2563         if (hi->email_addr)
2564             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
2565         if (hi->epithet)
2566             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
2567         if (hi->flags) {
2568             int ii, flen;
2569
2570             for (ii=flen=0; handle_flags[ii]; ++ii)
2571                 if (hi->flags & (1 << ii))
2572                     flags[flen++] = handle_flags[ii];
2573             flags[flen] = 0;
2574             saxdb_write_string(ctx, KEY_FLAGS, flags);
2575         }
2576 #ifdef WITH_PROTOCOL_BAHAMUT
2577         saxdb_write_int(ctx, KEY_ID, hi->id);
2578 #endif
2579         if (hi->infoline)
2580             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
2581         if (hi->last_quit_host[0])
2582             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
2583         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
2584         if (hi->masks->used)
2585             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
2586         if (hi->maxlogins)
2587             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
2588         if (hi->nicks) {
2589             struct string_list *slist;
2590             struct nick_info *ni;
2591
2592             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
2593             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
2594             saxdb_write_string_list(ctx, KEY_NICKS, slist);
2595             free(slist->list);
2596             free(slist);
2597         }
2598         if (hi->opserv_level)
2599             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
2600         if (hi->language != lang_C)
2601             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
2602         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
2603         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
2604         if (hi->screen_width)
2605             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
2606         if (hi->table_width)
2607             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
2608         flags[0] = hi->userlist_style;
2609         flags[1] = 0;
2610         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
2611         saxdb_end_record(ctx);
2612     }
2613     return 0;
2614 }
2615
2616 static handle_merge_func_t *handle_merge_func_list;
2617 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
2618
2619 void
2620 reg_handle_merge_func(handle_merge_func_t func)
2621 {
2622     if (handle_merge_func_used == handle_merge_func_size) {
2623         if (handle_merge_func_size) {
2624             handle_merge_func_size <<= 1;
2625             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
2626         } else {
2627             handle_merge_func_size = 8;
2628             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
2629         }
2630     }
2631     handle_merge_func_list[handle_merge_func_used++] = func;
2632 }
2633
2634 static NICKSERV_FUNC(cmd_merge)
2635 {
2636     struct handle_info *hi_from, *hi_to;
2637     struct userNode *last_user;
2638     struct userData *cList, *cListNext;
2639     unsigned int ii, jj, n;
2640     char buffer[MAXLEN];
2641
2642     NICKSERV_MIN_PARMS(3);
2643
2644     if (!(hi_from = get_victim_oper(user, argv[1])))
2645         return 0;
2646     if (!(hi_to = get_victim_oper(user, argv[2])))
2647         return 0;
2648     if (hi_to == hi_from) {
2649         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
2650         return 0;
2651     }
2652
2653     for (n=0; n<handle_merge_func_used; n++)
2654         handle_merge_func_list[n](user, hi_to, hi_from);
2655
2656     /* Append "from" handle's nicks to "to" handle's nick list. */
2657     if (hi_to->nicks) {
2658         struct nick_info *last_ni;
2659         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
2660         last_ni->next = hi_from->nicks;
2661     }
2662     while (hi_from->nicks) {
2663         hi_from->nicks->owner = hi_to;
2664         hi_from->nicks = hi_from->nicks->next;
2665     }
2666
2667     /* Merge the hostmasks. */
2668     for (ii=0; ii<hi_from->masks->used; ii++) {
2669         char *mask = hi_from->masks->list[ii];
2670         for (jj=0; jj<hi_to->masks->used; jj++)
2671             if (match_ircglobs(hi_to->masks->list[jj], mask))
2672                 break;
2673         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
2674             string_list_append(hi_to->masks, strdup(mask));
2675     }
2676
2677     /* Merge the lists of authed users. */
2678     if (hi_to->users) {
2679         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
2680         last_user->next_authed = hi_from->users;
2681     } else {
2682         hi_to->users = hi_from->users;
2683     }
2684     /* Repoint the old "from" handle's users. */
2685     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
2686         last_user->handle_info = hi_to;
2687     }
2688     hi_from->users = NULL;
2689
2690     /* Merge channel userlists. */
2691     for (cList=hi_from->channels; cList; cList=cListNext) {
2692         struct userData *cList2;
2693         cListNext = cList->u_next;
2694         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
2695             if (cList->channel == cList2->channel)
2696                 break;
2697         log_module(NS_LOG, LOG_DEBUG, "Merging %s->%s@%s: before %p->%p->%-p, %p->%p->%p",
2698                    hi_from->handle, hi_to->handle, cList->channel->channel->name,
2699                    cList->u_prev, cList, cList->u_next,
2700                    (cList2?cList2->u_prev:0), cList2, (cList2?cList2->u_next:0));
2701         if (cList2 && (cList2->access >= cList->access)) {
2702             /* keep cList2 in hi_to; remove cList from hi_from */
2703             log_module(NS_LOG, LOG_DEBUG, "Deleting %p", cList);
2704             del_channel_user(cList, 1);
2705         } else {
2706             if (cList2) {
2707                 /* remove the lower-ranking cList2 from hi_to */
2708                 log_module(NS_LOG, LOG_DEBUG, "Deleting %p", cList2);
2709                 del_channel_user(cList2, 1);
2710             }
2711             /* cList needs to be moved from hi_from to hi_to */
2712             cList->handle = hi_to;
2713             /* Remove from linked list for hi_from */
2714             assert(!cList->u_prev);
2715             hi_from->channels = cList->u_next;
2716             if (cList->u_next)
2717                 cList->u_next->u_prev = cList->u_prev;
2718             /* Add to linked list for hi_to */
2719             cList->u_prev = NULL;
2720             cList->u_next = hi_to->channels;
2721             if (hi_to->channels)
2722                 hi_to->channels->u_prev = cList;
2723             hi_to->channels = cList;
2724             log_module(NS_LOG, LOG_DEBUG, "Now %p->%p->%p",
2725                        cList->u_prev, cList, cList->u_next);
2726         }
2727     }
2728
2729     /* Do they get an OpServ level promotion? */
2730     if (hi_from->opserv_level > hi_to->opserv_level)
2731         hi_to->opserv_level = hi_from->opserv_level;
2732
2733     /* What about last seen time? */
2734     if (hi_from->lastseen > hi_to->lastseen)
2735         hi_to->lastseen = hi_from->lastseen;
2736
2737     /* Notify of success. */
2738     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
2739     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
2740     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
2741
2742     /* Unregister the "from" handle. */
2743     nickserv_unregister_handle(hi_from, NULL);
2744
2745     return 1;
2746 }
2747
2748 struct nickserv_discrim {
2749     unsigned int limit, min_level, max_level;
2750     unsigned long flags_on, flags_off;
2751     time_t min_registered, max_registered;
2752     time_t lastseen;
2753     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
2754     const char *nickmask;
2755     const char *hostmask;
2756     const char *handlemask;
2757     const char *emailmask;
2758 };
2759
2760 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
2761
2762 struct discrim_apply_info {
2763     struct nickserv_discrim *discrim;
2764     discrim_search_func func;
2765     struct userNode *source;
2766     unsigned int matched;
2767 };
2768
2769 static struct nickserv_discrim *
2770 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
2771 {
2772     unsigned int i;
2773     struct nickserv_discrim *discrim;
2774
2775     discrim = malloc(sizeof(*discrim));
2776     memset(discrim, 0, sizeof(*discrim));
2777     discrim->min_level = 0;
2778     discrim->max_level = ~0;
2779     discrim->limit = 50;
2780     discrim->min_registered = 0;
2781     discrim->max_registered = INT_MAX;
2782     discrim->lastseen = now;
2783
2784     for (i=0; i<argc; i++) {
2785         if (i == argc - 1) {
2786             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2787             goto fail;
2788         }
2789         if (!irccasecmp(argv[i], "limit")) {
2790             discrim->limit = atoi(argv[++i]);
2791         } else if (!irccasecmp(argv[i], "flags")) {
2792             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
2793         } else if (!irccasecmp(argv[i], "registered")) {
2794             const char *cmp = argv[++i];
2795             if (cmp[0] == '<') {
2796                 if (cmp[1] == '=') {
2797                     discrim->min_registered = now - ParseInterval(cmp+2);
2798                 } else {
2799                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
2800                 }
2801             } else if (cmp[0] == '=') {
2802                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
2803             } else if (cmp[0] == '>') {
2804                 if (cmp[1] == '=') {
2805                     discrim->max_registered = now - ParseInterval(cmp+2);
2806                 } else {
2807                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
2808                 }
2809             } else {
2810                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
2811             }
2812         } else if (!irccasecmp(argv[i], "seen")) {
2813             discrim->lastseen = now - ParseInterval(argv[++i]);
2814         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
2815             discrim->nickmask = argv[++i];
2816         } else if (!irccasecmp(argv[i], "hostmask")) {
2817             i++;
2818             if (!irccasecmp(argv[i], "exact")) {
2819                 if (i == argc - 1) {
2820                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2821                     goto fail;
2822                 }
2823                 discrim->hostmask_type = EXACT;
2824             } else if (!irccasecmp(argv[i], "subset")) {
2825                 if (i == argc - 1) {
2826                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2827                     goto fail;
2828                 }
2829                 discrim->hostmask_type = SUBSET;
2830             } else if (!irccasecmp(argv[i], "superset")) {
2831                 if (i == argc - 1) {
2832                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2833                     goto fail;
2834                 }
2835                 discrim->hostmask_type = SUPERSET;
2836             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
2837                if (i == argc - 1) {
2838                    send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
2839                    goto fail;
2840                }
2841                discrim->hostmask_type = LASTQUIT;
2842             } else {
2843                 i--;
2844                 discrim->hostmask_type = SUPERSET;
2845             }
2846             discrim->hostmask = argv[++i];
2847         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
2848             if (!irccasecmp(argv[++i], "*")) {
2849                 discrim->handlemask = 0;
2850             } else {
2851                 discrim->handlemask = argv[i];
2852             }
2853         } else if (!irccasecmp(argv[i], "email")) {
2854             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
2855                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
2856                 goto fail;
2857             } else if (!irccasecmp(argv[++i], "*")) {
2858                 discrim->emailmask = 0;
2859             } else {
2860                 discrim->emailmask = argv[i];
2861             }
2862         } else if (!irccasecmp(argv[i], "access")) {
2863             const char *cmp = argv[++i];
2864             if (cmp[0] == '<') {
2865                 if (discrim->min_level == 0) discrim->min_level = 1;
2866                 if (cmp[1] == '=') {
2867                     discrim->max_level = strtoul(cmp+2, NULL, 0);
2868                 } else {
2869                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
2870                 }
2871             } else if (cmp[0] == '=') {
2872                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
2873             } else if (cmp[0] == '>') {
2874                 if (cmp[1] == '=') {
2875                     discrim->min_level = strtoul(cmp+2, NULL, 0);
2876                 } else {
2877                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
2878                 }
2879             } else {
2880                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
2881             }
2882         } else {
2883             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
2884             goto fail;
2885         }
2886     }
2887     return discrim;
2888   fail:
2889     free(discrim);
2890     return NULL;
2891 }
2892
2893 static int
2894 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
2895 {
2896     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
2897         || (discrim->flags_off & hi->flags)
2898         || (discrim->min_registered > hi->registered)
2899         || (discrim->max_registered < hi->registered)
2900         || (discrim->lastseen < (hi->users?now:hi->lastseen))
2901         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
2902         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
2903         || (discrim->min_level > hi->opserv_level)
2904         || (discrim->max_level < hi->opserv_level)) {
2905         return 0;
2906     }
2907     if (discrim->hostmask) {
2908         unsigned int i;
2909         for (i=0; i<hi->masks->used; i++) {
2910             const char *mask = hi->masks->list[i];
2911             if ((discrim->hostmask_type == SUBSET)
2912                 && (match_ircglobs(discrim->hostmask, mask))) break;
2913             else if ((discrim->hostmask_type == EXACT)
2914                      && !irccasecmp(discrim->hostmask, mask)) break;
2915             else if ((discrim->hostmask_type == SUPERSET)
2916                      && (match_ircglobs(mask, discrim->hostmask))) break;
2917             else if ((discrim->hostmask_type == LASTQUIT)
2918                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
2919         }
2920         if (i==hi->masks->used) return 0;
2921     }
2922     if (discrim->nickmask) {
2923         struct nick_info *nick = hi->nicks;
2924         while (nick) {
2925             if (match_ircglob(nick->nick, discrim->nickmask)) break;
2926             nick = nick->next;
2927         }
2928         if (!nick) return 0;
2929     }
2930     return 1;
2931 }
2932
2933 static unsigned int
2934 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
2935 {
2936     dict_iterator_t it, next;
2937     unsigned int matched;
2938
2939     for (it = dict_first(nickserv_handle_dict), matched = 0;
2940          it && (matched < discrim->limit);
2941          it = next) {
2942         next = iter_next(it);
2943         if (nickserv_discrim_match(discrim, iter_data(it))) {
2944             dsf(source, iter_data(it));
2945             matched++;
2946         }
2947     }
2948     return matched;
2949 }
2950
2951 static void
2952 search_print_func(struct userNode *source, struct handle_info *match)
2953 {
2954     send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
2955 }
2956
2957 static void
2958 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
2959 {
2960 }
2961
2962 static void
2963 search_unregister_func (struct userNode *source, struct handle_info *match)
2964 {
2965     if (oper_has_access(source, nickserv, match->opserv_level, 0))
2966         nickserv_unregister_handle(match, source);
2967 }
2968
2969 static int
2970 nickserv_sort_accounts_by_access(const void *a, const void *b)
2971 {
2972     const struct handle_info *hi_a = *(const struct handle_info**)a;
2973     const struct handle_info *hi_b = *(const struct handle_info**)b;
2974     if (hi_a->opserv_level != hi_b->opserv_level)
2975         return hi_b->opserv_level - hi_a->opserv_level;
2976     return irccasecmp(hi_a->handle, hi_b->handle);
2977 }
2978
2979 void
2980 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
2981 {
2982     struct handle_info_list hil;
2983     struct helpfile_table tbl;
2984     unsigned int ii;
2985     dict_iterator_t it;
2986     const char **ary;
2987
2988     memset(&hil, 0, sizeof(hil));
2989     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2990         struct handle_info *hi = iter_data(it);
2991         if (hi->opserv_level)
2992             handle_info_list_append(&hil, hi);
2993     }
2994     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
2995     tbl.length = hil.used + 1;
2996     tbl.width = 2;
2997     tbl.flags = TABLE_NO_FREE;
2998     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
2999     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
3000     ary[0] = "Account";
3001     ary[1] = "Level";
3002     for (ii = 0; ii < hil.used; ) {
3003         ary = malloc(tbl.width * sizeof(ary[0]));
3004         ary[0] = hil.list[ii]->handle;
3005         ary[1] = strtab(hil.list[ii]->opserv_level);
3006         tbl.contents[++ii] = ary;
3007     }
3008     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3009     reply("MSG_MATCH_COUNT", hil.used);
3010     for (ii = 0; ii < hil.used; )
3011         free(tbl.contents[++ii]);
3012     free(tbl.contents);
3013     free(hil.list);
3014 }
3015
3016 static NICKSERV_FUNC(cmd_search)
3017 {
3018     struct nickserv_discrim *discrim;
3019     discrim_search_func action;
3020     struct svccmd *subcmd;
3021     unsigned int matches;
3022     char buf[MAXLEN];
3023
3024     NICKSERV_MIN_PARMS(3);
3025     sprintf(buf, "search %s", argv[1]);
3026     subcmd = dict_find(nickserv_service->commands, buf, NULL);
3027     if (!irccasecmp(argv[1], "print"))
3028         action = search_print_func;
3029     else if (!irccasecmp(argv[1], "count"))
3030         action = search_count_func;
3031     else if (!irccasecmp(argv[1], "unregister"))
3032         action = search_unregister_func;
3033     else {
3034         reply("NSMSG_INVALID_ACTION", argv[1]);
3035         return 0;
3036     }
3037
3038     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
3039         return 0;
3040
3041     discrim = nickserv_discrim_create(user, argc-2, argv+2);
3042     if (!discrim)
3043         return 0;
3044
3045     if (action == search_print_func)
3046         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
3047     else if (action == search_count_func)
3048         discrim->limit = INT_MAX;
3049
3050     matches = nickserv_discrim_search(discrim, action, user);
3051
3052     if (matches)
3053         reply("MSG_MATCH_COUNT", matches);
3054     else
3055         reply("MSG_NO_MATCHES");
3056
3057     free(discrim);
3058     return 0;
3059 }
3060
3061 static MODCMD_FUNC(cmd_checkpass)
3062 {
3063     struct handle_info *hi;
3064
3065     NICKSERV_MIN_PARMS(3);
3066     if (!(hi = get_handle_info(argv[1]))) {
3067         reply("MSG_HANDLE_UNKNOWN", argv[1]);
3068         return 0;
3069     }
3070     if (checkpass(argv[2], hi->passwd))
3071         reply("CHECKPASS_YES");
3072     else
3073         reply("CHECKPASS_NO");
3074     argv[2] = "****";
3075     return 1;
3076 }
3077
3078 static void
3079 nickserv_db_read_handle(const char *handle, dict_t obj)
3080 {
3081     const char *str;
3082     struct string_list *masks, *slist;
3083     struct handle_info *hi;
3084     struct userNode *authed_users;
3085     unsigned long int id;
3086     unsigned int ii;
3087     dict_t subdb;
3088
3089     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
3090     id = str ? strtoul(str, NULL, 0) : 0;
3091     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
3092     if (!str) {
3093         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
3094         return;
3095     }
3096     if ((hi = get_handle_info(handle))) {
3097         authed_users = hi->users;
3098         hi->users = NULL;
3099         dict_remove(nickserv_handle_dict, hi->handle);
3100     } else {
3101         authed_users = NULL;
3102     }
3103     hi = register_handle(handle, str, id);
3104     if (authed_users) {
3105         hi->users = authed_users;
3106         while (authed_users) {
3107             authed_users->handle_info = hi;
3108             authed_users = authed_users->next_authed;
3109         }
3110     }
3111     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
3112     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
3113     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
3114     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
3115     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
3116     hi->language = language_find(str ? str : "C");
3117     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
3118     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
3119     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
3120     if (str)
3121         hi->infoline = strdup(str);
3122     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
3123     hi->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
3124     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
3125     hi->lastseen = str ? (time_t)strtoul(str, NULL, 0) : hi->registered;
3126     /* We want to read the nicks even if disable_nicks is set.  This is so
3127      * that we don't lose the nick data entirely. */
3128     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
3129     if (slist) {
3130         for (ii=0; ii<slist->used; ii++)
3131             register_nick(slist->list[ii], hi);
3132     }
3133     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
3134     if (str) {
3135         for (ii=0; str[ii]; ii++)
3136             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
3137     }
3138     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
3139     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
3140     str = database_get_data(obj, KEY_ANNOUNCEMENTS, RECDB_QSTRING);
3141     hi->announcements = str ? str[0] : '?';
3142     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
3143     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
3144     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
3145     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
3146     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
3147     if (!str)
3148         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
3149     if (str)
3150         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
3151     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
3152     if (str)
3153         nickserv_set_email_addr(hi, str);
3154     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
3155     if (str)
3156         hi->epithet = strdup(str);
3157     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
3158     if (subdb) {
3159         const char *data, *type, *expires, *cookie_str;
3160         struct handle_cookie *cookie;
3161
3162         cookie = calloc(1, sizeof(*cookie));
3163         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
3164         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
3165         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
3166         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
3167         if (!type || !expires || !cookie_str) {
3168             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
3169             goto cookie_out;
3170         }
3171         if (!irccasecmp(type, KEY_ACTIVATION))
3172             cookie->type = ACTIVATION;
3173         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
3174             cookie->type = PASSWORD_CHANGE;
3175         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
3176             cookie->type = EMAIL_CHANGE;
3177         else if (!irccasecmp(type, KEY_ALLOWAUTH))
3178             cookie->type = ALLOWAUTH;
3179         else {
3180             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
3181             goto cookie_out;
3182         }
3183         cookie->expires = strtoul(expires, NULL, 0);
3184         if (cookie->expires < now)
3185             goto cookie_out;
3186         if (data)
3187             cookie->data = strdup(data);
3188         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
3189         cookie->hi = hi;
3190       cookie_out:
3191         if (cookie->hi)
3192             nickserv_bake_cookie(cookie);
3193         else
3194             nickserv_free_cookie(cookie);
3195     }
3196 }
3197
3198 static int
3199 nickserv_saxdb_read(dict_t db) {
3200     dict_iterator_t it;
3201     struct record_data *rd;
3202
3203     for (it=dict_first(db); it; it=iter_next(it)) {
3204         rd = iter_data(it);
3205         nickserv_db_read_handle(iter_key(it), rd->d.object);
3206     }
3207     return 0;
3208 }
3209
3210 static NICKSERV_FUNC(cmd_mergedb)
3211 {
3212     struct timeval start, stop;
3213     dict_t db;
3214
3215     NICKSERV_MIN_PARMS(2);
3216     gettimeofday(&start, NULL);
3217     if (!(db = parse_database(argv[1]))) {
3218         reply("NSMSG_DB_UNREADABLE", argv[1]);
3219         return 0;
3220     }
3221     nickserv_saxdb_read(db);
3222     free_database(db);
3223     gettimeofday(&stop, NULL);
3224     stop.tv_sec -= start.tv_sec;
3225     stop.tv_usec -= start.tv_usec;
3226     if (stop.tv_usec < 0) {
3227         stop.tv_sec -= 1;
3228         stop.tv_usec += 1000000;
3229     }
3230     reply("NSMSG_DB_MERGED", argv[1], stop.tv_sec, stop.tv_usec/1000);
3231     return 1;
3232 }
3233
3234 static void
3235 expire_handles(UNUSED_ARG(void *data))
3236 {
3237     dict_iterator_t it, next;
3238     time_t expiry;
3239     struct handle_info *hi;
3240
3241     for (it=dict_first(nickserv_handle_dict); it; it=next) {
3242         next = iter_next(it);
3243         hi = iter_data(it);
3244         if ((hi->opserv_level > 0)
3245             || hi->users
3246             || HANDLE_FLAGGED(hi, FROZEN)
3247             || HANDLE_FLAGGED(hi, NODELETE)) {
3248             continue;
3249         }
3250         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
3251         if ((now - hi->lastseen) > expiry) {
3252             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
3253             nickserv_unregister_handle(hi, NULL);
3254         }
3255     }
3256
3257     if (nickserv_conf.handle_expire_frequency)
3258         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3259 }
3260
3261 static void
3262 nickserv_load_dict(const char *fname)
3263 {
3264     FILE *file;
3265     char line[128];
3266     if (!(file = fopen(fname, "r"))) {
3267         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
3268         return;
3269     }
3270     while (!feof(file)) {
3271         fgets(line, sizeof(line), file);
3272         if (!line[0])
3273             continue;
3274         if (line[strlen(line)-1] == '\n')
3275             line[strlen(line)-1] = 0;
3276         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
3277     }
3278     fclose(file);
3279     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
3280 }
3281
3282 static enum reclaim_action
3283 reclaim_action_from_string(const char *str) {
3284     if (!str)
3285         return RECLAIM_NONE;
3286     else if (!irccasecmp(str, "warn"))
3287         return RECLAIM_WARN;
3288     else if (!irccasecmp(str, "svsnick"))
3289         return RECLAIM_SVSNICK;
3290     else if (!irccasecmp(str, "kill"))
3291         return RECLAIM_KILL;
3292     else
3293         return RECLAIM_NONE;
3294 }
3295
3296 static void
3297 nickserv_conf_read(void)
3298 {
3299     dict_t conf_node, child;
3300     const char *str;
3301     dict_iterator_t it;
3302
3303     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
3304         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
3305         return;
3306     }
3307     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
3308     if (!str)
3309         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
3310     if (nickserv_conf.valid_handle_regex_set) regfree(&nickserv_conf.valid_handle_regex);
3311     if (str) {
3312         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3313         nickserv_conf.valid_handle_regex_set = !err;
3314         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
3315     } else {
3316         nickserv_conf.valid_handle_regex_set = 0;
3317     }
3318     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
3319     if (nickserv_conf.valid_nick_regex_set) regfree(&nickserv_conf.valid_nick_regex);
3320     if (str) {
3321         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3322         nickserv_conf.valid_nick_regex_set = !err;
3323         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
3324     } else {
3325         nickserv_conf.valid_nick_regex_set = 0;
3326     }
3327     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
3328     if (!str)
3329         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
3330     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
3331     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
3332     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
3333     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
3334     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
3335     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
3336     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
3337     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
3338     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
3339     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
3340     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
3341     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
3342     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
3343     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
3344     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3345     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
3346     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
3347     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
3348     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
3349     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
3350     if (!str)
3351         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
3352     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
3353     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3354     if (!str)
3355         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3356     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
3357     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3358     if (!str)
3359         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3360     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
3361     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
3362     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
3363     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
3364     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
3365     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
3366     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
3367     if (!nickserv_conf.disable_nicks) {
3368         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
3369         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3370         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
3371         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
3372         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
3373         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3374         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
3375         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
3376     }
3377     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
3378     for (it=dict_first(child); it; it=iter_next(it)) {
3379         const char *key = iter_key(it), *value;
3380         unsigned char flag;
3381         int pos;
3382
3383         if (!strncasecmp(key, "uc_", 3))
3384             flag = toupper(key[3]);
3385         else if (!strncasecmp(key, "lc_", 3))
3386             flag = tolower(key[3]);
3387         else
3388             flag = key[0];
3389
3390         if ((pos = handle_inverse_flags[flag])) {
3391             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
3392             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
3393         }
3394     }
3395     if (nickserv_conf.weak_password_dict)
3396         dict_delete(nickserv_conf.weak_password_dict);
3397     nickserv_conf.weak_password_dict = dict_new();
3398     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
3399     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
3400     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
3401     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
3402     if (str)
3403         nickserv_load_dict(str);
3404     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
3405     if (nickserv && str)
3406         NickChange(nickserv, str, 0);
3407     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
3408     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
3409     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
3410     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
3411     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
3412     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
3413     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
3414     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
3415     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
3416     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
3417     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
3418     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
3419     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
3420     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
3421     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
3422     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
3423     str = conf_get_data("server/network", RECDB_QSTRING);
3424     nickserv_conf.network_name = str ? str : "some IRC network";
3425     if (!nickserv_conf.auth_policer_params) {
3426         nickserv_conf.auth_policer_params = policer_params_new();
3427         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
3428         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
3429     }
3430     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
3431     for (it=dict_first(child); it; it=iter_next(it))
3432         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
3433 }
3434
3435 static void
3436 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
3437     char newnick[NICKLEN+1];
3438
3439     assert(user);
3440     assert(ni);
3441     switch (action) {
3442     case RECLAIM_NONE:
3443         /* do nothing */
3444         break;
3445     case RECLAIM_WARN:
3446         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3447         break;
3448     case RECLAIM_SVSNICK:
3449         do {
3450             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
3451         } while (GetUserH(newnick));
3452         irc_svsnick(nickserv, user, newnick);
3453         break;
3454     case RECLAIM_KILL:
3455         irc_kill(nickserv, user, "NSMSG_RECLAIM_KILL");
3456         break;
3457     }
3458 }
3459
3460 static void
3461 nickserv_reclaim_p(void *data) {
3462     struct userNode *user = data;
3463     struct nick_info *ni = get_nick_info(user->nick);
3464     if (ni)
3465         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3466 }
3467
3468 static int
3469 check_user_nick(struct userNode *user) {
3470     struct nick_info *ni;
3471     user->modes &= ~FLAGS_REGNICK;
3472     if (!(ni = get_nick_info(user->nick)))
3473         return 0;
3474     if (user->handle_info == ni->owner) {
3475         user->modes |= FLAGS_REGNICK;
3476         irc_regnick(user);
3477         return 0;
3478     }
3479     if (nickserv_conf.warn_nick_owned)
3480         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3481     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
3482         return 0;
3483     if (nickserv_conf.auto_reclaim_delay)
3484         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
3485     else
3486         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3487     return 0;
3488 }
3489
3490 int
3491 handle_new_user(struct userNode *user)
3492 {
3493     return check_user_nick(user);
3494 }
3495
3496 void
3497 handle_account(struct userNode *user, const char *stamp)
3498 {
3499     struct handle_info *hi;
3500
3501 #ifdef WITH_PROTOCOL_P10
3502     hi = dict_find(nickserv_handle_dict, stamp, NULL);
3503 #else
3504     hi = dict_find(nickserv_id_dict, stamp, NULL);
3505 #endif
3506
3507     if (hi) {
3508         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
3509             return;
3510         }
3511         set_user_handle_info(user, hi, 0);
3512     } else {
3513         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s.", user->nick, stamp);
3514     }
3515 }
3516
3517 void
3518 handle_nick_change(struct userNode *user, const char *old_nick)
3519 {
3520     struct handle_info *hi;
3521
3522     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
3523         dict_remove(nickserv_allow_auth_dict, old_nick);
3524         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
3525     }
3526     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
3527     check_user_nick(user);
3528 }
3529
3530 void
3531 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
3532 {
3533     dict_remove(nickserv_allow_auth_dict, user->nick);
3534     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
3535     set_user_handle_info(user, NULL, 0);
3536 }
3537
3538 static struct modcmd *
3539 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
3540 {
3541     if (min_level > 0) {
3542         char buf[16];
3543         sprintf(buf, "%u", min_level);
3544         if (must_be_qualified) {
3545             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
3546         } else {
3547             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
3548         }
3549     } else if (min_level == 0) {
3550         if (must_be_qualified) {
3551             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
3552         } else {
3553             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
3554         }
3555     } else {
3556         if (must_be_qualified) {
3557             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
3558         } else {
3559             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
3560         }
3561     }
3562 }
3563
3564 static void
3565 nickserv_db_cleanup(void)
3566 {
3567     unreg_del_user_func(nickserv_remove_user);
3568     userList_clean(&curr_helpers);
3569     policer_params_delete(nickserv_conf.auth_policer_params);
3570     dict_delete(nickserv_handle_dict);
3571     dict_delete(nickserv_nick_dict);
3572     dict_delete(nickserv_opt_dict);
3573     dict_delete(nickserv_allow_auth_dict);
3574     dict_delete(nickserv_email_dict);
3575     dict_delete(nickserv_id_dict);
3576     dict_delete(nickserv_conf.weak_password_dict);
3577     free(auth_func_list);
3578     free(unreg_func_list);
3579     free(rf_list);
3580     free(allowauth_func_list);
3581     free(handle_merge_func_list);
3582     free(failpw_func_list);
3583     if (nickserv_conf.valid_handle_regex_set)
3584         regfree(&nickserv_conf.valid_handle_regex);
3585     if (nickserv_conf.valid_nick_regex_set)
3586         regfree(&nickserv_conf.valid_nick_regex);
3587 }
3588
3589 void
3590 init_nickserv(const char *nick)
3591 {
3592     unsigned int i;
3593     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
3594     reg_new_user_func(handle_new_user);
3595     reg_nick_change_func(handle_nick_change);
3596     reg_del_user_func(nickserv_remove_user);
3597     reg_account_func(handle_account);
3598
3599     /* set up handle_inverse_flags */
3600     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
3601     for (i=0; handle_flags[i]; i++) {
3602         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
3603         flag_access_levels[i] = 0;
3604     }
3605
3606     conf_register_reload(nickserv_conf_read);
3607     nickserv_opt_dict = dict_new();
3608     nickserv_email_dict = dict_new();
3609     dict_set_free_keys(nickserv_email_dict, free);
3610     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
3611
3612     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
3613     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
3614     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
3615     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
3616     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
3617     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
3618     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
3619     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
3620     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
3621     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
3622     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
3623     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
3624     nickserv_define_func("SET", cmd_set, -1, 1, 0);
3625     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
3626     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
3627     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
3628     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
3629     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
3630     nickserv_define_func("MERGE", cmd_merge, 0, 1, 0);
3631     if (!nickserv_conf.disable_nicks) {
3632         /* nick management commands */
3633         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
3634         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
3635         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
3636         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
3637         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
3638         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
3639     }
3640     if (nickserv_conf.email_enabled) {
3641         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
3642         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
3643         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
3644         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
3645         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
3646     }
3647     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
3648     /* miscellaneous commands */
3649     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
3650     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
3651     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
3652     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
3653     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
3654     /* other options */
3655     dict_insert(nickserv_opt_dict, "INFO", opt_info);
3656     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
3657     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
3658     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
3659     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
3660     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
3661     dict_insert(nickserv_opt_dict, "PASS", opt_password);
3662     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
3663     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
3664     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
3665     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
3666     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
3667     dict_insert(nickserv_opt_dict, "ANNOUNCEMENTS", opt_announcements);
3668     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
3669     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
3670
3671     nickserv_handle_dict = dict_new();
3672     dict_set_free_keys(nickserv_handle_dict, free);
3673     dict_set_free_data(nickserv_handle_dict, free_handle_info);
3674
3675     nickserv_id_dict = dict_new();
3676     dict_set_free_keys(nickserv_id_dict, free);
3677
3678     nickserv_nick_dict = dict_new();
3679     dict_set_free_data(nickserv_nick_dict, free_nick_info);
3680
3681     nickserv_allow_auth_dict = dict_new();
3682
3683     userList_init(&curr_helpers);
3684
3685     if (nick) {
3686         nickserv = AddService(nick, "Nick Services");
3687         nickserv_service = service_register(nickserv, 0);
3688     }
3689     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
3690     reg_exit_func(nickserv_db_cleanup);
3691     if(nickserv_conf.handle_expire_frequency)
3692         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3693     message_register_table(msgtab);
3694 }