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