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