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