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