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