Reject ident@host fakehost osets if the setter lacks access to fake idents
[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             if(!oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0))
2641                 goto no_access;
2642             ident = mask;
2643             *host++ = '\0';
2644         } else {
2645             ident = NULL;
2646             host = mask;
2647         }
2648
2649         if ((strlen(host) > HOSTLEN) || (host[0] == '.')) {
2650             send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
2651             return 0;
2652         }
2653
2654         if (ident && strlen(ident) > USERLEN) {
2655             send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
2656             return 0;
2657         }
2658
2659         if (host[0]) {
2660             free(hi->fakehost);
2661             if (!strcmp(host, "*"))
2662                 hi->fakehost = NULL;
2663             else
2664                 hi->fakehost = strdup(host);
2665             host = hi->fakehost;
2666         }
2667         else
2668             host = generate_fakehost(hi);
2669
2670         if (ident) {
2671             free(hi->fakeident);
2672             if (!strcmp(ident, "*"))
2673                 hi->fakeident = NULL;
2674             else
2675                 hi->fakeident = strdup(ident);
2676             ident = hi->fakeident;
2677         }
2678         else
2679             ident = generate_fakeident(hi, NULL);
2680
2681         apply_fakehost(hi, NULL);
2682     } else {
2683 no_access:
2684         host = generate_fakehost(hi);
2685         ident = generate_fakeident(hi, NULL);
2686     }
2687     if (!host)
2688         host = (char *) user_find_message(user, "MSG_NONE");
2689     if(ident)
2690         send_message(user, nickserv, "NSMSG_SET_FAKEIDENTHOST", ident, host);
2691     else
2692         send_message(user, nickserv, "NSMSG_SET_FAKEHOST", host);
2693     return 1;
2694 }
2695
2696 static OPTION_FUNC(opt_fakeident)
2697 {
2698     const char *ident;
2699
2700     if (!override) {
2701         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2702         return 0;
2703     }
2704
2705     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
2706         ident = argv[1];
2707         if (strlen(ident) > USERLEN) {
2708             send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
2709             return 0;
2710         }
2711         free(hi->fakeident);
2712         if (!strcmp(ident, "*"))
2713             hi->fakeident = NULL;
2714         else
2715             hi->fakeident = strdup(ident);
2716         ident = hi->fakeident;
2717         apply_fakehost(hi, NULL);
2718     } else
2719         ident = generate_fakeident(hi, NULL); /* NULL if no fake ident set */
2720     if (!ident)
2721         ident = user_find_message(user, "MSG_NONE");
2722     send_message(user, nickserv, "NSMSG_SET_FAKEIDENT", ident);
2723     return 1;
2724 }
2725
2726 static NICKSERV_FUNC(cmd_reclaim)
2727 {
2728     struct handle_info *hi;
2729     struct nick_info *ni;
2730     struct userNode *victim;
2731
2732     NICKSERV_MIN_PARMS(2);
2733     hi = user->handle_info;
2734     ni = dict_find(nickserv_nick_dict, argv[1], 0);
2735     if (!ni) {
2736         reply("NSMSG_UNKNOWN_NICK", argv[1]);
2737         return 0;
2738     }
2739     if (ni->owner != user->handle_info) {
2740         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
2741         return 0;
2742     }
2743     victim = GetUserH(ni->nick);
2744     if (!victim) {
2745         reply("MSG_NICK_UNKNOWN", ni->nick);
2746         return 0;
2747     }
2748     if (victim == user) {
2749         reply("NSMSG_NICK_USER_YOU");
2750         return 0;
2751     }
2752     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
2753     switch (nickserv_conf.reclaim_action) {
2754     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
2755     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
2756     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
2757     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
2758     }
2759     return 1;
2760 }
2761
2762 static NICKSERV_FUNC(cmd_unregnick)
2763 {
2764     const char *nick;
2765     struct handle_info *hi;
2766     struct nick_info *ni;
2767
2768     hi = user->handle_info;
2769     nick = (argc < 2) ? user->nick : (const char*)argv[1];
2770     ni = dict_find(nickserv_nick_dict, nick, NULL);
2771     if (!ni) {
2772         reply("NSMSG_UNKNOWN_NICK", nick);
2773         return 0;
2774     }
2775     if (hi != ni->owner) {
2776         reply("NSMSG_NOT_YOUR_NICK", nick);
2777         return 0;
2778     }
2779     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2780     delete_nick(ni);
2781     return 1;
2782 }
2783
2784 static NICKSERV_FUNC(cmd_ounregnick)
2785 {
2786     struct nick_info *ni;
2787
2788     NICKSERV_MIN_PARMS(2);
2789     if (!(ni = get_nick_info(argv[1]))) {
2790         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
2791         return 0;
2792     }
2793     if (!oper_outranks(user, ni->owner))
2794         return 0;
2795     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2796     delete_nick(ni);
2797     return 1;
2798 }
2799
2800 static NICKSERV_FUNC(cmd_unregister)
2801 {
2802     struct handle_info *hi;
2803     char *passwd;
2804
2805     NICKSERV_MIN_PARMS(2);
2806     hi = user->handle_info;
2807     passwd = argv[1];
2808     argv[1] = "****";
2809     if (checkpass(passwd, hi->passwd)) {
2810         nickserv_unregister_handle(hi, user);
2811         return 1;
2812     } else {
2813         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
2814         reply("NSMSG_PASSWORD_INVALID");
2815         return 0;
2816     }
2817 }
2818
2819 static NICKSERV_FUNC(cmd_ounregister)
2820 {
2821     struct handle_info *hi;
2822     char reason[MAXLEN];
2823     int force;
2824
2825     NICKSERV_MIN_PARMS(2);
2826     if (!(hi = get_victim_oper(user, argv[1])))
2827         return 0;
2828
2829     if (HANDLE_FLAGGED(hi, NODELETE)) {
2830         reply("NSMSG_UNREGISTER_NODELETE", hi->handle);
2831         return 0;
2832     }
2833
2834     force = IsOper(user) && (argc > 2) && !irccasecmp(argv[2], "force");
2835     if (!force &&
2836         ((hi->flags & nickserv_conf.ounregister_flags)
2837          || hi->users
2838          || (hi->last_quit_host[0] && ((unsigned)(now - hi->lastseen) < nickserv_conf.ounregister_inactive)))) {
2839         reply((IsOper(user) ? "NSMSG_UNREGISTER_MUST_FORCE" : "NSMSG_UNREGISTER_CANNOT_FORCE"), hi->handle);
2840         return 0;
2841     }
2842
2843     snprintf(reason, sizeof(reason), "%s unregistered account %s.", user->handle_info->handle, hi->handle);
2844     global_message(MESSAGE_RECIPIENT_STAFF, reason);
2845     nickserv_unregister_handle(hi, user);
2846     return 1;
2847 }
2848
2849 static NICKSERV_FUNC(cmd_status)
2850 {
2851     if (nickserv_conf.disable_nicks) {
2852         reply("NSMSG_GLOBAL_STATS_NONICK",
2853                         dict_size(nickserv_handle_dict));
2854     } else {
2855         if (user->handle_info) {
2856             int cnt=0;
2857             struct nick_info *ni;
2858             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
2859             reply("NSMSG_HANDLE_STATS", cnt);
2860         } else {
2861             reply("NSMSG_HANDLE_NONE");
2862         }
2863         reply("NSMSG_GLOBAL_STATS",
2864               dict_size(nickserv_handle_dict),
2865               dict_size(nickserv_nick_dict));
2866     }
2867     return 1;
2868 }
2869
2870 static NICKSERV_FUNC(cmd_ghost)
2871 {
2872     struct userNode *target;
2873     char reason[MAXLEN];
2874
2875     NICKSERV_MIN_PARMS(2);
2876     if (!(target = GetUserH(argv[1]))) {
2877         reply("MSG_NICK_UNKNOWN", argv[1]);
2878         return 0;
2879     }
2880     if (target == user) {
2881         reply("NSMSG_CANNOT_GHOST_SELF");
2882         return 0;
2883     }
2884     if (!target->handle_info || (target->handle_info != user->handle_info)) {
2885         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
2886         return 0;
2887     }
2888     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
2889     DelUser(target, nickserv, 1, reason);
2890     reply("NSMSG_GHOST_KILLED", argv[1]);
2891     return 1;
2892 }
2893
2894 static NICKSERV_FUNC(cmd_vacation)
2895 {
2896     HANDLE_SET_FLAG(user->handle_info, FROZEN);
2897     reply("NSMSG_ON_VACATION");
2898     return 1;
2899 }
2900
2901 static NICKSERV_FUNC(cmd_addnote)
2902 {
2903     struct handle_info *hi;
2904     unsigned long duration;
2905     char text[MAXLEN];
2906     unsigned int id;
2907     struct handle_note *prev;
2908     struct handle_note *note;
2909
2910     /* Parse parameters and figure out values for note's fields. */
2911     NICKSERV_MIN_PARMS(4);
2912     hi = get_victim_oper(user, argv[1]);
2913     if (!hi)
2914         return 0;
2915     if(!strcmp(argv[2], "0"))
2916         duration = 0;
2917     else if(!(duration = ParseInterval(argv[2])))
2918     {
2919         reply("MSG_INVALID_DURATION", argv[2]);
2920         return 0;
2921     }
2922     if (duration > 2*365*86400) {
2923         reply("NSMSG_EXCESSIVE_DURATION", argv[2]);
2924         return 0;
2925     }
2926     unsplit_string(argv + 3, argc - 3, text);
2927     WALK_NOTES(hi, prev, note) {}
2928     id = prev ? (prev->id + 1) : 1;
2929
2930     /* Create the new note structure. */
2931     note = calloc(1, sizeof(*note) + strlen(text));
2932     note->next = NULL;
2933     note->expires = duration ? (now + duration) : 0;
2934     note->set = now;
2935     note->id = id;
2936     safestrncpy(note->setter, user->handle_info->handle, sizeof(note->setter));
2937     strcpy(note->note, text);
2938     if (prev)
2939         prev->next = note;
2940     else
2941         hi->notes = note;
2942     reply("NSMSG_NOTE_ADDED", id, hi->handle);
2943     return 1;
2944 }
2945
2946 static NICKSERV_FUNC(cmd_delnote)
2947 {
2948     struct handle_info *hi;
2949     struct handle_note *prev;
2950     struct handle_note *note;
2951     int id;
2952
2953     NICKSERV_MIN_PARMS(3);
2954     hi = get_victim_oper(user, argv[1]);
2955     if (!hi)
2956         return 0;
2957     id = strtoul(argv[2], NULL, 10);
2958     WALK_NOTES(hi, prev, note) {
2959         if (id == note->id) {
2960             if (prev)
2961                 prev->next = note->next;
2962             else
2963                 hi->notes = note->next;
2964             free(note);
2965             reply("NSMSG_NOTE_REMOVED", id, hi->handle);
2966             return 1;
2967         }
2968     }
2969     reply("NSMSG_NO_SUCH_NOTE", hi->handle, id);
2970     return 0;
2971 }
2972
2973 static int
2974 nickserv_saxdb_write(struct saxdb_context *ctx) {
2975     dict_iterator_t it;
2976     struct handle_info *hi;
2977     char flags[33];
2978
2979     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2980         hi = iter_data(it);
2981         assert(hi->id != 0);
2982         saxdb_start_record(ctx, iter_key(it), 0);
2983         if (hi->cookie) {
2984             struct handle_cookie *cookie = hi->cookie;
2985             char *type;
2986
2987             switch (cookie->type) {
2988             case ACTIVATION: type = KEY_ACTIVATION; break;
2989             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
2990             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
2991             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
2992             default: type = NULL; break;
2993             }
2994             if (type) {
2995                 saxdb_start_record(ctx, KEY_COOKIE, 0);
2996                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
2997                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
2998                 if (cookie->data)
2999                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
3000                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
3001                 saxdb_end_record(ctx);
3002             }
3003         }
3004         if (hi->notes) {
3005             struct handle_note *prev, *note;
3006             saxdb_start_record(ctx, KEY_NOTES, 0);
3007             WALK_NOTES(hi, prev, note) {
3008                 snprintf(flags, sizeof(flags), "%d", note->id);
3009                 saxdb_start_record(ctx, flags, 0);
3010                 if (note->expires)
3011                     saxdb_write_int(ctx, KEY_NOTE_EXPIRES, note->expires);
3012                 saxdb_write_int(ctx, KEY_NOTE_SET, note->set);
3013                 saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
3014                 saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
3015                 saxdb_end_record(ctx);
3016             }
3017             saxdb_end_record(ctx);
3018         }
3019         if (hi->email_addr)
3020             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
3021         if (hi->epithet)
3022             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
3023         if (hi->fakehost)
3024             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
3025         if (hi->fakeident)
3026             saxdb_write_string(ctx, KEY_FAKEIDENT, hi->fakeident);
3027         if (hi->flags) {
3028             int ii, flen;
3029
3030             for (ii=flen=0; handle_flags[ii]; ++ii)
3031                 if (hi->flags & (1 << ii))
3032                     flags[flen++] = handle_flags[ii];
3033             flags[flen] = 0;
3034             saxdb_write_string(ctx, KEY_FLAGS, flags);
3035         }
3036         saxdb_write_int(ctx, KEY_ID, hi->id);
3037         if (hi->infoline)
3038             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
3039         if (hi->last_quit_host[0])
3040             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
3041         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
3042         if (hi->karma != 0)
3043             saxdb_write_sint(ctx, KEY_KARMA, hi->karma);
3044         if (hi->masks->used)
3045             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
3046         if (hi->maxlogins)
3047             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
3048         if (hi->nicks) {
3049             struct string_list *slist;
3050             struct nick_info *ni;
3051
3052             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
3053             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
3054             saxdb_write_string_list(ctx, KEY_NICKS, slist);
3055             free(slist->list);
3056             free(slist);
3057         }
3058         if (hi->opserv_level)
3059             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
3060         if (hi->language != lang_C)
3061             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
3062         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
3063         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
3064         if (hi->screen_width)
3065             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
3066         if (hi->table_width)
3067             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
3068         flags[0] = hi->userlist_style;
3069         flags[1] = 0;
3070         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
3071         saxdb_end_record(ctx);
3072     }
3073     return 0;
3074 }
3075
3076 static handle_merge_func_t *handle_merge_func_list;
3077 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
3078
3079 void
3080 reg_handle_merge_func(handle_merge_func_t func)
3081 {
3082     if (handle_merge_func_used == handle_merge_func_size) {
3083         if (handle_merge_func_size) {
3084             handle_merge_func_size <<= 1;
3085             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
3086         } else {
3087             handle_merge_func_size = 8;
3088             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
3089         }
3090     }
3091     handle_merge_func_list[handle_merge_func_used++] = func;
3092 }
3093
3094 static NICKSERV_FUNC(cmd_merge)
3095 {
3096     struct handle_info *hi_from, *hi_to;
3097     struct userNode *last_user;
3098     struct userData *cList, *cListNext;
3099     unsigned int ii, jj, n;
3100     char buffer[MAXLEN];
3101
3102     NICKSERV_MIN_PARMS(3);
3103
3104     if (!(hi_from = get_victim_oper(user, argv[1])))
3105         return 0;
3106     if (!(hi_to = get_victim_oper(user, argv[2])))
3107         return 0;
3108     if (hi_to == hi_from) {
3109         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
3110         return 0;
3111     }
3112
3113     for (n=0; n<handle_merge_func_used; n++)
3114         handle_merge_func_list[n](user, hi_to, hi_from);
3115
3116     /* Append "from" handle's nicks to "to" handle's nick list. */
3117     if (hi_to->nicks) {
3118         struct nick_info *last_ni;
3119         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
3120         last_ni->next = hi_from->nicks;
3121     }
3122     while (hi_from->nicks) {
3123         hi_from->nicks->owner = hi_to;
3124         hi_from->nicks = hi_from->nicks->next;
3125     }
3126
3127     /* Merge the hostmasks. */
3128     for (ii=0; ii<hi_from->masks->used; ii++) {
3129         char *mask = hi_from->masks->list[ii];
3130         for (jj=0; jj<hi_to->masks->used; jj++)
3131             if (match_ircglobs(hi_to->masks->list[jj], mask))
3132                 break;
3133         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
3134             string_list_append(hi_to->masks, strdup(mask));
3135     }
3136
3137     /* Merge the lists of authed users. */
3138     if (hi_to->users) {
3139         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
3140         last_user->next_authed = hi_from->users;
3141     } else {
3142         hi_to->users = hi_from->users;
3143     }
3144     /* Repoint the old "from" handle's users. */
3145     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
3146         last_user->handle_info = hi_to;
3147     }
3148     hi_from->users = NULL;
3149
3150     /* Merge channel userlists. */
3151     for (cList=hi_from->channels; cList; cList=cListNext) {
3152         struct userData *cList2;
3153         cListNext = cList->u_next;
3154         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
3155             if (cList->channel == cList2->channel)
3156                 break;
3157         if (cList2 && (cList2->access >= cList->access)) {
3158             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);
3159             /* keep cList2 in hi_to; remove cList from hi_from */
3160             del_channel_user(cList, 1);
3161         } else {
3162             if (cList2) {
3163                 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);
3164                 /* remove the lower-ranking cList2 from hi_to */
3165                 del_channel_user(cList2, 1);
3166             } else {
3167                 log_module(NS_LOG, LOG_INFO, "Merge: %s had no access in %s", hi_to->handle, cList->channel->channel->name);
3168             }
3169             /* cList needs to be moved from hi_from to hi_to */
3170             cList->handle = hi_to;
3171             /* Remove from linked list for hi_from */
3172             assert(!cList->u_prev);
3173             hi_from->channels = cList->u_next;
3174             if (cList->u_next)
3175                 cList->u_next->u_prev = cList->u_prev;
3176             /* Add to linked list for hi_to */
3177             cList->u_prev = NULL;
3178             cList->u_next = hi_to->channels;
3179             if (hi_to->channels)
3180                 hi_to->channels->u_prev = cList;
3181             hi_to->channels = cList;
3182         }
3183     }
3184
3185     /* Do they get an OpServ level promotion? */
3186     if (hi_from->opserv_level > hi_to->opserv_level)
3187         hi_to->opserv_level = hi_from->opserv_level;
3188
3189     /* What about last seen time? */
3190     if (hi_from->lastseen > hi_to->lastseen)
3191         hi_to->lastseen = hi_from->lastseen;
3192
3193     /* New karma is the sum of the two original karmas. */
3194     hi_to->karma += hi_from->karma;
3195
3196     /* Does a fakehost carry over?  (This intentionally doesn't set it
3197      * for users previously attached to hi_to.  They'll just have to
3198      * reconnect.)
3199      */
3200     if (hi_from->fakehost && !hi_to->fakehost)
3201         hi_to->fakehost = strdup(hi_from->fakehost);
3202     if (hi_from->fakeident && !hi_to->fakeident)
3203         hi_to->fakeident = strdup(hi_from->fakeident);
3204
3205     /* Notify of success. */
3206     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
3207     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
3208     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
3209
3210     /* Unregister the "from" handle. */
3211     nickserv_unregister_handle(hi_from, NULL);
3212
3213     return 1;
3214 }
3215
3216 struct nickserv_discrim {
3217     unsigned long flags_on, flags_off;
3218     unsigned long min_registered, max_registered;
3219     unsigned long lastseen;
3220     unsigned int limit;
3221     int min_level, max_level;
3222     int min_karma, max_karma;
3223     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
3224     const char *nickmask;
3225     const char *hostmask;
3226     const char *fakehostmask;
3227     const char *fakeidentmask;
3228     const char *handlemask;
3229     const char *emailmask;
3230 };
3231
3232 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
3233
3234 struct discrim_apply_info {
3235     struct nickserv_discrim *discrim;
3236     discrim_search_func func;
3237     struct userNode *source;
3238     unsigned int matched;
3239 };
3240
3241 static struct nickserv_discrim *
3242 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
3243 {
3244     unsigned int i;
3245     struct nickserv_discrim *discrim;
3246
3247     discrim = malloc(sizeof(*discrim));
3248     memset(discrim, 0, sizeof(*discrim));
3249     discrim->min_level = 0;
3250     discrim->max_level = INT_MAX;
3251     discrim->limit = 50;
3252     discrim->min_registered = 0;
3253     discrim->max_registered = ULONG_MAX;
3254     discrim->lastseen = ULONG_MAX;
3255     discrim->min_karma = INT_MIN;
3256     discrim->max_karma = INT_MAX;
3257
3258     for (i=0; i<argc; i++) {
3259         if (i == argc - 1) {
3260             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3261             goto fail;
3262         }
3263         if (!irccasecmp(argv[i], "limit")) {
3264             discrim->limit = strtoul(argv[++i], NULL, 0);
3265         } else if (!irccasecmp(argv[i], "flags")) {
3266             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
3267         } else if (!irccasecmp(argv[i], "registered")) {
3268             const char *cmp = argv[++i];
3269             if (cmp[0] == '<') {
3270                 if (cmp[1] == '=') {
3271                     discrim->min_registered = now - ParseInterval(cmp+2);
3272                 } else {
3273                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
3274                 }
3275             } else if (cmp[0] == '=') {
3276                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
3277             } else if (cmp[0] == '>') {
3278                 if (cmp[1] == '=') {
3279                     discrim->max_registered = now - ParseInterval(cmp+2);
3280                 } else {
3281                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
3282                 }
3283             } else {
3284                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3285             }
3286         } else if (!irccasecmp(argv[i], "seen")) {
3287             discrim->lastseen = now - ParseInterval(argv[++i]);
3288         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
3289             discrim->nickmask = argv[++i];
3290         } else if (!irccasecmp(argv[i], "hostmask")) {
3291             i++;
3292             if (!irccasecmp(argv[i], "exact")) {
3293                 if (i == argc - 1) {
3294                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3295                     goto fail;
3296                 }
3297                 discrim->hostmask_type = EXACT;
3298             } else if (!irccasecmp(argv[i], "subset")) {
3299                 if (i == argc - 1) {
3300                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3301                     goto fail;
3302                 }
3303                 discrim->hostmask_type = SUBSET;
3304             } else if (!irccasecmp(argv[i], "superset")) {
3305                 if (i == argc - 1) {
3306                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3307                     goto fail;
3308                 }
3309                 discrim->hostmask_type = SUPERSET;
3310             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
3311                 if (i == argc - 1) {
3312                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3313                     goto fail;
3314                 }
3315                 discrim->hostmask_type = LASTQUIT;
3316             } else {
3317                 i--;
3318                 discrim->hostmask_type = SUPERSET;
3319             }
3320             discrim->hostmask = argv[++i];
3321         } else if (!irccasecmp(argv[i], "fakehost")) {
3322             if (!irccasecmp(argv[++i], "*")) {
3323                 discrim->fakehostmask = 0;
3324             } else {
3325                 discrim->fakehostmask = argv[i];
3326             }
3327         } else if (!irccasecmp(argv[i], "fakeident")) {
3328             if (!irccasecmp(argv[++i], "*")) {
3329                 discrim->fakeidentmask = 0;
3330             } else {
3331                 discrim->fakeidentmask = argv[i];
3332             }
3333         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
3334             if (!irccasecmp(argv[++i], "*")) {
3335                 discrim->handlemask = 0;
3336             } else {
3337                 discrim->handlemask = argv[i];
3338             }
3339         } else if (!irccasecmp(argv[i], "email")) {
3340             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
3341                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
3342                 goto fail;
3343             } else if (!irccasecmp(argv[++i], "*")) {
3344                 discrim->emailmask = 0;
3345             } else {
3346                 discrim->emailmask = argv[i];
3347             }
3348         } else if (!irccasecmp(argv[i], "access")) {
3349             const char *cmp = argv[++i];
3350             if (cmp[0] == '<') {
3351                 if (discrim->min_level == 0) discrim->min_level = 1;
3352                 if (cmp[1] == '=') {
3353                     discrim->max_level = strtoul(cmp+2, NULL, 0);
3354                 } else {
3355                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
3356                 }
3357             } else if (cmp[0] == '=') {
3358                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
3359             } else if (cmp[0] == '>') {
3360                 if (cmp[1] == '=') {
3361                     discrim->min_level = strtoul(cmp+2, NULL, 0);
3362                 } else {
3363                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
3364                 }
3365             } else {
3366                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3367             }
3368         } else if (!irccasecmp(argv[i], "karma")) {
3369             const char *cmp = argv[++i];
3370             if (cmp[0] == '<') {
3371                 if (cmp[1] == '=') {
3372                     discrim->max_karma = strtoul(cmp+2, NULL, 0);
3373                 } else {
3374                     discrim->max_karma = strtoul(cmp+1, NULL, 0) - 1;
3375                 }
3376             } else if (cmp[0] == '=') {
3377                 discrim->min_karma = discrim->max_karma = strtoul(cmp+1, NULL, 0);
3378             } else if (cmp[0] == '>') {
3379                 if (cmp[1] == '=') {
3380                     discrim->min_karma = strtoul(cmp+2, NULL, 0);
3381                 } else {
3382                     discrim->min_karma = strtoul(cmp+1, NULL, 0) + 1;
3383                 }
3384             } else {
3385                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3386             }
3387         } else {
3388             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
3389             goto fail;
3390         }
3391     }
3392     return discrim;
3393   fail:
3394     free(discrim);
3395     return NULL;
3396 }
3397
3398 static int
3399 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
3400 {
3401     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
3402         || (discrim->flags_off & hi->flags)
3403         || (discrim->min_registered > hi->registered)
3404         || (discrim->max_registered < hi->registered)
3405         || (discrim->lastseen < (hi->users?now:hi->lastseen))
3406         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
3407         || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
3408         || (discrim->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask)))
3409         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
3410         || (discrim->min_level > hi->opserv_level)
3411         || (discrim->max_level < hi->opserv_level)
3412         || (discrim->min_karma > hi->karma)
3413         || (discrim->max_karma < hi->karma)
3414         ) {
3415         return 0;
3416     }
3417     if (discrim->hostmask) {
3418         unsigned int i;
3419         for (i=0; i<hi->masks->used; i++) {
3420             const char *mask = hi->masks->list[i];
3421             if ((discrim->hostmask_type == SUBSET)
3422                 && (match_ircglobs(discrim->hostmask, mask))) break;
3423             else if ((discrim->hostmask_type == EXACT)
3424                      && !irccasecmp(discrim->hostmask, mask)) break;
3425             else if ((discrim->hostmask_type == SUPERSET)
3426                      && (match_ircglobs(mask, discrim->hostmask))) break;
3427             else if ((discrim->hostmask_type == LASTQUIT)
3428                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
3429         }
3430         if (i==hi->masks->used) return 0;
3431     }
3432     if (discrim->nickmask) {
3433         struct nick_info *nick = hi->nicks;
3434         while (nick) {
3435             if (match_ircglob(nick->nick, discrim->nickmask)) break;
3436             nick = nick->next;
3437         }
3438         if (!nick) return 0;
3439     }
3440     return 1;
3441 }
3442
3443 static unsigned int
3444 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
3445 {
3446     dict_iterator_t it, next;
3447     unsigned int matched;
3448
3449     for (it = dict_first(nickserv_handle_dict), matched = 0;
3450          it && (matched < discrim->limit);
3451          it = next) {
3452         next = iter_next(it);
3453         if (nickserv_discrim_match(discrim, iter_data(it))) {
3454             dsf(source, iter_data(it));
3455             matched++;
3456         }
3457     }
3458     return matched;
3459 }
3460
3461 static void
3462 search_print_func(struct userNode *source, struct handle_info *match)
3463 {
3464     send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
3465 }
3466
3467 static void
3468 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
3469 {
3470 }
3471
3472 static void
3473 search_unregister_func (struct userNode *source, struct handle_info *match)
3474 {
3475     if (oper_has_access(source, nickserv, match->opserv_level, 0))
3476         nickserv_unregister_handle(match, source);
3477 }
3478
3479 static int
3480 nickserv_sort_accounts_by_access(const void *a, const void *b)
3481 {
3482     const struct handle_info *hi_a = *(const struct handle_info**)a;
3483     const struct handle_info *hi_b = *(const struct handle_info**)b;
3484     if (hi_a->opserv_level != hi_b->opserv_level)
3485         return hi_b->opserv_level - hi_a->opserv_level;
3486     return irccasecmp(hi_a->handle, hi_b->handle);
3487 }
3488
3489 void
3490 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
3491 {
3492     struct handle_info_list hil;
3493     struct helpfile_table tbl;
3494     unsigned int ii;
3495     dict_iterator_t it;
3496     const char **ary;
3497
3498     memset(&hil, 0, sizeof(hil));
3499     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3500         struct handle_info *hi = iter_data(it);
3501         if (hi->opserv_level)
3502             handle_info_list_append(&hil, hi);
3503     }
3504     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
3505     tbl.length = hil.used + 1;
3506     tbl.width = 2;
3507     tbl.flags = TABLE_NO_FREE;
3508     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
3509     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
3510     ary[0] = "Account";
3511     ary[1] = "Level";
3512     for (ii = 0; ii < hil.used; ) {
3513         ary = malloc(tbl.width * sizeof(ary[0]));
3514         ary[0] = hil.list[ii]->handle;
3515         ary[1] = strtab(hil.list[ii]->opserv_level);
3516         tbl.contents[++ii] = ary;
3517     }
3518     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3519     reply("MSG_MATCH_COUNT", hil.used);
3520     for (ii = 0; ii < hil.used; ii++)
3521         free(tbl.contents[ii]);
3522     free(tbl.contents);
3523     free(hil.list);
3524 }
3525
3526 static NICKSERV_FUNC(cmd_search)
3527 {
3528     struct nickserv_discrim *discrim;
3529     discrim_search_func action;
3530     struct svccmd *subcmd;
3531     unsigned int matches;
3532     char buf[MAXLEN];
3533
3534     NICKSERV_MIN_PARMS(3);
3535     sprintf(buf, "search %s", argv[1]);
3536     subcmd = dict_find(nickserv_service->commands, buf, NULL);
3537     if (!irccasecmp(argv[1], "print"))
3538         action = search_print_func;
3539     else if (!irccasecmp(argv[1], "count"))
3540         action = search_count_func;
3541     else if (!irccasecmp(argv[1], "unregister"))
3542         action = search_unregister_func;
3543     else {
3544         reply("NSMSG_INVALID_ACTION", argv[1]);
3545         return 0;
3546     }
3547
3548     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
3549         return 0;
3550
3551     discrim = nickserv_discrim_create(user, argc-2, argv+2);
3552     if (!discrim)
3553         return 0;
3554
3555     if (action == search_print_func)
3556         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
3557     else if (action == search_count_func)
3558         discrim->limit = INT_MAX;
3559
3560     matches = nickserv_discrim_search(discrim, action, user);
3561
3562     if (matches)
3563         reply("MSG_MATCH_COUNT", matches);
3564     else
3565         reply("MSG_NO_MATCHES");
3566
3567     free(discrim);
3568     return 0;
3569 }
3570
3571 static MODCMD_FUNC(cmd_checkpass)
3572 {
3573     struct handle_info *hi;
3574
3575     NICKSERV_MIN_PARMS(3);
3576     if (!(hi = get_handle_info(argv[1]))) {
3577         reply("MSG_HANDLE_UNKNOWN", argv[1]);
3578         return 0;
3579     }
3580     if (checkpass(argv[2], hi->passwd))
3581         reply("CHECKPASS_YES");
3582     else
3583         reply("CHECKPASS_NO");
3584     argv[2] = "****";
3585     return 1;
3586 }
3587
3588 static MODCMD_FUNC(cmd_checkemail)
3589 {
3590     struct handle_info *hi;
3591
3592     NICKSERV_MIN_PARMS(3);
3593     if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
3594         return 0;
3595     }
3596     if (!hi->email_addr)
3597         reply("CHECKEMAIL_NOT_SET");
3598     else if (!irccasecmp(argv[2], hi->email_addr))
3599         reply("CHECKEMAIL_YES");
3600     else
3601         reply("CHECKEMAIL_NO");
3602     return 1;
3603 }
3604
3605
3606 static void
3607 nickserv_db_read_handle(const char *handle, dict_t obj)
3608 {
3609     const char *str;
3610     struct string_list *masks, *slist;
3611     struct handle_info *hi;
3612     struct userNode *authed_users;
3613     struct userData *channel_list;
3614     unsigned long id;
3615     unsigned int ii;
3616     dict_t subdb;
3617
3618     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
3619     id = str ? strtoul(str, NULL, 0) : 0;
3620     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
3621     if (!str) {
3622         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
3623         return;
3624     }
3625     if ((hi = get_handle_info(handle))) {
3626         authed_users = hi->users;
3627         channel_list = hi->channels;
3628         hi->users = NULL;
3629         hi->channels = NULL;
3630         dict_remove(nickserv_handle_dict, hi->handle);
3631     } else {
3632         authed_users = NULL;
3633         channel_list = NULL;
3634     }
3635     hi = register_handle(handle, str, id);
3636     if (authed_users) {
3637         hi->users = authed_users;
3638         while (authed_users) {
3639             authed_users->handle_info = hi;
3640             authed_users = authed_users->next_authed;
3641         }
3642     }
3643     hi->channels = channel_list;
3644     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
3645     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
3646     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
3647     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
3648     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
3649     hi->language = language_find(str ? str : "C");
3650     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
3651     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
3652     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
3653     if (str)
3654         hi->infoline = strdup(str);
3655     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
3656     hi->registered = str ? strtoul(str, NULL, 0) : now;
3657     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
3658     hi->lastseen = str ? strtoul(str, NULL, 0) : hi->registered;
3659     str = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
3660     hi->karma = str ? strtoul(str, NULL, 0) : 0;
3661     /* We want to read the nicks even if disable_nicks is set.  This is so
3662      * that we don't lose the nick data entirely. */
3663     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
3664     if (slist) {
3665         for (ii=0; ii<slist->used; ii++)
3666             register_nick(slist->list[ii], hi);
3667     }
3668     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
3669     if (str) {
3670         for (ii=0; str[ii]; ii++)
3671             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
3672     }
3673     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
3674     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
3675     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
3676     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
3677     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
3678     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
3679     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
3680     if (!str)
3681         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
3682     if (str)
3683         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
3684     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
3685     if (str)
3686         nickserv_set_email_addr(hi, str);
3687     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
3688     if (str)
3689         hi->epithet = strdup(str);
3690     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
3691     if (str)
3692         hi->fakehost = strdup(str);
3693     str = database_get_data(obj, KEY_FAKEIDENT, RECDB_QSTRING);
3694     if (str)
3695         hi->fakeident = strdup(str);
3696     /* Read the "cookie" sub-database (if it exists). */
3697     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
3698     if (subdb) {
3699         const char *data, *type, *expires, *cookie_str;
3700         struct handle_cookie *cookie;
3701
3702         cookie = calloc(1, sizeof(*cookie));
3703         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
3704         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
3705         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
3706         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
3707         if (!type || !expires || !cookie_str) {
3708             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
3709             goto cookie_out;
3710         }
3711         if (!irccasecmp(type, KEY_ACTIVATION))
3712             cookie->type = ACTIVATION;
3713         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
3714             cookie->type = PASSWORD_CHANGE;
3715         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
3716             cookie->type = EMAIL_CHANGE;
3717         else if (!irccasecmp(type, KEY_ALLOWAUTH))
3718             cookie->type = ALLOWAUTH;
3719         else {
3720             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
3721             goto cookie_out;
3722         }
3723         cookie->expires = strtoul(expires, NULL, 0);
3724         if (cookie->expires < now)
3725             goto cookie_out;
3726         if (data)
3727             cookie->data = strdup(data);
3728         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
3729         cookie->hi = hi;
3730       cookie_out:
3731         if (cookie->hi)
3732             nickserv_bake_cookie(cookie);
3733         else
3734             nickserv_free_cookie(cookie);
3735     }
3736     /* Read the "notes" sub-database (if it exists). */
3737     subdb = database_get_data(obj, KEY_NOTES, RECDB_OBJECT);
3738     if (subdb) {
3739         dict_iterator_t it;
3740         struct handle_note *last_note;
3741         struct handle_note *note;
3742
3743         last_note = NULL;
3744         for (it = dict_first(subdb); it; it = iter_next(it)) {
3745             const char *expires;
3746             const char *setter;
3747             const char *text;
3748             const char *set;
3749             const char *note_id;
3750             dict_t notedb;
3751
3752             note_id = iter_key(it);
3753             notedb = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
3754             if (!notedb) {
3755                 log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", note_id, hi->handle);
3756                 continue;
3757             }
3758             expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
3759             setter = database_get_data(notedb, KEY_NOTE_SETTER, RECDB_QSTRING);
3760             text = database_get_data(notedb, KEY_NOTE_NOTE, RECDB_QSTRING);
3761             set = database_get_data(notedb, KEY_NOTE_SET, RECDB_QSTRING);
3762             if (!setter || !text || !set) {
3763                 log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", note_id, hi->handle);
3764                 continue;
3765             }
3766             note = calloc(1, sizeof(*note) + strlen(text));
3767             note->next = NULL;
3768             note->expires = expires ? strtoul(expires, NULL, 10) : 0;
3769             note->set = strtoul(set, NULL, 10);
3770             note->id = strtoul(note_id, NULL, 10);
3771             safestrncpy(note->setter, setter, sizeof(note->setter));
3772             strcpy(note->note, text);
3773             if (last_note)
3774                 last_note->next = note;
3775             else
3776                 hi->notes = note;
3777             last_note = note;
3778         }
3779     }
3780 }
3781
3782 static int
3783 nickserv_saxdb_read(dict_t db) {
3784     dict_iterator_t it;
3785     struct record_data *rd;
3786
3787     for (it=dict_first(db); it; it=iter_next(it)) {
3788         rd = iter_data(it);
3789         nickserv_db_read_handle(iter_key(it), rd->d.object);
3790     }
3791     return 0;
3792 }
3793
3794 static NICKSERV_FUNC(cmd_mergedb)
3795 {
3796     struct timeval start, stop;
3797     dict_t db;
3798
3799     NICKSERV_MIN_PARMS(2);
3800     gettimeofday(&start, NULL);
3801     if (!(db = parse_database(argv[1]))) {
3802         reply("NSMSG_DB_UNREADABLE", argv[1]);
3803         return 0;
3804     }
3805     nickserv_saxdb_read(db);
3806     free_database(db);
3807     gettimeofday(&stop, NULL);
3808     stop.tv_sec -= start.tv_sec;
3809     stop.tv_usec -= start.tv_usec;
3810     if (stop.tv_usec < 0) {
3811         stop.tv_sec -= 1;
3812         stop.tv_usec += 1000000;
3813     }
3814     reply("NSMSG_DB_MERGED", argv[1], (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
3815     return 1;
3816 }
3817
3818 static void
3819 expire_handles(UNUSED_ARG(void *data))
3820 {
3821     dict_iterator_t it, next;
3822     unsigned long expiry;
3823     struct handle_info *hi;
3824
3825     for (it=dict_first(nickserv_handle_dict); it; it=next) {
3826         next = iter_next(it);
3827         hi = iter_data(it);
3828         if ((hi->opserv_level > 0)
3829             || hi->users
3830             || HANDLE_FLAGGED(hi, FROZEN)
3831             || HANDLE_FLAGGED(hi, NODELETE)) {
3832             continue;
3833         }
3834         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
3835         if ((now - hi->lastseen) > expiry) {
3836             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
3837             nickserv_unregister_handle(hi, NULL);
3838         }
3839     }
3840
3841     if (nickserv_conf.handle_expire_frequency)
3842         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3843 }
3844
3845 static void
3846 nickserv_load_dict(const char *fname)
3847 {
3848     FILE *file;
3849     char line[128];
3850     if (!(file = fopen(fname, "r"))) {
3851         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
3852         return;
3853     }
3854     while (fgets(line, sizeof(line), file)) {
3855         if (!line[0])
3856             continue;
3857         if (line[strlen(line)-1] == '\n')
3858             line[strlen(line)-1] = 0;
3859         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
3860     }
3861     fclose(file);
3862     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
3863 }
3864
3865 static enum reclaim_action
3866 reclaim_action_from_string(const char *str) {
3867     if (!str)
3868         return RECLAIM_NONE;
3869     else if (!irccasecmp(str, "warn"))
3870         return RECLAIM_WARN;
3871     else if (!irccasecmp(str, "svsnick"))
3872         return RECLAIM_SVSNICK;
3873     else if (!irccasecmp(str, "kill"))
3874         return RECLAIM_KILL;
3875     else
3876         return RECLAIM_NONE;
3877 }
3878
3879 static void
3880 nickserv_conf_read(void)
3881 {
3882     dict_t conf_node, child;
3883     const char *str;
3884     dict_iterator_t it;
3885
3886     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
3887         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
3888         return;
3889     }
3890     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
3891     if (!str)
3892         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
3893     if (nickserv_conf.valid_handle_regex_set)
3894         regfree(&nickserv_conf.valid_handle_regex);
3895     if (str) {
3896         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3897         nickserv_conf.valid_handle_regex_set = !err;
3898         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
3899     } else {
3900         nickserv_conf.valid_handle_regex_set = 0;
3901     }
3902     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
3903     if (nickserv_conf.valid_nick_regex_set)
3904         regfree(&nickserv_conf.valid_nick_regex);
3905     if (str) {
3906         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3907         nickserv_conf.valid_nick_regex_set = !err;
3908         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
3909     } else {
3910         nickserv_conf.valid_nick_regex_set = 0;
3911     }
3912     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
3913     if (!str)
3914         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
3915     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
3916     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
3917     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
3918     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
3919     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
3920     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
3921     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
3922     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
3923     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
3924     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
3925     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
3926     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
3927     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
3928     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
3929     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3930     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
3931     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
3932     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
3933     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
3934     str = database_get_data(conf_node, KEY_SET_TITLE_LEVEL, RECDB_QSTRING);
3935     nickserv_conf.set_title_level = str ? strtoul(str, NULL, 0) : 900;
3936     str = database_get_data(conf_node, KEY_SET_FAKEHOST_LEVEL, RECDB_QSTRING);
3937     nickserv_conf.set_fakehost_level = str ? strtoul(str, NULL, 0) : 1000;
3938     str = database_get_data(conf_node, KEY_SET_FAKEIDENT_LEVEL, RECDB_QSTRING);
3939     nickserv_conf.set_fakeident_level = str ? strtoul(str, NULL, 0) : 1000;
3940     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
3941     if (!str)
3942         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
3943     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
3944     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3945     if (!str)
3946         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3947     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
3948     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3949     if (!str)
3950         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3951     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
3952     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
3953     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
3954     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
3955     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
3956     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
3957     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
3958     str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
3959     nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
3960     str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
3961     if (!str)
3962         str = "ShgsfnHbu";
3963     nickserv_conf.ounregister_flags = 0;
3964     while(*str) {
3965         unsigned int pos = handle_inverse_flags[(unsigned char)*str];
3966         str++;
3967         if(pos)
3968             nickserv_conf.ounregister_flags |= 1 << (pos - 1);
3969     }
3970     str = database_get_data(conf_node, KEY_HANDLE_TS_MODE, RECDB_QSTRING);
3971     if (!str)
3972         nickserv_conf.handle_ts_mode = TS_IGNORE;
3973     else if (!irccasecmp(str, "ircu"))
3974         nickserv_conf.handle_ts_mode = TS_IRCU;
3975     else
3976         nickserv_conf.handle_ts_mode = TS_IGNORE;
3977     if (!nickserv_conf.disable_nicks) {
3978         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
3979         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3980         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
3981         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
3982         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
3983         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3984         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
3985         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
3986     }
3987     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
3988     for (it=dict_first(child); it; it=iter_next(it)) {
3989         const char *key = iter_key(it), *value;
3990         unsigned char flag;
3991         int pos;
3992
3993         if (!strncasecmp(key, "uc_", 3))
3994             flag = toupper(key[3]);
3995         else if (!strncasecmp(key, "lc_", 3))
3996             flag = tolower(key[3]);
3997         else
3998             flag = key[0];
3999
4000         if ((pos = handle_inverse_flags[flag])) {
4001             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
4002             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
4003         }
4004     }
4005     if (nickserv_conf.weak_password_dict)
4006         dict_delete(nickserv_conf.weak_password_dict);
4007     nickserv_conf.weak_password_dict = dict_new();
4008     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
4009     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
4010     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
4011     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
4012     if (str)
4013         nickserv_load_dict(str);
4014     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
4015     if (nickserv && str)
4016         NickChange(nickserv, str, 0);
4017     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
4018     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
4019     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
4020     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
4021     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
4022     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
4023     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
4024     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
4025     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
4026     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
4027     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
4028     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
4029     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
4030     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
4031     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
4032     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
4033     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
4034     nickserv_conf.titlehost_suffix = str ? str : "example.net";
4035     str = conf_get_data("server/network", RECDB_QSTRING);
4036     nickserv_conf.network_name = str ? str : "some IRC network";
4037     if (!nickserv_conf.auth_policer_params) {
4038         nickserv_conf.auth_policer_params = policer_params_new();
4039         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
4040         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
4041     }
4042     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
4043     for (it=dict_first(child); it; it=iter_next(it))
4044         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
4045 }
4046
4047 static void
4048 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
4049     const char *msg;
4050     char newnick[NICKLEN+1];
4051
4052     assert(user);
4053     assert(ni);
4054     switch (action) {
4055     case RECLAIM_NONE:
4056         /* do nothing */
4057         break;
4058     case RECLAIM_WARN:
4059         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
4060         break;
4061     case RECLAIM_SVSNICK:
4062         do {
4063             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
4064         } while (GetUserH(newnick));
4065         irc_svsnick(nickserv, user, newnick);
4066         break;
4067     case RECLAIM_KILL:
4068         msg = user_find_message(user, "NSMSG_RECLAIM_KILL");
4069         DelUser(user, nickserv, 1, msg);
4070         break;
4071     }
4072 }
4073
4074 static void
4075 nickserv_reclaim_p(void *data) {
4076     struct userNode *user = data;
4077     struct nick_info *ni = get_nick_info(user->nick);
4078     if (ni)
4079         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
4080 }
4081
4082 static void
4083 check_user_nick(struct userNode *user) {
4084     struct nick_info *ni;
4085     user->modes &= ~FLAGS_REGNICK;
4086     if (!(ni = get_nick_info(user->nick)))
4087         return;
4088     if (user->handle_info == ni->owner) {
4089         user->modes |= FLAGS_REGNICK;
4090         irc_regnick(user);
4091         return;
4092     }
4093     if (nickserv_conf.warn_nick_owned)
4094         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
4095     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
4096         return;
4097     if (nickserv_conf.auto_reclaim_delay)
4098         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
4099     else
4100         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
4101 }
4102
4103 void
4104 handle_account(struct userNode *user, const char *stamp, unsigned long timestamp, unsigned long serial)
4105 {
4106     struct handle_info *hi = NULL;
4107
4108     if (stamp != NULL)
4109         hi = dict_find(nickserv_handle_dict, stamp, NULL);
4110     if ((hi == NULL) && (serial != 0)) {
4111         char id[IDLEN + 1];
4112         inttobase64(id, serial, IDLEN);
4113         hi = dict_find(nickserv_id_dict, id, NULL);
4114     }
4115
4116     if (hi) {
4117         if ((nickserv_conf.handle_ts_mode == TS_IRCU)
4118             && (timestamp != hi->registered)) {
4119             return;
4120         }
4121         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
4122             return;
4123         }
4124         set_user_handle_info(user, hi, 0);
4125     } else {
4126         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s:%lu:%lu.", user->nick, stamp, timestamp, serial);
4127     }
4128 }
4129
4130 void
4131 handle_nick_change(struct userNode *user, const char *old_nick)
4132 {
4133     struct handle_info *hi;
4134
4135     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
4136         dict_remove(nickserv_allow_auth_dict, old_nick);
4137         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
4138     }
4139     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4140     check_user_nick(user);
4141 }
4142
4143 void
4144 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
4145 {
4146     dict_remove(nickserv_allow_auth_dict, user->nick);
4147     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4148     set_user_handle_info(user, NULL, 0);
4149 }
4150
4151 static struct modcmd *
4152 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
4153 {
4154     if (min_level > 0) {
4155         char buf[16];
4156         sprintf(buf, "%u", min_level);
4157         if (must_be_qualified) {
4158             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
4159         } else {
4160             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
4161         }
4162     } else if (min_level == 0) {
4163         if (must_be_qualified) {
4164             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4165         } else {
4166             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4167         }
4168     } else {
4169         if (must_be_qualified) {
4170             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
4171         } else {
4172             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
4173         }
4174     }
4175 }
4176
4177 static void
4178 nickserv_db_cleanup(void)
4179 {
4180     unreg_del_user_func(nickserv_remove_user);
4181     userList_clean(&curr_helpers);
4182     policer_params_delete(nickserv_conf.auth_policer_params);
4183     dict_delete(nickserv_handle_dict);
4184     dict_delete(nickserv_nick_dict);
4185     dict_delete(nickserv_opt_dict);
4186     dict_delete(nickserv_allow_auth_dict);
4187     dict_delete(nickserv_email_dict);
4188     dict_delete(nickserv_id_dict);
4189     dict_delete(nickserv_conf.weak_password_dict);
4190     free(auth_func_list);
4191     free(unreg_func_list);
4192     free(rf_list);
4193     free(allowauth_func_list);
4194     free(handle_merge_func_list);
4195     free(failpw_func_list);
4196     if (nickserv_conf.valid_handle_regex_set)
4197         regfree(&nickserv_conf.valid_handle_regex);
4198     if (nickserv_conf.valid_nick_regex_set)
4199         regfree(&nickserv_conf.valid_nick_regex);
4200 }
4201
4202 void
4203 init_nickserv(const char *nick)
4204 {
4205     unsigned int i;
4206     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
4207     reg_new_user_func(check_user_nick);
4208     reg_nick_change_func(handle_nick_change);
4209     reg_del_user_func(nickserv_remove_user);
4210     reg_account_func(handle_account);
4211
4212     /* set up handle_inverse_flags */
4213     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
4214     for (i=0; handle_flags[i]; i++) {
4215         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
4216         flag_access_levels[i] = 0;
4217     }
4218
4219     conf_register_reload(nickserv_conf_read);
4220     nickserv_opt_dict = dict_new();
4221     nickserv_email_dict = dict_new();
4222     dict_set_free_keys(nickserv_email_dict, free);
4223     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
4224
4225     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
4226     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
4227     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
4228     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
4229     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
4230     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
4231     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
4232     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
4233     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
4234     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
4235     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
4236     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
4237     nickserv_define_func("SET", cmd_set, -1, 1, 0);
4238     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
4239     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
4240     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
4241     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
4242     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
4243     nickserv_define_func("MERGE", cmd_merge, 750, 1, 0);
4244     nickserv_define_func("ADDNOTE", cmd_addnote, 0, 1, 0);
4245     nickserv_define_func("DELNOTE", cmd_delnote, 0, 1, 0);
4246     nickserv_define_func("NOTES", cmd_notes, 0, 1, 0);
4247     if (!nickserv_conf.disable_nicks) {
4248         /* nick management commands */
4249         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
4250         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
4251         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
4252         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
4253         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
4254         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
4255     }
4256     if (nickserv_conf.email_enabled) {
4257         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
4258         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
4259         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
4260         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
4261         nickserv_define_func("ODELCOOKIE", cmd_odelcookie, 0, 1, 0);
4262         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
4263     }
4264     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
4265     /* miscellaneous commands */
4266     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
4267     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
4268     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
4269     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
4270     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
4271     nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0);
4272     /* other options */
4273     dict_insert(nickserv_opt_dict, "INFO", opt_info);
4274     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
4275     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
4276     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
4277     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
4278     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
4279     dict_insert(nickserv_opt_dict, "PASS", opt_password);
4280     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
4281     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
4282     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
4283     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
4284     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
4285     if (nickserv_conf.titlehost_suffix) {
4286         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
4287         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
4288         dict_insert(nickserv_opt_dict, "FAKEIDENT", opt_fakeident);
4289     }
4290     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
4291     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
4292     dict_insert(nickserv_opt_dict, "KARMA", opt_karma);
4293     nickserv_define_func("OSET KARMA", NULL, 0, 1, 0);
4294
4295     nickserv_handle_dict = dict_new();
4296     dict_set_free_keys(nickserv_handle_dict, free);
4297     dict_set_free_data(nickserv_handle_dict, free_handle_info);
4298
4299     nickserv_id_dict = dict_new();
4300     dict_set_free_keys(nickserv_id_dict, free);
4301
4302     nickserv_nick_dict = dict_new();
4303     dict_set_free_data(nickserv_nick_dict, free);
4304
4305     nickserv_allow_auth_dict = dict_new();
4306
4307     userList_init(&curr_helpers);
4308
4309     if (nick) {
4310         const char *modes = conf_get_data("services/nickserv/modes", RECDB_QSTRING);
4311         nickserv = AddLocalUser(nick, nick, NULL, "Nick Services", modes);
4312         nickserv_service = service_register(nickserv);
4313     }
4314     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
4315     reg_exit_func(nickserv_db_cleanup);
4316     if(nickserv_conf.handle_expire_frequency)
4317         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
4318     message_register_table(msgtab);
4319 }