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