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