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