17f9a15c8dc3a0e9d9ffab109791919adad633af
[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 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 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 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, UNUSED_ARG(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             if(user)
2425                 send_message(user, nickserv, "NSMSG_ADDMASK_ALREADY", new_mask);
2426             free(new_mask);
2427             return 0;
2428         }
2429     }
2430     string_list_append(hi->masks, new_mask);
2431     if(user)
2432         send_message(user, nickserv, "NSMSG_ADDMASK_SUCCESS", new_mask);
2433     return 1;
2434 }
2435
2436 static NICKSERV_FUNC(cmd_addmask)
2437 {
2438     if (argc < 2) {
2439         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
2440         int res = nickserv_addmask(user, user->handle_info, mask);
2441         free(mask);
2442         return res;
2443     } else {
2444         if (!is_gline(argv[1])) {
2445             reply("NSMSG_MASK_INVALID", argv[1]);
2446             return 0;
2447         }
2448         return nickserv_addmask(user, user->handle_info, argv[1]);
2449     }
2450 }
2451
2452 static NICKSERV_FUNC(cmd_oaddmask)
2453 {
2454     struct handle_info *hi;
2455
2456     NICKSERV_MIN_PARMS(3);
2457     if (!(hi = get_victim_oper(user, argv[1])))
2458         return 0;
2459     return nickserv_addmask(user, hi, argv[2]);
2460 }
2461
2462 static int
2463 nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask, int force)
2464 {
2465     unsigned int i;
2466     for (i=0; i<hi->masks->used; i++) {
2467         if (!strcmp(del_mask, hi->masks->list[i])) {
2468             char *old_mask = hi->masks->list[i];
2469             if (hi->masks->used == 1 && !force) {
2470                 send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
2471                 return 0;
2472             }
2473             hi->masks->list[i] = hi->masks->list[--hi->masks->used];
2474             send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
2475             free(old_mask);
2476             return 1;
2477         }
2478     }
2479     send_message(user, nickserv, "NSMSG_DELMASK_NOT_FOUND");
2480     return 0;
2481 }
2482
2483 static NICKSERV_FUNC(cmd_delmask)
2484 {
2485     NICKSERV_MIN_PARMS(2);
2486     return nickserv_delmask(user, user->handle_info, argv[1], 0);
2487 }
2488
2489 static NICKSERV_FUNC(cmd_odelmask)
2490 {
2491     struct handle_info *hi;
2492     NICKSERV_MIN_PARMS(3);
2493     if (!(hi = get_victim_oper(user, argv[1])))
2494         return 0;
2495     return nickserv_delmask(user, hi, argv[2], 1);
2496 }
2497
2498 int
2499 nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *padded, unsigned long *premoved) {
2500     unsigned int nn, add = 1, pos;
2501     unsigned long added, removed, flag;
2502
2503     for (added=removed=nn=0; str[nn]; nn++) {
2504         switch (str[nn]) {
2505         case '+': add = 1; break;
2506         case '-': add = 0; break;
2507         default:
2508             if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
2509                 send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
2510                 return 0;
2511             }
2512             if (user && (user->handle_info->opserv_level < flag_access_levels[pos-1])) {
2513                 /* cheesy avoidance of looking up the flag name.. */
2514                 send_message(user, bot, "NSMSG_FLAG_PRIVILEGED", str[nn]);
2515                 return 0;
2516             }
2517             flag = 1 << (pos - 1);
2518             if (add)
2519                 added |= flag, removed &= ~flag;
2520             else
2521                 removed |= flag, added &= ~flag;
2522             break;
2523         }
2524     }
2525     *padded = added;
2526     *premoved = removed;
2527     return 1;
2528 }
2529
2530 static int
2531 nickserv_apply_flags(struct userNode *user, struct handle_info *hi, const char *flags)
2532 {
2533     unsigned long before, after, added, removed;
2534     struct userNode *uNode;
2535
2536     before = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2537     if (!nickserv_modify_handle_flags(user, nickserv, flags, &added, &removed))
2538         return 0;
2539     hi->flags = (hi->flags | added) & ~removed;
2540     after = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2541
2542     /* Strip helping flag if they're only a support helper and not
2543      * currently in #support. */
2544     if (HANDLE_FLAGGED(hi, HELPING) && (after == HI_FLAG_SUPPORT_HELPER)) {
2545         struct channelList *schannels;
2546         unsigned int ii;
2547         schannels = chanserv_support_channels();
2548         for (ii = 0; ii < schannels->used; ++ii)
2549             if (find_handle_in_channel(schannels->list[ii], hi, NULL))
2550                 break;
2551         if (ii == schannels->used)
2552             HANDLE_CLEAR_FLAG(hi, HELPING);
2553     }
2554
2555     if (after && !before) {
2556         /* Add user to current helper list. */
2557         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2558             userList_append(&curr_helpers, uNode);
2559     } else if (!after && before) {
2560         /* Remove user from current helper list. */
2561         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2562             userList_remove(&curr_helpers, uNode);
2563     }
2564
2565     return 1;
2566 }
2567
2568 static void
2569 set_list(struct userNode *user, struct handle_info *hi, int override)
2570 {
2571     option_func_t *opt;
2572     unsigned int i;
2573     char *set_display[] = {
2574         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
2575         "EMAIL", "MAXLOGINS", "LANGUAGE", "DEVNULL"
2576     };
2577
2578     send_message(user, nickserv, "NSMSG_SETTING_LIST");
2579
2580     /* Do this so options are presented in a consistent order. */
2581     for (i = 0; i < ArrayLength(set_display); ++i)
2582         if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
2583             opt(user, hi, override, 0, NULL);
2584 }
2585
2586 static NICKSERV_FUNC(cmd_set)
2587 {
2588     struct handle_info *hi;
2589     option_func_t *opt;
2590
2591     hi = user->handle_info;
2592     if (argc < 2) {
2593         set_list(user, hi, 0);
2594         return 1;
2595     }
2596     if (!(opt = dict_find(nickserv_opt_dict, argv[1], NULL))) {
2597         reply("NSMSG_INVALID_OPTION", argv[1]);
2598         return 0;
2599     }
2600     return opt(user, hi, 0, argc-1, argv+1);
2601 }
2602
2603 static NICKSERV_FUNC(cmd_oset)
2604 {
2605     struct handle_info *hi;
2606     struct svccmd *subcmd;
2607     option_func_t *opt;
2608     char cmdname[MAXLEN];
2609
2610     NICKSERV_MIN_PARMS(2);
2611
2612     if (!(hi = get_victim_oper(user, argv[1])))
2613         return 0;
2614
2615     if (argc < 3) {
2616         set_list(user, hi, 0);
2617         return 1;
2618     }
2619
2620     if (!(opt = dict_find(nickserv_opt_dict, argv[2], NULL))) {
2621         reply("NSMSG_INVALID_OPTION", argv[2]);
2622         return 0;
2623     }
2624
2625     sprintf(cmdname, "%s %s", cmd->name, argv[2]);
2626     subcmd = dict_find(cmd->parent->commands, cmdname, NULL);
2627     if (subcmd && !svccmd_can_invoke(user, cmd->parent->bot, subcmd, NULL, SVCCMD_NOISY))
2628         return 0;
2629
2630     return opt(user, hi, 1, argc-2, argv+2);
2631 }
2632
2633 static OPTION_FUNC(opt_info)
2634 {
2635     const char *info;
2636     if (argc > 1) {
2637         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2638             free(hi->infoline);
2639             hi->infoline = NULL;
2640         } else {
2641             hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
2642         }
2643     }
2644
2645     info = hi->infoline ? hi->infoline : user_find_message(user, "MSG_NONE");
2646     send_message(user, nickserv, "NSMSG_SET_INFO", info);
2647     return 1;
2648 }
2649
2650 static OPTION_FUNC(opt_devnull)
2651 {
2652     const char *devnull;
2653     
2654     if (argc > 1) {
2655         if (!override) {
2656             send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2657             return 0;
2658         }
2659         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2660             free(hi->devnull);
2661             hi->devnull = NULL;
2662         } else {
2663             devnull = unsplit_string(argv+1, argc-1, NULL);
2664             if(devnull_check_name(devnull)) { 
2665                 if(hi->devnull)
2666                     free(hi->devnull);
2667                 hi->devnull = strdup(devnull);
2668             }
2669         }
2670     }
2671
2672     devnull = hi->devnull ? hi->devnull : user_find_message(user, "MSG_NONE");
2673     send_message(user, nickserv, "NSMSG_SET_DEVNULL", devnull);
2674     return 1;
2675 }
2676
2677 void nickserv_devnull_delete(char *name) {
2678     dict_iterator_t it;
2679     struct handle_info *hi;
2680
2681     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2682         hi = iter_data(it);
2683         if (hi->devnull && !irccasecmp(name, hi->devnull)) {
2684             free(hi->devnull);
2685             hi->devnull = NULL;
2686         }
2687     }
2688 }
2689
2690 void nickserv_devnull_rename(char *oldname, char *newname) {
2691     dict_iterator_t it;
2692     struct handle_info *hi;
2693
2694     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2695         hi = iter_data(it);
2696         if (hi->devnull && !irccasecmp(oldname, hi->devnull)) {
2697             hi->devnull = strdup(newname);
2698         }
2699     }
2700 }
2701
2702 static OPTION_FUNC(opt_website)
2703 {
2704     const char *website;
2705     
2706     if (argc > 1) {
2707         if (!HANDLE_FLAGGED(user->handle_info, BOT)) {
2708             send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2709             return 0;
2710         }
2711         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2712             free(hi->website);
2713             hi->website = NULL;
2714         } else {
2715             website = unsplit_string(argv+1, argc-1, NULL);
2716             hi->website = strdup(website);
2717         }
2718     }
2719     if (HANDLE_FLAGGED(user->handle_info, BOT)) {
2720         website = hi->website ? hi->website : user_find_message(user, "MSG_NONE");
2721         send_message(user, nickserv, "NSMSG_SET_WEBSITE", website);
2722     }
2723     return 1;
2724 }
2725
2726 static OPTION_FUNC(opt_width)
2727 {
2728     if (argc > 1)
2729         hi->screen_width = strtoul(argv[1], NULL, 0);
2730
2731     if ((hi->screen_width > 0) && (hi->screen_width < MIN_LINE_SIZE))
2732         hi->screen_width = MIN_LINE_SIZE;
2733     else if (hi->screen_width > MAX_LINE_SIZE)
2734         hi->screen_width = MAX_LINE_SIZE;
2735
2736     send_message(user, nickserv, "NSMSG_SET_WIDTH", hi->screen_width);
2737     return 1;
2738 }
2739
2740 static OPTION_FUNC(opt_tablewidth)
2741 {
2742     if (argc > 1)
2743         hi->table_width = strtoul(argv[1], NULL, 0);
2744
2745     if ((hi->table_width > 0) && (hi->table_width < MIN_LINE_SIZE))
2746         hi->table_width = MIN_LINE_SIZE;
2747     else if (hi->screen_width > MAX_LINE_SIZE)
2748         hi->table_width = MAX_LINE_SIZE;
2749
2750     send_message(user, nickserv, "NSMSG_SET_TABLEWIDTH", hi->table_width);
2751     return 1;
2752 }
2753
2754 static OPTION_FUNC(opt_color)
2755 {
2756     if (argc > 1) {
2757         if (enabled_string(argv[1]))
2758             HANDLE_SET_FLAG(hi, MIRC_COLOR);
2759         else if (disabled_string(argv[1]))
2760             HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
2761         else {
2762             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2763             return 0;
2764         }
2765     }
2766
2767     send_message(user, nickserv, "NSMSG_SET_COLOR", user_find_message(user, HANDLE_FLAGGED(hi, MIRC_COLOR) ? "MSG_ON" : "MSG_OFF"));
2768     return 1;
2769 }
2770
2771 static OPTION_FUNC(opt_privmsg)
2772 {
2773     if (argc > 1) {
2774         if (enabled_string(argv[1]))
2775             HANDLE_SET_FLAG(hi, USE_PRIVMSG);
2776         else if (disabled_string(argv[1]))
2777             HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
2778         else {
2779             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2780             return 0;
2781         }
2782     }
2783
2784     send_message(user, nickserv, "NSMSG_SET_PRIVMSG", user_find_message(user, HANDLE_FLAGGED(hi, USE_PRIVMSG) ? "MSG_ON" : "MSG_OFF"));
2785     return 1;
2786 }
2787
2788 static OPTION_FUNC(opt_autohide)
2789 {
2790     if (argc > 1) {
2791         if (enabled_string(argv[1]))
2792             HANDLE_SET_FLAG(hi, AUTOHIDE);
2793         else if (disabled_string(argv[1]))
2794             HANDLE_CLEAR_FLAG(hi, AUTOHIDE);
2795         else {
2796             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2797             return 0;
2798         }
2799     }
2800
2801     send_message(user, nickserv, "NSMSG_SET_AUTOHIDE", user_find_message(user, HANDLE_FLAGGED(hi, AUTOHIDE) ? "MSG_ON" : "MSG_OFF"));
2802     return 1;
2803 }
2804
2805 static OPTION_FUNC(opt_style)
2806 {
2807     char *style;
2808
2809     if (argc > 1) {
2810         if (!irccasecmp(argv[1], "Zoot"))
2811             hi->userlist_style = HI_STYLE_ZOOT;
2812         else if (!irccasecmp(argv[1], "def"))
2813             hi->userlist_style = HI_STYLE_DEF;
2814     }
2815
2816     switch (hi->userlist_style) {
2817     case HI_STYLE_DEF:
2818         style = "def";
2819         break;
2820     case HI_STYLE_ZOOT:
2821     default:
2822         style = "Zoot";
2823     }
2824
2825     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
2826     return 1;
2827 }
2828
2829 static OPTION_FUNC(opt_password)
2830 {
2831     if (!override) {
2832         send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
2833         return 0;
2834     }
2835
2836     if (argc > 1)
2837         cryptpass(argv[1], hi->passwd);
2838
2839     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
2840     argv[1] = "****";
2841     
2842     return 1;
2843 }
2844
2845 static OPTION_FUNC(opt_flags)
2846 {
2847     char flags[33];
2848     unsigned int ii, flen;
2849
2850     if (!override) {
2851         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2852         return 0;
2853     }
2854
2855     if (argc > 1)
2856         nickserv_apply_flags(user, hi, argv[1]);
2857
2858     for (ii = flen = 0; handle_flags[ii]; ii++)
2859         if (hi->flags & (1 << ii))
2860             flags[flen++] = handle_flags[ii];
2861     flags[flen] = '\0';
2862     if (hi->flags)
2863         send_message(user, nickserv, "NSMSG_SET_FLAGS", flags);
2864     else
2865         send_message(user, nickserv, "NSMSG_SET_FLAGS", user_find_message(user, "MSG_NONE"));
2866     return 1;
2867 }
2868
2869 static OPTION_FUNC(opt_email)
2870 {
2871     if (argc > 1) {
2872         const char *str;
2873         if (!is_valid_email_addr(argv[1])) {
2874             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
2875             return 0;
2876         }
2877         if ((str = mail_prohibited_address(argv[1]))) {
2878             send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", argv[1], str);
2879             return 0;
2880         }
2881         if (hi->email_addr && !irccasecmp(hi->email_addr, argv[1]))
2882             send_message(user, nickserv, "NSMSG_EMAIL_SAME");
2883         else if (!override)
2884                 nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1]);
2885         else {
2886             nickserv_set_email_addr(hi, argv[1]);
2887             if (hi->cookie)
2888                 nickserv_eat_cookie(hi->cookie);
2889             send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2890         }
2891     } else
2892         send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2893     return 1;
2894 }
2895
2896 static OPTION_FUNC(opt_maxlogins)
2897 {
2898     unsigned char maxlogins;
2899     if (argc > 1) {
2900         maxlogins = strtoul(argv[1], NULL, 0);
2901         if ((maxlogins > nickserv_conf.hard_maxlogins) && !override) {
2902             send_message(user, nickserv, "NSMSG_BAD_MAX_LOGINS", nickserv_conf.hard_maxlogins);
2903             return 0;
2904         }
2905         hi->maxlogins = maxlogins;
2906     }
2907     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
2908     send_message(user, nickserv, "NSMSG_SET_MAXLOGINS", maxlogins);
2909     return 1;
2910 }
2911
2912 static OPTION_FUNC(opt_language)
2913 {
2914     struct language *lang;
2915     if (argc > 1) {
2916         lang = language_find(argv[1]);
2917         if (irccasecmp(lang->name, argv[1]))
2918             send_message(user, nickserv, "NSMSG_LANGUAGE_NOT_FOUND", argv[1], lang->name);
2919         hi->language = lang;
2920     }
2921     send_message(user, nickserv, "NSMSG_SET_LANGUAGE", hi->language->name);
2922     return 1;
2923 }
2924
2925 static OPTION_FUNC(opt_karma)
2926 {
2927     if (!override) {
2928         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2929         return 0;
2930     }
2931
2932     if (argc > 1) {
2933         if (argv[1][0] == '+' && isdigit(argv[1][1])) {
2934             hi->karma += strtoul(argv[1] + 1, NULL, 10);
2935         } else if (argv[1][0] == '-' && isdigit(argv[1][1])) {
2936             hi->karma -= strtoul(argv[1] + 1, NULL, 10);
2937         } else {
2938             send_message(user, nickserv, "NSMSG_INVALID_KARMA", argv[1]);
2939         }
2940     }
2941
2942     send_message(user, nickserv, "NSMSG_SET_KARMA", hi->karma);
2943     return 1;
2944 }
2945
2946 int
2947 oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
2948     if (!oper_has_access(user, bot, nickserv_conf.modoper_level, 0))
2949         return 0;
2950     if ((user->handle_info->opserv_level < target->opserv_level)
2951         || ((user->handle_info->opserv_level == target->opserv_level)
2952             && (user->handle_info->opserv_level < 1000))) {
2953         send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
2954         return 0;
2955     }
2956     if ((user->handle_info->opserv_level < new_level)
2957         || ((user->handle_info->opserv_level == new_level)
2958             && (user->handle_info->opserv_level < 1000))) {
2959         send_message(user, bot, "NSMSG_OPSERV_LEVEL_BAD");
2960         return 0;
2961     }
2962     if (user->handle_info == target) {
2963         send_message(user, bot, "MSG_STUPID_ACCESS_CHANGE");
2964         return 0;
2965     }
2966     if (target->opserv_level == new_level)
2967         return 0;
2968     log_module(NS_LOG, LOG_INFO, "Account %s setting oper level for account %s to %d (from %d).",
2969         user->handle_info->handle, target->handle, new_level, target->opserv_level);
2970     target->opserv_level = new_level;
2971     return 1;
2972 }
2973
2974 int
2975 oper_try_set_staff_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
2976     if (!oper_has_access(user, bot, nickserv_conf.modstaff_level, 0))
2977         return 0;
2978     if ((user->handle_info->opserv_level < target->opserv_level)
2979         || ((user->handle_info->opserv_level == target->opserv_level)
2980             && (user->handle_info->opserv_level < 1000))) {
2981         send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
2982         return 0;
2983     }
2984     if (target->staff_level == new_level)
2985         return 0;
2986     log_module(NS_LOG, LOG_INFO, "Account %s setting staff level for account %s to %d (from %d).",
2987         user->handle_info->handle, target->handle, new_level, target->staff_level);
2988     target->staff_level = new_level;
2989     return 1;
2990 }
2991
2992 static OPTION_FUNC(opt_level)
2993 {
2994     int res;
2995
2996     if (!override) {
2997         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2998         return 0;
2999     }
3000
3001     res = (argc > 1) ? oper_try_set_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
3002     send_message(user, nickserv, "NSMSG_SET_LEVEL", hi->opserv_level);
3003     return res;
3004 }
3005
3006 static OPTION_FUNC(opt_staff_level)
3007 {
3008     int res;
3009
3010     if (!override) {
3011         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
3012         return 0;
3013     }
3014
3015     res = (argc > 1) ? oper_try_set_staff_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
3016     send_message(user, nickserv, "NSMSG_SET_STAFFLEVEL", hi->staff_level);
3017     return res;
3018 }
3019
3020 static OPTION_FUNC(opt_epithet)
3021 {
3022     if (!override) {
3023         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
3024         return 0;
3025     }
3026
3027     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_epithet_level, 0)) {
3028         char *epithet = unsplit_string(argv+1, argc-1, NULL);
3029         if (hi->epithet)
3030             free(hi->epithet);
3031         if ((epithet[0] == '*') && !epithet[1])
3032             hi->epithet = NULL;
3033         else
3034             hi->epithet = strdup(epithet);
3035     }
3036
3037     if (hi->epithet)
3038         send_message(user, nickserv, "NSMSG_SET_EPITHET", hi->epithet);
3039     else
3040         send_message(user, nickserv, "NSMSG_SET_EPITHET", user_find_message(user, "MSG_NONE"));
3041     return 1;
3042 }
3043
3044 static OPTION_FUNC(opt_title)
3045 {
3046     const char *title;
3047
3048     if (!override) {
3049         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
3050         return 0;
3051     }
3052
3053     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_title_level, 0)) {
3054         title = argv[1];
3055         if (strchr(title, '.')) {
3056             send_message(user, nickserv, "NSMSG_TITLE_INVALID");
3057             return 0;
3058         }
3059         if ((strlen(user->handle_info->handle) + strlen(title) +
3060              strlen(titlehost_suffix) + 2) > HOSTLEN) {
3061             send_message(user, nickserv, "NSMSG_TITLE_TRUNCATED");
3062             return 0;
3063         }
3064
3065         free(hi->fakehost);
3066         if (!strcmp(title, "*")) {
3067             hi->fakehost = NULL;
3068         } else {
3069             hi->fakehost = malloc(strlen(title)+2);
3070             hi->fakehost[0] = '.';
3071             strcpy(hi->fakehost+1, title);
3072         }
3073         apply_fakehost(hi, NULL);
3074     } else if (hi->fakehost && (hi->fakehost[0] == '.'))
3075         title = hi->fakehost + 1;
3076     else
3077         title = NULL;
3078     if (!title)
3079         title = user_find_message(user, "MSG_NONE");
3080     send_message(user, nickserv, "NSMSG_SET_TITLE", title);
3081     return 1;
3082 }
3083
3084 static OPTION_FUNC(opt_fakehost)
3085 {
3086     char mask[USERLEN + HOSTLEN + 2];
3087     char *host, *ident;
3088
3089     if (!override) {
3090         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
3091         return 0;
3092     }
3093
3094     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakehost_level, 0)) {
3095         if(strlen(argv[1]) >= sizeof(mask)) {
3096             send_message(user, nickserv, "NSMSG_FAKEMASK_INVALID", USERLEN + HOSTLEN + 1);
3097             return 0;
3098         }
3099
3100         safestrncpy(mask, argv[1], sizeof(mask));
3101
3102         if ((host = strrchr(mask, '@')) && host != mask) {
3103             /* If ident@host was used and the user doesn't have access to set idents, do not change anything. */
3104             if (!oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
3105                 host = NULL;
3106                 ident = NULL;
3107             } else {
3108                 ident = mask;
3109                 *host++ = '\0';
3110             }
3111         } else {
3112             ident = NULL;
3113             host = mask;
3114         }
3115
3116         if (ident && strlen(ident) > USERLEN) {
3117             send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
3118             return 0;
3119         }
3120
3121         if (host && ((strlen(host) > HOSTLEN) || (host[0] == '.'))) {
3122             send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
3123             return 0;
3124         }
3125
3126         if (host && host[0]) {
3127             free(hi->fakehost);
3128             if (!strcmp(host, "*"))
3129                 hi->fakehost = NULL;
3130             else
3131                 hi->fakehost = strdup(host);
3132             host = hi->fakehost;
3133         }
3134         else
3135             host = generate_fakehost(hi);
3136
3137         if (ident) {
3138             free(hi->fakeident);
3139             if (!strcmp(ident, "*"))
3140                 hi->fakeident = NULL;
3141             else
3142                 hi->fakeident = strdup(ident);
3143             ident = hi->fakeident;
3144         }
3145         else
3146             ident = generate_fakeident(hi, NULL);
3147
3148         apply_fakehost(hi, NULL);
3149     } else {
3150         host = generate_fakehost(hi);
3151         ident = generate_fakeident(hi, NULL);
3152     }
3153     if (!host)
3154         host = (char *) user_find_message(user, "MSG_NONE");
3155     if(ident)
3156         send_message(user, nickserv, "NSMSG_SET_FAKEIDENTHOST", ident, host);
3157     else
3158         send_message(user, nickserv, "NSMSG_SET_FAKEHOST", host);
3159     return 1;
3160 }
3161
3162 static OPTION_FUNC(opt_fakeident)
3163 {
3164     const char *ident;
3165
3166     if (!override) {
3167         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
3168         return 0;
3169     }
3170
3171     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
3172         ident = argv[1];
3173         if (strlen(ident) > USERLEN) {
3174             send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
3175             return 0;
3176         }
3177         free(hi->fakeident);
3178         if (!strcmp(ident, "*"))
3179             hi->fakeident = NULL;
3180         else
3181             hi->fakeident = strdup(ident);
3182         ident = hi->fakeident;
3183         apply_fakehost(hi, NULL);
3184     } else
3185         ident = generate_fakeident(hi, NULL); /* NULL if no fake ident set */
3186     if (!ident)
3187         ident = user_find_message(user, "MSG_NONE");
3188     send_message(user, nickserv, "NSMSG_SET_FAKEIDENT", ident);
3189     return 1;
3190 }
3191
3192 static NICKSERV_FUNC(cmd_reclaim)
3193 {
3194     struct nick_info *ni;
3195     struct userNode *victim;
3196
3197     NICKSERV_MIN_PARMS(2);
3198     ni = dict_find(nickserv_nick_dict, argv[1], 0);
3199     if (!ni) {
3200         reply("NSMSG_UNKNOWN_NICK", argv[1]);
3201         return 0;
3202     }
3203     if (ni->owner != user->handle_info) {
3204         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
3205         return 0;
3206     }
3207     victim = GetUserH(ni->nick);
3208     if (!victim) {
3209         reply("MSG_NICK_UNKNOWN", ni->nick);
3210         return 0;
3211     }
3212     if (victim == user) {
3213         reply("NSMSG_NICK_USER_YOU");
3214         return 0;
3215     }
3216     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
3217     switch (nickserv_conf.reclaim_action) {
3218     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
3219     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
3220     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
3221     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
3222     }
3223     return 1;
3224 }
3225
3226 static NICKSERV_FUNC(cmd_unregnick)
3227 {
3228     const char *nick;
3229     struct handle_info *hi;
3230     struct nick_info *ni;
3231
3232     hi = user->handle_info;
3233     nick = (argc < 2) ? user->nick : (const char*)argv[1];
3234     ni = dict_find(nickserv_nick_dict, nick, NULL);
3235     if (!ni) {
3236         reply("NSMSG_UNKNOWN_NICK", nick);
3237         return 0;
3238     }
3239     if (hi != ni->owner) {
3240         reply("NSMSG_NOT_YOUR_NICK", nick);
3241         return 0;
3242     }
3243     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
3244     delete_nick(ni);
3245     return 1;
3246 }
3247
3248 static NICKSERV_FUNC(cmd_ounregnick)
3249 {
3250     struct nick_info *ni;
3251
3252     NICKSERV_MIN_PARMS(2);
3253     if (!(ni = get_nick_info(argv[1]))) {
3254         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
3255         return 0;
3256     }
3257     if (!oper_outranks(user, ni->owner))
3258         return 0;
3259     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
3260     delete_nick(ni);
3261     return 1;
3262 }
3263
3264 static NICKSERV_FUNC(cmd_unregister)
3265 {
3266     struct handle_info *hi;
3267     char *passwd;
3268
3269     NICKSERV_MIN_PARMS(2);
3270     hi = user->handle_info;
3271     passwd = argv[1];
3272     argv[1] = "****";
3273     if (checkpass(passwd, hi->passwd)) {
3274         nickserv_unregister_handle(hi, user);
3275         return 1;
3276     } else {
3277         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
3278         reply("NSMSG_PASSWORD_INVALID");
3279         return 0;
3280     }
3281 }
3282
3283 static NICKSERV_FUNC(cmd_ounregister)
3284 {
3285     struct handle_info *hi;
3286     char reason[MAXLEN];
3287     int force;
3288
3289     NICKSERV_MIN_PARMS(2);
3290     if (!(hi = get_victim_oper(user, argv[1])))
3291         return 0;
3292
3293     if (HANDLE_FLAGGED(hi, NODELETE)) {
3294         reply("NSMSG_UNREGISTER_NODELETE", hi->handle);
3295         return 0;
3296     }
3297
3298     force = IsOper(user) && (argc > 2) && !irccasecmp(argv[2], "force");
3299     if (!force &&
3300         ((hi->flags & nickserv_conf.ounregister_flags)
3301          || hi->users
3302          || (hi->last_quit_host[0] && ((unsigned)(now - hi->lastseen) < nickserv_conf.ounregister_inactive)))) {
3303         reply((IsOper(user) ? "NSMSG_UNREGISTER_MUST_FORCE" : "NSMSG_UNREGISTER_CANNOT_FORCE"), hi->handle);
3304         return 0;
3305     }
3306
3307     snprintf(reason, sizeof(reason), "%s unregistered account %s.", user->handle_info->handle, hi->handle);
3308     global_message(MESSAGE_RECIPIENT_STAFF, reason);
3309     nickserv_unregister_handle(hi, user);
3310     return 1;
3311 }
3312
3313 static NICKSERV_FUNC(cmd_status)
3314 {
3315     if (nickserv_conf.disable_nicks) {
3316         reply("NSMSG_GLOBAL_STATS_NONICK",
3317                         dict_size(nickserv_handle_dict));
3318     } else {
3319         if (user->handle_info) {
3320             int cnt=0;
3321             struct nick_info *ni;
3322             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
3323             reply("NSMSG_HANDLE_STATS", cnt);
3324         } else {
3325             reply("NSMSG_HANDLE_NONE");
3326         }
3327         reply("NSMSG_GLOBAL_STATS",
3328               dict_size(nickserv_handle_dict),
3329               dict_size(nickserv_nick_dict));
3330     }
3331     return 1;
3332 }
3333
3334 static NICKSERV_FUNC(cmd_ghost)
3335 {
3336     struct userNode *target;
3337     char reason[MAXLEN];
3338
3339     NICKSERV_MIN_PARMS(2);
3340     if (!(target = GetUserH(argv[1]))) {
3341         reply("MSG_NICK_UNKNOWN", argv[1]);
3342         return 0;
3343     }
3344     if (target == user) {
3345         reply("NSMSG_CANNOT_GHOST_SELF");
3346         return 0;
3347     }
3348     if (!target->handle_info || (target->handle_info != user->handle_info)) {
3349         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
3350         return 0;
3351     }
3352     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
3353     DelUser(target, nickserv, 1, reason);
3354     reply("NSMSG_GHOST_KILLED", argv[1]);
3355     return 1;
3356 }
3357
3358 static NICKSERV_FUNC(cmd_vacation)
3359 {
3360     HANDLE_SET_FLAG(user->handle_info, FROZEN);
3361     reply("NSMSG_ON_VACATION");
3362     return 1;
3363 }
3364
3365 static NICKSERV_FUNC(cmd_addnote)
3366 {
3367     struct handle_info *hi;
3368     unsigned long duration;
3369     char text[MAXLEN];
3370     unsigned int id;
3371     struct handle_note *prev;
3372     struct handle_note *note;
3373
3374     /* Parse parameters and figure out values for note's fields. */
3375     NICKSERV_MIN_PARMS(4);
3376     hi = get_victim_oper(user, argv[1]);
3377     if (!hi)
3378         return 0;
3379     if(!strcmp(argv[2], "0"))
3380         duration = 0;
3381     else if(!(duration = ParseInterval(argv[2])))
3382     {
3383         reply("MSG_INVALID_DURATION", argv[2]);
3384         return 0;
3385     }
3386     if (duration > 2*365*86400) {
3387         reply("NSMSG_EXCESSIVE_DURATION", argv[2]);
3388         return 0;
3389     }
3390     unsplit_string(argv + 3, argc - 3, text);
3391     WALK_NOTES(hi, prev, note) {}
3392     id = prev ? (prev->id + 1) : 1;
3393
3394     /* Create the new note structure. */
3395     note = calloc(1, sizeof(*note) + strlen(text));
3396     note->next = NULL;
3397     note->expires = duration ? (now + duration) : 0;
3398     note->set = now;
3399     note->id = id;
3400     safestrncpy(note->setter, user->handle_info->handle, sizeof(note->setter));
3401     strcpy(note->note, text);
3402     if (prev)
3403         prev->next = note;
3404     else
3405         hi->notes = note;
3406     reply("NSMSG_NOTE_ADDED", id, hi->handle);
3407     return 1;
3408 }
3409
3410 static NICKSERV_FUNC(cmd_delnote)
3411 {
3412     struct handle_info *hi;
3413     struct handle_note *prev;
3414     struct handle_note *note;
3415     int id;
3416
3417     NICKSERV_MIN_PARMS(3);
3418     hi = get_victim_oper(user, argv[1]);
3419     if (!hi)
3420         return 0;
3421     id = strtoul(argv[2], NULL, 10);
3422     WALK_NOTES(hi, prev, note) {
3423         if (id == note->id) {
3424             if (prev)
3425                 prev->next = note->next;
3426             else
3427                 hi->notes = note->next;
3428             free(note);
3429             reply("NSMSG_NOTE_REMOVED", id, hi->handle);
3430             return 1;
3431         }
3432     }
3433     reply("NSMSG_NO_SUCH_NOTE", hi->handle, id);
3434     return 0;
3435 }
3436
3437 static int
3438 nickserv_saxdb_write(struct saxdb_context *ctx) {
3439     dict_iterator_t it;
3440     struct handle_info *hi;
3441     char flags[33];
3442
3443     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3444         hi = iter_data(it);
3445         assert(hi->id != 0);
3446         saxdb_start_record(ctx, iter_key(it), 0);
3447         if (hi->cookie) {
3448             struct handle_cookie *cookie = hi->cookie;
3449             char *type;
3450
3451             switch (cookie->type) {
3452             case ACTIVATION: type = KEY_ACTIVATION; break;
3453             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
3454             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
3455             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
3456             default: type = NULL; break;
3457             }
3458             if (type) {
3459                 saxdb_start_record(ctx, KEY_COOKIE, 0);
3460                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
3461                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
3462                 if (cookie->data)
3463                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
3464                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
3465                 saxdb_end_record(ctx);
3466             }
3467         }
3468         if (hi->notes) {
3469             struct handle_note *prev, *note;
3470             saxdb_start_record(ctx, KEY_NOTES, 0);
3471             WALK_NOTES(hi, prev, note) {
3472                 snprintf(flags, sizeof(flags), "%d", note->id);
3473                 saxdb_start_record(ctx, flags, 0);
3474                 if (note->expires)
3475                     saxdb_write_int(ctx, KEY_NOTE_EXPIRES, note->expires);
3476                 saxdb_write_int(ctx, KEY_NOTE_SET, note->set);
3477                 saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
3478                 saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
3479                 saxdb_end_record(ctx);
3480             }
3481             saxdb_end_record(ctx);
3482         }
3483         if (hi->email_addr)
3484             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
3485         if (hi->epithet)
3486             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
3487         if (hi->fakehost)
3488             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
3489         if (hi->fakeident)
3490             saxdb_write_string(ctx, KEY_FAKEIDENT, hi->fakeident);
3491         if (hi->flags) {
3492             int ii, flen;
3493
3494             for (ii=flen=0; handle_flags[ii]; ++ii)
3495                 if (hi->flags & (1 << ii))
3496                     flags[flen++] = handle_flags[ii];
3497             flags[flen] = 0;
3498             saxdb_write_string(ctx, KEY_FLAGS, flags);
3499         }
3500         saxdb_write_int(ctx, KEY_ID, hi->id);
3501         if (hi->infoline)
3502             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
3503         if (hi->devnull)
3504             saxdb_write_string(ctx, KEY_DEVNULL, hi->devnull);
3505         if (hi->website)
3506             saxdb_write_string(ctx, KEY_WEBSITE, hi->website);
3507         if (hi->last_quit_host[0])
3508             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
3509         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
3510         if (hi->karma != 0)
3511             saxdb_write_sint(ctx, KEY_KARMA, hi->karma);
3512         if (hi->masks->used)
3513             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
3514         if (hi->maxlogins)
3515             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
3516         if (hi->nicks) {
3517             struct string_list *slist;
3518             struct nick_info *ni;
3519
3520             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
3521             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
3522             saxdb_write_string_list(ctx, KEY_NICKS, slist);
3523             free(slist->list);
3524             free(slist);
3525         }
3526         if (hi->opserv_level)
3527             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
3528         if (hi->staff_level)
3529             saxdb_write_int(ctx, KEY_STAFF_LEVEL, hi->staff_level);
3530         if (hi->language != lang_C)
3531             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
3532         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
3533         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
3534         if (hi->screen_width)
3535             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
3536         if (hi->table_width)
3537             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
3538         flags[0] = hi->userlist_style;
3539         flags[1] = 0;
3540         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
3541         if(hi->authlog) {
3542             saxdb_start_record(ctx, KEY_AUTHLOG, 0);
3543             struct authlogEntry *authlog;
3544             int i = 0;
3545             for(authlog = hi->authlog; authlog; authlog = authlog->next) {
3546                 saxdb_start_record(ctx, strtab(++i), 0);
3547                 saxdb_write_int(ctx, KEY_AUTHLOG_LOGIN_TIME, authlog->login_time);
3548                 saxdb_write_int(ctx, KEY_AUTHLOG_LOGOUT_TIME, authlog->logout_time);
3549                 saxdb_write_string(ctx, KEY_AUTHLOG_HOSTMASK, authlog->hostmask);
3550                 if(authlog->quit_reason)
3551                     saxdb_write_string(ctx, KEY_AUTHLOG_QUIT_REASON, authlog->quit_reason);
3552                 saxdb_end_record(ctx);
3553             }
3554             saxdb_end_record(ctx); //END KEY_AUTHLOG
3555         }
3556         saxdb_end_record(ctx);
3557     }
3558     return 0;
3559 }
3560
3561 static handle_merge_func_t *handle_merge_func_list;
3562 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
3563
3564 void
3565 reg_handle_merge_func(handle_merge_func_t func)
3566 {
3567     if (handle_merge_func_used == handle_merge_func_size) {
3568         if (handle_merge_func_size) {
3569             handle_merge_func_size <<= 1;
3570             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
3571         } else {
3572             handle_merge_func_size = 8;
3573             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
3574         }
3575     }
3576     handle_merge_func_list[handle_merge_func_used++] = func;
3577 }
3578
3579 static NICKSERV_FUNC(cmd_merge)
3580 {
3581     struct handle_info *hi_from, *hi_to;
3582     struct userNode *last_user;
3583     struct userData *cList, *cListNext;
3584     unsigned int ii, jj, n;
3585     char buffer[MAXLEN];
3586
3587     NICKSERV_MIN_PARMS(3);
3588
3589     if (!(hi_from = get_victim_oper(user, argv[1])))
3590         return 0;
3591     if (!(hi_to = get_victim_oper(user, argv[2])))
3592         return 0;
3593     if (hi_to == hi_from) {
3594         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
3595         return 0;
3596     }
3597
3598     for (n=0; n<handle_merge_func_used; n++)
3599         handle_merge_func_list[n](user, hi_to, hi_from);
3600
3601     /* Append "from" handle's nicks to "to" handle's nick list. */
3602     if (hi_to->nicks) {
3603         struct nick_info *last_ni;
3604         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
3605         last_ni->next = hi_from->nicks;
3606     }
3607     while (hi_from->nicks) {
3608         hi_from->nicks->owner = hi_to;
3609         hi_from->nicks = hi_from->nicks->next;
3610     }
3611
3612     /* Merge the hostmasks. */
3613     for (ii=0; ii<hi_from->masks->used; ii++) {
3614         char *mask = hi_from->masks->list[ii];
3615         for (jj=0; jj<hi_to->masks->used; jj++)
3616             if (match_ircglobs(hi_to->masks->list[jj], mask))
3617                 break;
3618         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
3619             string_list_append(hi_to->masks, strdup(mask));
3620     }
3621
3622     /* Merge the lists of authed users. */
3623     if (hi_to->users) {
3624         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
3625         last_user->next_authed = hi_from->users;
3626     } else {
3627         hi_to->users = hi_from->users;
3628     }
3629     /* Repoint the old "from" handle's users. */
3630     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
3631         last_user->handle_info = hi_to;
3632     }
3633     hi_from->users = NULL;
3634
3635     /* Merge channel userlists. */
3636     for (cList=hi_from->channels; cList; cList=cListNext) {
3637         struct userData *cList2;
3638         cListNext = cList->u_next;
3639         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
3640             if (cList->channel == cList2->channel)
3641                 break;
3642         if (cList2 && (cList2->access >= cList->access)) {
3643             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);
3644             /* keep cList2 in hi_to; remove cList from hi_from */
3645             del_channel_user(cList, 1);
3646         } else {
3647             if (cList2) {
3648                 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);
3649                 /* remove the lower-ranking cList2 from hi_to */
3650                 del_channel_user(cList2, 1);
3651             } else {
3652                 log_module(NS_LOG, LOG_INFO, "Merge: %s had no access in %s", hi_to->handle, cList->channel->channel->name);
3653             }
3654             /* cList needs to be moved from hi_from to hi_to */
3655             cList->handle = hi_to;
3656             /* Remove from linked list for hi_from */
3657             assert(!cList->u_prev);
3658             hi_from->channels = cList->u_next;
3659             if (cList->u_next)
3660                 cList->u_next->u_prev = cList->u_prev;
3661             /* Add to linked list for hi_to */
3662             cList->u_prev = NULL;
3663             cList->u_next = hi_to->channels;
3664             if (hi_to->channels)
3665                 hi_to->channels->u_prev = cList;
3666             hi_to->channels = cList;
3667         }
3668     }
3669
3670     /* Do they get an OpServ level promotion? */
3671     if (hi_from->opserv_level > hi_to->opserv_level)
3672         hi_to->opserv_level = hi_from->opserv_level;
3673
3674     /* Do they get a staff level promotion? */
3675     if (hi_from->staff_level > hi_to->staff_level)
3676         hi_to->staff_level = hi_from->staff_level;
3677
3678     /* What about last seen time? */
3679     if (hi_from->lastseen > hi_to->lastseen)
3680         hi_to->lastseen = hi_from->lastseen;
3681
3682     /* New karma is the sum of the two original karmas. */
3683     hi_to->karma += hi_from->karma;
3684
3685     /* Does a fakehost carry over?  (This intentionally doesn't set it
3686      * for users previously attached to hi_to.  They'll just have to
3687      * reconnect.)
3688      */
3689     if (hi_from->fakehost && !hi_to->fakehost)
3690         hi_to->fakehost = strdup(hi_from->fakehost);
3691     if (hi_from->fakeident && !hi_to->fakeident)
3692         hi_to->fakeident = strdup(hi_from->fakeident);
3693
3694     /* Notify of success. */
3695     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
3696     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
3697     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
3698
3699     /* Unregister the "from" handle. */
3700     nickserv_unregister_handle(hi_from, NULL);
3701
3702     return 1;
3703 }
3704
3705 #define NICKSERV_DISCRIM_FIELDS_AUTH     0x01
3706 #define NICKSERV_DISCRIM_FIELDS_EMAIL    0x02
3707 #define NICKSERV_DISCRIM_FIELDS_SEEN     0x04
3708 #define NICKSERV_DISCRIM_FIELDS_ACCESS   0x08
3709 #define NICKSERV_DISCRIM_FIELDS_FAKEHOST 0x10
3710 #define NICKSERV_DISCRIM_FIELDS_WEBSITE  0x20
3711 #define NICKSERV_DISCRIM_FIELDS_DEVNULL  0x40
3712
3713 #define NICKSERV_DISCRIM_FIELD_COUNT     7
3714
3715 struct nickserv_discrim {
3716     unsigned int show_fields;
3717     struct helpfile_table *output_table;
3718     int output_table_pos;
3719     unsigned int output_table_free_fields;
3720     
3721     unsigned long flags_on, flags_off;
3722     unsigned long min_registered, max_registered;
3723     unsigned long lastseen;
3724     unsigned int limit;
3725     int min_level, max_level;
3726     int min_karma, max_karma;
3727     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
3728     const char *nickmask;
3729     const char *hostmask;
3730     const char *fakehostmask;
3731     const char *fakeidentmask;
3732     const char *website;
3733     const char *devnullclass;
3734     const char *handlemask;
3735     const char *emailmask;
3736 };
3737
3738 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi, struct nickserv_discrim *discrim);
3739
3740 struct discrim_apply_info {
3741     struct nickserv_discrim *discrim;
3742     discrim_search_func func;
3743     struct userNode *source;
3744     unsigned int matched;
3745 };
3746
3747 static struct nickserv_discrim *
3748 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
3749 {
3750     unsigned int i;
3751     struct nickserv_discrim *discrim;
3752
3753     discrim = malloc(sizeof(*discrim));
3754     memset(discrim, 0, sizeof(*discrim));
3755     discrim->min_level = 0;
3756     discrim->max_level = INT_MAX;
3757     discrim->limit = 50;
3758     discrim->min_registered = 0;
3759     discrim->max_registered = ULONG_MAX;
3760     discrim->lastseen = ULONG_MAX;
3761     discrim->min_karma = INT_MIN;
3762     discrim->max_karma = INT_MAX;
3763
3764     for (i=0; i<argc; i++) {
3765         if (i == argc - 1) {
3766             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3767             goto fail;
3768         }
3769         if (!irccasecmp(argv[i], "limit")) {
3770             discrim->limit = strtoul(argv[++i], NULL, 0);
3771         } else if (!irccasecmp(argv[i], "flags")) {
3772             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
3773         } else if (!irccasecmp(argv[i], "fields")) {
3774             char *fields = argv[++i];
3775             char *delimiter = strstr(fields, ",");
3776             while(1) {
3777                 if(delimiter)
3778                     *delimiter = '\0';
3779                 if(!irccasecmp(fields, "auth"))
3780                     discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_AUTH;
3781                 else if(!irccasecmp(fields, "email"))
3782                     discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_EMAIL;
3783                 else if(!irccasecmp(fields, "seen"))
3784                     discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_SEEN;
3785                 else if(!irccasecmp(fields, "access"))
3786                     discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_ACCESS;
3787                 else if(!irccasecmp(fields, "fakehost"))
3788                     discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_FAKEHOST;
3789                 else if(!irccasecmp(fields, "website") && IsBot(user))
3790                     discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_WEBSITE;
3791                 else if(!irccasecmp(fields, "devnull"))
3792                     discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_DEVNULL;
3793                 else {
3794                     send_message(user, nickserv, "MSG_INVALID_FIELD", fields);
3795                     goto fail;
3796                 }
3797                 if(delimiter) {
3798                     *delimiter = ',';
3799                     fields = delimiter+1;
3800                     if(*fields) {
3801                         delimiter = strstr(fields, ",");
3802                         continue;
3803                     }
3804                 }
3805                 break;
3806             }
3807         } else if (!irccasecmp(argv[i], "registered")) {
3808             const char *cmp = argv[++i];
3809             if (cmp[0] == '<') {
3810                 if (cmp[1] == '=') {
3811                     discrim->min_registered = now - ParseInterval(cmp+2);
3812                 } else {
3813                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
3814                 }
3815             } else if (cmp[0] == '=') {
3816                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
3817             } else if (cmp[0] == '>') {
3818                 if (cmp[1] == '=') {
3819                     discrim->max_registered = now - ParseInterval(cmp+2);
3820                 } else {
3821                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
3822                 }
3823             } else {
3824                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3825             }
3826         } else if (!irccasecmp(argv[i], "seen")) {
3827             discrim->lastseen = now - ParseInterval(argv[++i]);
3828         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
3829             discrim->nickmask = argv[++i];
3830         } else if (!irccasecmp(argv[i], "hostmask")) {
3831             i++;
3832             if (!irccasecmp(argv[i], "exact")) {
3833                 if (i == argc - 1) {
3834                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3835                     goto fail;
3836                 }
3837                 discrim->hostmask_type = EXACT;
3838             } else if (!irccasecmp(argv[i], "subset")) {
3839                 if (i == argc - 1) {
3840                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3841                     goto fail;
3842                 }
3843                 discrim->hostmask_type = SUBSET;
3844             } else if (!irccasecmp(argv[i], "superset")) {
3845                 if (i == argc - 1) {
3846                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3847                     goto fail;
3848                 }
3849                 discrim->hostmask_type = SUPERSET;
3850             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
3851                 if (i == argc - 1) {
3852                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3853                     goto fail;
3854                 }
3855                 discrim->hostmask_type = LASTQUIT;
3856             } else {
3857                 i--;
3858                 discrim->hostmask_type = SUPERSET;
3859             }
3860             discrim->hostmask = argv[++i];
3861         } else if (!irccasecmp(argv[i], "fakehost")) {
3862             if (!irccasecmp(argv[++i], "*")) {
3863                 discrim->fakehostmask = 0;
3864             } else {
3865                 discrim->fakehostmask = argv[i];
3866             }
3867         } else if (!irccasecmp(argv[i], "fakeident")) {
3868             if (!irccasecmp(argv[++i], "*")) {
3869                 discrim->fakeidentmask = 0;
3870             } else {
3871                 discrim->fakeidentmask = argv[i];
3872             }
3873         } else if (!irccasecmp(argv[i], "website")) {
3874             if (!irccasecmp(argv[++i], "*")) {
3875                 discrim->website = 0;
3876             } else {
3877                 discrim->website = argv[i];
3878             }
3879         } else if (!irccasecmp(argv[i], "devnull")) {
3880             if (!irccasecmp(argv[++i], "*")) {
3881                 discrim->devnullclass = 0;
3882             } else {
3883                 discrim->devnullclass = argv[i];
3884             }
3885         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
3886             if (!irccasecmp(argv[++i], "*")) {
3887                 discrim->handlemask = 0;
3888             } else {
3889                 discrim->handlemask = argv[i];
3890             }
3891         } else if (!irccasecmp(argv[i], "email")) {
3892             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
3893                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
3894                 goto fail;
3895             } else if (!irccasecmp(argv[++i], "*")) {
3896                 discrim->emailmask = 0;
3897             } else {
3898                 discrim->emailmask = argv[i];
3899             }
3900         } else if (!irccasecmp(argv[i], "access")) {
3901             const char *cmp = argv[++i];
3902             if (cmp[0] == '<') {
3903                 if (discrim->min_level == 0) discrim->min_level = 1;
3904                 if (cmp[1] == '=') {
3905                     discrim->max_level = strtoul(cmp+2, NULL, 0);
3906                 } else {
3907                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
3908                 }
3909             } else if (cmp[0] == '=') {
3910                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
3911             } else if (cmp[0] == '>') {
3912                 if (cmp[1] == '=') {
3913                     discrim->min_level = strtoul(cmp+2, NULL, 0);
3914                 } else {
3915                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
3916                 }
3917             } else {
3918                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3919             }
3920         } else if (!irccasecmp(argv[i], "karma")) {
3921             const char *cmp = argv[++i];
3922             if (cmp[0] == '<') {
3923                 if (cmp[1] == '=') {
3924                     discrim->max_karma = strtoul(cmp+2, NULL, 0);
3925                 } else {
3926                     discrim->max_karma = strtoul(cmp+1, NULL, 0) - 1;
3927                 }
3928             } else if (cmp[0] == '=') {
3929                 discrim->min_karma = discrim->max_karma = strtoul(cmp+1, NULL, 0);
3930             } else if (cmp[0] == '>') {
3931                 if (cmp[1] == '=') {
3932                     discrim->min_karma = strtoul(cmp+2, NULL, 0);
3933                 } else {
3934                     discrim->min_karma = strtoul(cmp+1, NULL, 0) + 1;
3935                 }
3936             } else {
3937                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3938             }
3939         } else {
3940             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
3941             goto fail;
3942         }
3943     }
3944     return discrim;
3945   fail:
3946     free(discrim);
3947     return NULL;
3948 }
3949
3950 static int
3951 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
3952 {
3953     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
3954         || (discrim->flags_off & hi->flags)
3955         || (discrim->min_registered > hi->registered)
3956         || (discrim->max_registered < hi->registered)
3957         || (discrim->lastseen < (hi->users?now:hi->lastseen))
3958         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
3959         || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
3960         || (discrim->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask)))
3961         || (discrim->website && (!hi->website || !match_ircglob(hi->website, discrim->website)))
3962         || (discrim->devnullclass && (!hi->devnull || !match_ircglob(hi->devnull, discrim->devnullclass)))
3963         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
3964         || (discrim->min_level > hi->opserv_level)
3965         || (discrim->max_level < hi->opserv_level)
3966         || (discrim->min_karma > hi->karma)
3967         || (discrim->max_karma < hi->karma)
3968         ) {
3969         return 0;
3970     }
3971     if (discrim->hostmask) {
3972         unsigned int i;
3973         for (i=0; i<hi->masks->used; i++) {
3974             const char *mask = hi->masks->list[i];
3975             if ((discrim->hostmask_type == SUBSET)
3976                 && (match_ircglobs(discrim->hostmask, mask))) break;
3977             else if ((discrim->hostmask_type == EXACT)
3978                      && !irccasecmp(discrim->hostmask, mask)) break;
3979             else if ((discrim->hostmask_type == SUPERSET)
3980                      && (match_ircglobs(mask, discrim->hostmask))) break;
3981             else if ((discrim->hostmask_type == LASTQUIT)
3982                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
3983         }
3984         if (i==hi->masks->used) return 0;
3985     }
3986     if (discrim->nickmask) {
3987         struct nick_info *nick = hi->nicks;
3988         while (nick) {
3989             if (match_ircglob(nick->nick, discrim->nickmask)) break;
3990             nick = nick->next;
3991         }
3992         if (!nick) return 0;
3993     }
3994     return 1;
3995 }
3996
3997 static unsigned int
3998 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
3999 {
4000     dict_iterator_t it, next;
4001     unsigned int matched;
4002
4003     for (it = dict_first(nickserv_handle_dict), matched = 0;
4004          it && (matched < discrim->limit);
4005          it = next) {
4006         next = iter_next(it);
4007         if (nickserv_discrim_match(discrim, iter_data(it))) {
4008             dsf(source, iter_data(it), discrim);
4009             matched++;
4010         }
4011     }
4012     return matched;
4013 }
4014
4015 static void
4016 search_print_func(struct userNode *source, struct handle_info *match, struct nickserv_discrim *discrim)
4017 {
4018     if(discrim->show_fields) {
4019         //custom fields
4020         if(discrim->output_table) {
4021             discrim->output_table->contents[++discrim->output_table_pos] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0]));
4022             int i = 0;
4023             if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH)
4024                 discrim->output_table->contents[discrim->output_table_pos][i++] = match->handle;
4025             if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL)
4026                 discrim->output_table->contents[discrim->output_table_pos][i++] = (match->email_addr ? match->email_addr : "*");
4027             if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN) {
4028                 char *seen;
4029                 char seenBuf[INTERVALLEN];
4030                 if(match->users) {
4031                     seen = "Here";
4032                 } else if(match->lastseen == 0) {
4033                     seen = "Never";
4034                 } else {
4035                     seen = intervalString(seenBuf, now - match->lastseen, source->handle_info);
4036                 }
4037                 discrim->output_table_free_fields |= 1 << i;
4038                 discrim->output_table->contents[discrim->output_table_pos][i++] = strdup(seen);
4039             }
4040             if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS)
4041                 discrim->output_table->contents[discrim->output_table_pos][i++] = strtab(match->opserv_level);
4042             if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST)
4043                 discrim->output_table->contents[discrim->output_table_pos][i++] = (match->fakehost ? match->fakehost : "*");
4044             if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE)
4045                 discrim->output_table->contents[discrim->output_table_pos][i++] = (match->website ? match->website : "*");
4046             if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL)
4047                 discrim->output_table->contents[discrim->output_table_pos][i++] = (match->devnull ? match->devnull : "*");
4048             
4049         }
4050     } else
4051         send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
4052 }
4053
4054 static void
4055 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match), UNUSED_ARG(struct nickserv_discrim *discrim))
4056 {
4057 }
4058
4059 static void
4060 search_unregister_func (struct userNode *source, struct handle_info *match, UNUSED_ARG(struct nickserv_discrim *discrim))
4061 {
4062     if (oper_has_access(source, nickserv, match->opserv_level, 0))
4063         nickserv_unregister_handle(match, source);
4064 }
4065
4066 static int
4067 nickserv_sort_accounts_by_access(const void *a, const void *b)
4068 {
4069     const struct handle_info *hi_a = *(const struct handle_info**)a;
4070     const struct handle_info *hi_b = *(const struct handle_info**)b;
4071     if (hi_a->opserv_level != hi_b->opserv_level)
4072         return hi_b->opserv_level - hi_a->opserv_level;
4073     return irccasecmp(hi_a->handle, hi_b->handle);
4074 }
4075
4076 void
4077 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
4078 {
4079     struct handle_info_list hil;
4080     struct helpfile_table tbl;
4081     unsigned int ii;
4082     dict_iterator_t it;
4083     const char **ary;
4084
4085     memset(&hil, 0, sizeof(hil));
4086     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
4087         struct handle_info *hi = iter_data(it);
4088         if (hi->opserv_level)
4089             handle_info_list_append(&hil, hi);
4090     }
4091     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
4092     tbl.length = hil.used + 1;
4093     tbl.width = 2;
4094     tbl.flags = TABLE_NO_FREE;
4095     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
4096     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
4097     ary[0] = "Account";
4098     ary[1] = "Level";
4099     for (ii = 0; ii < hil.used; ) {
4100         ary = malloc(tbl.width * sizeof(ary[0]));
4101         ary[0] = hil.list[ii]->handle;
4102         ary[1] = strtab(hil.list[ii]->opserv_level);
4103         tbl.contents[++ii] = ary;
4104     }
4105     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
4106     reply("MSG_MATCH_COUNT", hil.used);
4107     for (ii = 0; ii < hil.used; ii++)
4108         free(tbl.contents[ii]);
4109     free(tbl.contents);
4110     free(hil.list);
4111 }
4112
4113 static NICKSERV_FUNC(cmd_search)
4114 {
4115     struct nickserv_discrim *discrim;
4116     discrim_search_func action;
4117     struct svccmd *subcmd;
4118     unsigned int matches;
4119     char buf[MAXLEN];
4120
4121     NICKSERV_MIN_PARMS(3);
4122     sprintf(buf, "search %s", argv[1]);
4123     subcmd = dict_find(nickserv_service->commands, buf, NULL);
4124     if (!irccasecmp(argv[1], "print"))
4125         action = search_print_func;
4126     else if (!irccasecmp(argv[1], "count"))
4127         action = search_count_func;
4128     else if (!irccasecmp(argv[1], "unregister"))
4129         action = search_unregister_func;
4130     else {
4131         reply("NSMSG_INVALID_ACTION", argv[1]);
4132         return 0;
4133     }
4134
4135     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
4136         return 0;
4137
4138     discrim = nickserv_discrim_create(user, argc-2, argv+2);
4139     if (!discrim)
4140         return 0;
4141
4142     if (action == search_print_func)
4143         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
4144     else if (action == search_count_func)
4145         discrim->limit = INT_MAX;
4146
4147     matches = nickserv_discrim_search(discrim, action, user);
4148     
4149     if(discrim->show_fields) {
4150         int width = 0;
4151         int ii;
4152         for(ii = 0; ii < NICKSERV_DISCRIM_FIELD_COUNT; ii++) {
4153             if(discrim->show_fields & (1 << ii)) width++;
4154         }
4155         discrim->output_table = malloc(sizeof(discrim->output_table[0]));
4156         discrim->output_table->length = matches+1;
4157         discrim->output_table->width = width;
4158         discrim->output_table->flags = TABLE_NO_FREE;
4159         discrim->output_table->contents = malloc(discrim->output_table->length * sizeof(discrim->output_table->contents[0]));
4160         discrim->output_table->contents[0] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0]));
4161         
4162         ii = 0;
4163         if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH)
4164             discrim->output_table->contents[0][ii++] = "Auth";
4165         if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL)
4166             discrim->output_table->contents[0][ii++] = "EMail";
4167         if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN)
4168             discrim->output_table->contents[0][ii++] = "Seen";
4169         if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS)
4170             discrim->output_table->contents[0][ii++] = "Access";
4171         if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST)
4172             discrim->output_table->contents[0][ii++] = "Fakehost";
4173         if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE)
4174             discrim->output_table->contents[0][ii++] = "Website";
4175         if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL)
4176             discrim->output_table->contents[0][ii++] = "DevNull";
4177         
4178         nickserv_discrim_search(discrim, action, user);
4179         
4180         table_send(nickserv, user->nick, 0, NULL, *discrim->output_table);
4181         
4182         for(ii = 1; ii < discrim->output_table->length; ++ii) {
4183             int ij;
4184             for(ij = 0; ij < NICKSERV_DISCRIM_FIELD_COUNT; ij++) {
4185                 if(discrim->output_table_free_fields & (1 << ij))
4186                     free((char*)discrim->output_table->contents[ii][ij]);
4187             }
4188             free(discrim->output_table->contents[ii]);
4189         }
4190         free(discrim->output_table->contents[0]);
4191         free(discrim->output_table->contents);
4192         free(discrim->output_table);
4193     }
4194     if (matches)
4195         reply("MSG_MATCH_COUNT", matches);
4196     else
4197         reply("MSG_NO_MATCHES");
4198
4199     
4200     free(discrim);
4201     return 0;
4202 }
4203
4204 static MODCMD_FUNC(cmd_checkpass)
4205 {
4206     struct handle_info *hi;
4207
4208     NICKSERV_MIN_PARMS(3);
4209     if (!(hi = get_handle_info(argv[1]))) {
4210         reply("MSG_HANDLE_UNKNOWN", argv[1]);
4211         return 0;
4212     }
4213     if (checkpass(argv[2], hi->passwd))
4214         reply("CHECKPASS_YES");
4215     else
4216         reply("CHECKPASS_NO");
4217     argv[2] = "****";
4218     return 1;
4219 }
4220
4221 static MODCMD_FUNC(cmd_checkemail)
4222 {
4223     struct handle_info *hi;
4224
4225     NICKSERV_MIN_PARMS(3);
4226     if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
4227         return 0;
4228     }
4229     if (!hi->email_addr)
4230         reply("CHECKEMAIL_NOT_SET");
4231     else if (!irccasecmp(argv[2], hi->email_addr))
4232         reply("CHECKEMAIL_YES");
4233     else
4234         reply("CHECKEMAIL_NO");
4235     return 1;
4236 }
4237
4238 static int
4239 nickserv_db_read_authlog(UNUSED_ARG(const char *key), void *data, void *extra)
4240 {
4241     struct record_data *rd = data;
4242     struct handle_info *hi = extra;
4243     const char *str;
4244     struct authlogEntry *authlog;
4245     authlog = malloc(sizeof(*authlog));
4246     
4247     str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGIN_TIME, RECDB_QSTRING);
4248     authlog->login_time = str ? strtoul(str, NULL, 0) : 0;
4249     
4250     str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGOUT_TIME, RECDB_QSTRING);
4251     authlog->logout_time = str ? strtoul(str, NULL, 0) : 0;
4252     
4253     str = database_get_data(rd->d.object, KEY_AUTHLOG_HOSTMASK, RECDB_QSTRING);
4254     authlog->hostmask = str ? strdup(str) : NULL;
4255     
4256     str = database_get_data(rd->d.object, KEY_AUTHLOG_QUIT_REASON, RECDB_QSTRING);
4257     authlog->quit_reason = str ? strdup(str) : NULL;
4258     
4259     authlog->user = NULL;
4260     
4261     authlog->next = NULL;
4262     
4263     //append it to the end of the list...
4264     struct authlogEntry *authlog_entry;
4265     if(!hi->authlog) {
4266         hi->authlog = authlog;
4267     } else {
4268         for(authlog_entry = hi->authlog; authlog_entry; authlog_entry = authlog_entry->next) {
4269             if(!authlog_entry->next) {
4270                 authlog_entry->next = authlog;
4271                 break;
4272             }
4273         }
4274     }
4275     return 0;
4276 }
4277
4278 static void
4279 nickserv_db_read_handle(const char *handle, dict_t obj)
4280 {
4281     const char *str;
4282     struct string_list *masks, *slist;
4283     struct handle_info *hi;
4284     struct userNode *authed_users;
4285     struct userData *channel_list;
4286     unsigned long id;
4287     unsigned int ii;
4288     dict_t subdb;
4289
4290     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
4291     id = str ? strtoul(str, NULL, 0) : 0;
4292     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
4293     if (!str) {
4294         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
4295         return;
4296     }
4297     if ((hi = get_handle_info(handle))) {
4298         authed_users = hi->users;
4299         channel_list = hi->channels;
4300         hi->users = NULL;
4301         hi->channels = NULL;
4302         dict_remove(nickserv_handle_dict, hi->handle);
4303     } else {
4304         authed_users = NULL;
4305         channel_list = NULL;
4306     }
4307     hi = register_handle(handle, str, id);
4308     if (authed_users) {
4309         hi->users = authed_users;
4310         while (authed_users) {
4311             authed_users->handle_info = hi;
4312             authed_users = authed_users->next_authed;
4313         }
4314     }
4315     hi->channels = channel_list;
4316     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
4317     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
4318     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
4319     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
4320     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
4321     hi->language = language_find(str ? str : "C");
4322     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
4323     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
4324     str = database_get_data(obj, KEY_STAFF_LEVEL, RECDB_QSTRING);
4325     hi->staff_level = str ? strtoul(str, NULL, 0) : 0;
4326     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
4327     if (str)
4328         hi->infoline = strdup(str);
4329     str = database_get_data(obj, KEY_WEBSITE, RECDB_QSTRING);
4330     if (str)
4331         hi->website = strdup(str);
4332     str = database_get_data(obj, KEY_DEVNULL, RECDB_QSTRING);
4333     if (str)
4334         hi->devnull = strdup(str);
4335     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
4336     hi->registered = str ? strtoul(str, NULL, 0) : now;
4337     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
4338     hi->lastseen = str ? strtoul(str, NULL, 0) : hi->registered;
4339     str = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
4340     hi->karma = str ? strtoul(str, NULL, 0) : 0;
4341     /* We want to read the nicks even if disable_nicks is set.  This is so
4342      * that we don't lose the nick data entirely. */
4343     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
4344     if (slist) {
4345         for (ii=0; ii<slist->used; ii++)
4346             register_nick(slist->list[ii], hi);
4347     }
4348     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
4349     if (str) {
4350         for (ii=0; str[ii]; ii++)
4351             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
4352     }
4353     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
4354     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
4355     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
4356     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
4357     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
4358     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
4359     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
4360     if (!str)
4361         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
4362     if (str)
4363         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
4364     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
4365     if (str)
4366         nickserv_set_email_addr(hi, str);
4367     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
4368     if (str)
4369         hi->epithet = strdup(str);
4370     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
4371     if (str)
4372         hi->fakehost = strdup(str);
4373     str = database_get_data(obj, KEY_FAKEIDENT, RECDB_QSTRING);
4374     if (str)
4375         hi->fakeident = strdup(str);
4376     /* Read the "cookie" sub-database (if it exists). */
4377     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
4378     if (subdb) {
4379         const char *data, *type, *expires, *cookie_str;
4380         struct handle_cookie *cookie;
4381
4382         cookie = calloc(1, sizeof(*cookie));
4383         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
4384         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
4385         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
4386         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
4387         if (!type || !expires || !cookie_str) {
4388             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
4389             goto cookie_out;
4390         }
4391         if (!irccasecmp(type, KEY_ACTIVATION))
4392             cookie->type = ACTIVATION;
4393         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
4394             cookie->type = PASSWORD_CHANGE;
4395         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
4396             cookie->type = EMAIL_CHANGE;
4397         else if (!irccasecmp(type, KEY_ALLOWAUTH))
4398             cookie->type = ALLOWAUTH;
4399         else {
4400             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
4401             goto cookie_out;
4402         }
4403         cookie->expires = strtoul(expires, NULL, 0);
4404         if (cookie->expires < now)
4405             goto cookie_out;
4406         if (data)
4407             cookie->data = strdup(data);
4408         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
4409         cookie->hi = hi;
4410       cookie_out:
4411         if (cookie->hi)
4412             nickserv_bake_cookie(cookie);
4413         else
4414             nickserv_free_cookie(cookie);
4415     }
4416     /* Read the "notes" sub-database (if it exists). */
4417     subdb = database_get_data(obj, KEY_NOTES, RECDB_OBJECT);
4418     if (subdb) {
4419         dict_iterator_t it;
4420         struct handle_note *last_note;
4421         struct handle_note *note;
4422
4423         last_note = NULL;
4424         for (it = dict_first(subdb); it; it = iter_next(it)) {
4425             const char *expires;
4426             const char *setter;
4427             const char *text;
4428             const char *set;
4429             const char *note_id;
4430             dict_t notedb;
4431
4432             note_id = iter_key(it);
4433             notedb = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
4434             if (!notedb) {
4435                 log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", note_id, hi->handle);
4436                 continue;
4437             }
4438             expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
4439             setter = database_get_data(notedb, KEY_NOTE_SETTER, RECDB_QSTRING);
4440             text = database_get_data(notedb, KEY_NOTE_NOTE, RECDB_QSTRING);
4441             set = database_get_data(notedb, KEY_NOTE_SET, RECDB_QSTRING);
4442             if (!setter || !text || !set) {
4443                 log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", note_id, hi->handle);
4444                 continue;
4445             }
4446             note = calloc(1, sizeof(*note) + strlen(text));
4447             note->next = NULL;
4448             note->expires = expires ? strtoul(expires, NULL, 10) : 0;
4449             note->set = strtoul(set, NULL, 10);
4450             note->id = strtoul(note_id, NULL, 10);
4451             safestrncpy(note->setter, setter, sizeof(note->setter));
4452             strcpy(note->note, text);
4453             if (last_note)
4454                 last_note->next = note;
4455             else
4456                 hi->notes = note;
4457             last_note = note;
4458         }
4459     }
4460     if ((subdb = database_get_data(obj, KEY_AUTHLOG, RECDB_OBJECT)))
4461         dict_foreach(subdb, nickserv_db_read_authlog, hi);
4462 }
4463
4464 static int
4465 nickserv_saxdb_read(dict_t db) {
4466     dict_iterator_t it;
4467     struct record_data *rd;
4468
4469     for (it=dict_first(db); it; it=iter_next(it)) {
4470         rd = iter_data(it);
4471         nickserv_db_read_handle(iter_key(it), rd->d.object);
4472     }
4473     return 0;
4474 }
4475
4476 static NICKSERV_FUNC(cmd_mergedb)
4477 {
4478     struct timeval start, stop;
4479     dict_t db;
4480
4481     NICKSERV_MIN_PARMS(2);
4482     gettimeofday(&start, NULL);
4483     if (!(db = parse_database(argv[1]))) {
4484         reply("NSMSG_DB_UNREADABLE", argv[1]);
4485         return 0;
4486     }
4487     nickserv_saxdb_read(db);
4488     free_database(db);
4489     gettimeofday(&stop, NULL);
4490     stop.tv_sec -= start.tv_sec;
4491     stop.tv_usec -= start.tv_usec;
4492     if (stop.tv_usec < 0) {
4493         stop.tv_sec -= 1;
4494         stop.tv_usec += 1000000;
4495     }
4496     reply("NSMSG_DB_MERGED", argv[1], (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
4497     return 1;
4498 }
4499
4500 static void
4501 expire_handles(UNUSED_ARG(void *data))
4502 {
4503     dict_iterator_t it, next;
4504     unsigned long expiry;
4505     struct handle_info *hi;
4506
4507     for (it=dict_first(nickserv_handle_dict); it; it=next) {
4508         next = iter_next(it);
4509         hi = iter_data(it);
4510         if ((hi->opserv_level > 0)
4511             || hi->users
4512             || HANDLE_FLAGGED(hi, FROZEN)
4513             || HANDLE_FLAGGED(hi, NODELETE)) {
4514             continue;
4515         }
4516         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
4517         if ((now - hi->lastseen) > expiry) {
4518             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
4519             nickserv_unregister_handle(hi, NULL);
4520         }
4521     }
4522
4523     if (nickserv_conf.handle_expire_frequency)
4524         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
4525 }
4526
4527 static void
4528 nickserv_load_dict(const char *fname)
4529 {
4530     FILE *file;
4531     char line[128];
4532     if (!(file = fopen(fname, "r"))) {
4533         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
4534         return;
4535     }
4536     while (fgets(line, sizeof(line), file)) {
4537         if (!line[0])
4538             continue;
4539         if (line[strlen(line)-1] == '\n')
4540             line[strlen(line)-1] = 0;
4541         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
4542     }
4543     fclose(file);
4544     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
4545 }
4546
4547 static enum reclaim_action
4548 reclaim_action_from_string(const char *str) {
4549     if (!str)
4550         return RECLAIM_NONE;
4551     else if (!irccasecmp(str, "warn"))
4552         return RECLAIM_WARN;
4553     else if (!irccasecmp(str, "svsnick"))
4554         return RECLAIM_SVSNICK;
4555     else if (!irccasecmp(str, "kill"))
4556         return RECLAIM_KILL;
4557     else
4558         return RECLAIM_NONE;
4559 }
4560
4561 static void
4562 nickserv_conf_read(void)
4563 {
4564     dict_t conf_node, child;
4565     const char *str;
4566     dict_iterator_t it;
4567
4568     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
4569         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
4570         return;
4571     }
4572     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
4573     if (!str)
4574         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
4575     if (nickserv_conf.valid_handle_regex_set)
4576         regfree(&nickserv_conf.valid_handle_regex);
4577     if (str) {
4578         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
4579         nickserv_conf.valid_handle_regex_set = !err;
4580         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
4581     } else {
4582         nickserv_conf.valid_handle_regex_set = 0;
4583     }
4584     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
4585     if (nickserv_conf.valid_nick_regex_set)
4586         regfree(&nickserv_conf.valid_nick_regex);
4587     if (str) {
4588         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
4589         nickserv_conf.valid_nick_regex_set = !err;
4590         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
4591     } else {
4592         nickserv_conf.valid_nick_regex_set = 0;
4593     }
4594     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
4595     if (!str)
4596         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
4597     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
4598     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
4599     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
4600     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
4601     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
4602     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
4603     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
4604     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
4605     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
4606     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
4607     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
4608     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
4609     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
4610     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
4611     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
4612     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
4613     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
4614     str = database_get_data(conf_node, KEY_MODSTAFF_LEVEL, RECDB_QSTRING);
4615     nickserv_conf.modstaff_level = str ? strtoul(str, NULL, 0) : 800;
4616     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
4617     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
4618     str = database_get_data(conf_node, KEY_SET_TITLE_LEVEL, RECDB_QSTRING);
4619     nickserv_conf.set_title_level = str ? strtoul(str, NULL, 0) : 900;
4620     str = database_get_data(conf_node, KEY_SET_FAKEHOST_LEVEL, RECDB_QSTRING);
4621     nickserv_conf.set_fakehost_level = str ? strtoul(str, NULL, 0) : 1000;
4622     str = database_get_data(conf_node, KEY_SET_FAKEIDENT_LEVEL, RECDB_QSTRING);
4623     nickserv_conf.set_fakeident_level = str ? strtoul(str, NULL, 0) : 1000;
4624     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
4625     if (!str)
4626         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
4627     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
4628     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
4629     if (!str)
4630         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
4631     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
4632     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
4633     if (!str)
4634         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
4635     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
4636     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
4637     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
4638     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
4639     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
4640     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
4641     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
4642     str = database_get_data(conf_node, KEY_MAX_AUTHLOG_LEN, RECDB_QSTRING);
4643     nickserv_conf.max_authlog_len = str ? strtoul(str, NULL, 0) : 30;
4644     str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
4645     nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
4646     str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
4647     if (!str)
4648         str = "ShgsfnHbu";
4649     nickserv_conf.ounregister_flags = 0;
4650     while(*str) {
4651         unsigned int pos = handle_inverse_flags[(unsigned char)*str];
4652         str++;
4653         if(pos)
4654             nickserv_conf.ounregister_flags |= 1 << (pos - 1);
4655     }
4656     str = database_get_data(conf_node, KEY_HANDLE_TS_MODE, RECDB_QSTRING);
4657     if (!str)
4658         nickserv_conf.handle_ts_mode = TS_IGNORE;
4659     else if (!irccasecmp(str, "ircu"))
4660         nickserv_conf.handle_ts_mode = TS_IRCU;
4661     else
4662         nickserv_conf.handle_ts_mode = TS_IGNORE;
4663     if (!nickserv_conf.disable_nicks) {
4664         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
4665         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
4666         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
4667         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
4668         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
4669         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
4670         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
4671         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
4672     }
4673     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
4674     for (it=dict_first(child); it; it=iter_next(it)) {
4675         const char *key = iter_key(it), *value;
4676         unsigned char flag;
4677         int pos;
4678
4679         if (!strncasecmp(key, "uc_", 3))
4680             flag = toupper(key[3]);
4681         else if (!strncasecmp(key, "lc_", 3))
4682             flag = tolower(key[3]);
4683         else
4684             flag = key[0];
4685
4686         if ((pos = handle_inverse_flags[flag])) {
4687             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
4688             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
4689         }
4690     }
4691     if (nickserv_conf.weak_password_dict)
4692         dict_delete(nickserv_conf.weak_password_dict);
4693     nickserv_conf.weak_password_dict = dict_new();
4694     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
4695     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
4696     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
4697     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
4698     if (str)
4699         nickserv_load_dict(str);
4700     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
4701     if (nickserv && str)
4702         NickChange(nickserv, str, 0);
4703     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
4704     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
4705     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
4706     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
4707     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
4708     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
4709     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
4710     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
4711     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
4712     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
4713     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
4714     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
4715     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
4716     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
4717     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
4718     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
4719     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
4720     titlehost_suffix = str ? str : "example.net";
4721     str = conf_get_data("server/network", RECDB_QSTRING);
4722     nickserv_conf.network_name = str ? str : "some IRC network";
4723     if (!nickserv_conf.auth_policer_params) {
4724         nickserv_conf.auth_policer_params = policer_params_new();
4725         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
4726         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
4727     }
4728     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
4729     for (it=dict_first(child); it; it=iter_next(it))
4730         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
4731 }
4732
4733 static void
4734 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
4735     const char *msg;
4736     char newnick[NICKLEN+1];
4737
4738     assert(user);
4739     assert(ni);
4740     switch (action) {
4741     case RECLAIM_NONE:
4742         /* do nothing */
4743         break;
4744     case RECLAIM_WARN:
4745         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
4746         break;
4747     case RECLAIM_SVSNICK:
4748         do {
4749             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
4750         } while (GetUserH(newnick));
4751         irc_svsnick(nickserv, user, newnick);
4752         break;
4753     case RECLAIM_KILL:
4754         msg = user_find_message(user, "NSMSG_RECLAIM_KILL");
4755         DelUser(user, nickserv, 1, msg);
4756         break;
4757     }
4758 }
4759
4760 static void
4761 nickserv_reclaim_p(void *data) {
4762     struct userNode *user = data;
4763     struct nick_info *ni = get_nick_info(user->nick);
4764     if (ni)
4765         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
4766 }
4767
4768 static void
4769 check_user_nick(struct userNode *user) {
4770     //check if this user is a pending LOC user
4771     if(pendingLOCUsers) {
4772         struct pendingLOCUser *pending, *next, *prev = NULL;
4773         int remove;
4774         for(pending = pendingLOCUsers; pending; pending = next) {
4775             next = pending->next;
4776             remove = 0;
4777             if(user->handle_info == pending->handle_info) {
4778                 pending->authlog->user = user;
4779                 free((char*) pending->authlog->hostmask);
4780                 pending->authlog->hostmask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST);
4781                 remove = 1;
4782             } else if(now - pending->time > 10)
4783                 remove = 1;
4784             if(remove) {
4785                 if(prev)
4786                     prev->next = next;
4787                 else
4788                     pendingLOCUsers = next;
4789                 free(pending);
4790             }
4791         }
4792     }
4793     struct nick_info *ni;
4794     user->modes &= ~FLAGS_REGNICK;
4795     if (!(ni = get_nick_info(user->nick)))
4796         return;
4797     if (user->handle_info == ni->owner) {
4798         user->modes |= FLAGS_REGNICK;
4799         irc_regnick(user);
4800         return;
4801     }
4802     if (nickserv_conf.warn_nick_owned)
4803         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
4804     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
4805         return;
4806     if (nickserv_conf.auto_reclaim_delay)
4807         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
4808     else
4809         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
4810 }
4811
4812 void
4813 handle_account(struct userNode *user, const char *stamp, unsigned long timestamp, unsigned long serial)
4814 {
4815     struct handle_info *hi = NULL;
4816
4817     if (stamp != NULL)
4818         hi = dict_find(nickserv_handle_dict, stamp, NULL);
4819     if ((hi == NULL) && (serial != 0)) {
4820         char id[IDLEN + 1];
4821         inttobase64(id, serial, IDLEN);
4822         hi = dict_find(nickserv_id_dict, id, NULL);
4823     }
4824
4825     if (hi) {
4826         if ((nickserv_conf.handle_ts_mode == TS_IRCU)
4827             && (timestamp != hi->registered)) {
4828             return;
4829         }
4830         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
4831             return;
4832         }
4833         set_user_handle_info(user, hi, 0);
4834     } else {
4835         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s:%lu:%lu.", user->nick, stamp, timestamp, serial);
4836     }
4837 }
4838
4839 void
4840 handle_nick_change(struct userNode *user, const char *old_nick)
4841 {
4842     struct handle_info *hi;
4843
4844     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
4845         dict_remove(nickserv_allow_auth_dict, old_nick);
4846         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
4847     }
4848     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4849     check_user_nick(user);
4850 }
4851
4852 void
4853 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), const char *why)
4854 {
4855     if(user->handle_info) {
4856         //check if theres an open authlog entry
4857         struct authlogEntry *authlog;
4858         for(authlog = user->handle_info->authlog; authlog; authlog = authlog->next) {
4859             if(authlog->user == user) {
4860                 authlog->user = NULL;
4861                 authlog->logout_time = now;
4862                 authlog->quit_reason = strdup(why);
4863                 break;
4864             }
4865         }
4866     }
4867     dict_remove(nickserv_allow_auth_dict, user->nick);
4868     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
4869     set_user_handle_info(user, NULL, 0);
4870 }
4871
4872 static struct modcmd *
4873 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
4874 {
4875     if (min_level > 0) {
4876         char buf[16];
4877         sprintf(buf, "%u", min_level);
4878         if (must_be_qualified) {
4879             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
4880         } else {
4881             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
4882         }
4883     } else if (min_level == 0) {
4884         if (must_be_qualified) {
4885             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4886         } else {
4887             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
4888         }
4889     } else {
4890         if (must_be_qualified) {
4891             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
4892         } else {
4893             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
4894         }
4895     }
4896 }
4897
4898 static void
4899 nickserv_db_cleanup(void)
4900 {
4901     unreg_del_user_func(nickserv_remove_user);
4902     userList_clean(&curr_helpers);
4903     policer_params_delete(nickserv_conf.auth_policer_params);
4904     dict_delete(nickserv_handle_dict);
4905     dict_delete(nickserv_nick_dict);
4906     dict_delete(nickserv_opt_dict);
4907     dict_delete(nickserv_allow_auth_dict);
4908     dict_delete(nickserv_email_dict);
4909     dict_delete(nickserv_id_dict);
4910     dict_delete(nickserv_conf.weak_password_dict);
4911     free(auth_func_list);
4912     free(unreg_func_list);
4913     free(rf_list);
4914     free(allowauth_func_list);
4915     free(handle_merge_func_list);
4916     free(failpw_func_list);
4917     if (nickserv_conf.valid_handle_regex_set)
4918         regfree(&nickserv_conf.valid_handle_regex);
4919     if (nickserv_conf.valid_nick_regex_set)
4920         regfree(&nickserv_conf.valid_nick_regex);
4921     struct pendingLOCUser *pending, *next;
4922     for(pending = pendingLOCUsers; pending; pending = next) {
4923         next = pending->next;
4924         free(pending);
4925     }
4926     pendingLOCUsers = NULL;
4927 }
4928
4929 void
4930 init_nickserv(const char *nick)
4931 {
4932     unsigned int i;
4933     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
4934     reg_new_user_func(check_user_nick);
4935     reg_nick_change_func(handle_nick_change);
4936     reg_del_user_func(nickserv_remove_user);
4937     reg_account_func(handle_account);
4938
4939     /* set up handle_inverse_flags */
4940     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
4941     for (i=0; handle_flags[i]; i++) {
4942         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
4943         flag_access_levels[i] = 0;
4944     }
4945
4946     conf_register_reload(nickserv_conf_read);
4947     nickserv_opt_dict = dict_new();
4948     nickserv_email_dict = dict_new();
4949     dict_set_free_keys(nickserv_email_dict, free);
4950     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
4951
4952     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
4953     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
4954     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
4955     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
4956     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
4957     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
4958     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
4959     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
4960     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
4961     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
4962     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
4963     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
4964     nickserv_define_func("SET", cmd_set, -1, 1, 0);
4965     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
4966     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
4967     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
4968     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
4969     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
4970     nickserv_define_func("MERGE", cmd_merge, 750, 1, 0);
4971     nickserv_define_func("ADDNOTE", cmd_addnote, 0, 1, 0);
4972     nickserv_define_func("DELNOTE", cmd_delnote, 0, 1, 0);
4973     nickserv_define_func("NOTES", cmd_notes, 0, 1, 0);
4974     if (!nickserv_conf.disable_nicks) {
4975         /* nick management commands */
4976         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
4977         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
4978         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
4979         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
4980         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
4981         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
4982     }
4983     if (nickserv_conf.email_enabled) {
4984         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
4985         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
4986         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
4987         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
4988         nickserv_define_func("ODELCOOKIE", cmd_odelcookie, 0, 1, 0);
4989         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
4990     }
4991     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
4992     /* miscellaneous commands */
4993     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
4994     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
4995     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
4996     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
4997     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
4998     nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0);
4999     nickserv_define_func("AUTHLOG", cmd_authlog, -1, 1, 0);
5000     nickserv_define_func("OAUTHLOG", cmd_oauthlog, 0, 1, 0);
5001     /* other options */
5002     dict_insert(nickserv_opt_dict, "INFO", opt_info);
5003     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
5004     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
5005     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
5006     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
5007     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
5008     dict_insert(nickserv_opt_dict, "AUTOHIDE", opt_autohide);
5009     dict_insert(nickserv_opt_dict, "PASS", opt_password);
5010     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
5011     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
5012     dict_insert(nickserv_opt_dict, "WEBSITE", opt_website);
5013     dict_insert(nickserv_opt_dict, "DEVNULL", opt_devnull);
5014     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
5015     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
5016     dict_insert(nickserv_opt_dict, "STAFF", opt_staff_level);
5017     dict_insert(nickserv_opt_dict, "STAFF_LEVEL", opt_staff_level);
5018     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
5019     if (titlehost_suffix) {
5020         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
5021         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
5022         dict_insert(nickserv_opt_dict, "FAKEIDENT", opt_fakeident);
5023     }
5024     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
5025     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
5026     dict_insert(nickserv_opt_dict, "KARMA", opt_karma);
5027     nickserv_define_func("OSET KARMA", NULL, 0, 1, 0);
5028
5029     nickserv_handle_dict = dict_new();
5030     dict_set_free_keys(nickserv_handle_dict, free);
5031     dict_set_free_data(nickserv_handle_dict, free_handle_info);
5032
5033     nickserv_id_dict = dict_new();
5034     dict_set_free_keys(nickserv_id_dict, free);
5035
5036     nickserv_nick_dict = dict_new();
5037     dict_set_free_data(nickserv_nick_dict, free);
5038
5039     nickserv_allow_auth_dict = dict_new();
5040
5041     userList_init(&curr_helpers);
5042
5043     if (nick) {
5044         const char *modes = conf_get_data("services/nickserv/modes", RECDB_QSTRING);
5045         nickserv = AddLocalUser(nick, nick, NULL, "Nick Services", modes);
5046         nickserv_service = service_register(nickserv);
5047     }
5048     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
5049     reg_exit_func(nickserv_db_cleanup);
5050     if(nickserv_conf.handle_expire_frequency)
5051         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
5052     message_register_table(msgtab);
5053 }