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