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