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