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