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