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