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