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