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