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