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