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