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