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