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