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