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