Do not display account notes to non-staff.
[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 (oper_has_access(user, cmd->parent->bot, 0, 1) || IsSupport(user)) {
1365         if (!hi->notes) {
1366             reply("NSMSG_HANDLEINFO_NO_NOTES");
1367         } else {
1368             struct handle_note *prev, *note;
1369
1370             WALK_NOTES(hi, prev, note) {
1371                 char set_time[INTERVALLEN];
1372                 intervalString(set_time, now - note->set, user->handle_info);
1373                 if (note->expires) {
1374                     char exp_time[INTERVALLEN];
1375                     intervalString(exp_time, note->expires - now, user->handle_info);
1376                     reply("NSMSG_HANDLEINFO_NOTE_EXPIRES", note->id, set_time, note->setter, exp_time, note->note);
1377                 } else {
1378                     reply("NSMSG_HANDLEINFO_NOTE", note->id, set_time, note->setter, note->note);
1379                 }
1380             }
1381         }
1382     }
1383
1384     if (hi->flags) {
1385         unsigned long flen = 1;
1386         char flags[34]; /* 32 bits possible plus '+' and '\0' */
1387         flags[0] = '+';
1388         for (i=0, flen=1; handle_flags[i]; i++)
1389             if (hi->flags & 1 << i)
1390                 flags[flen++] = handle_flags[i];
1391         flags[flen] = 0;
1392         reply("NSMSG_HANDLEINFO_FLAGS", flags);
1393     } else {
1394         reply("NSMSG_HANDLEINFO_FLAGS", nsmsg_none);
1395     }
1396
1397     if (HANDLE_FLAGGED(hi, SUPPORT_HELPER)
1398         || HANDLE_FLAGGED(hi, NETWORK_HELPER)
1399         || (hi->opserv_level > 0)) {
1400         reply("NSMSG_HANDLEINFO_EPITHET", (hi->epithet ? hi->epithet : nsmsg_none));
1401     }
1402
1403     if (hi->fakehost)
1404         reply("NSMSG_HANDLEINFO_FAKEHOST", (hi->fakehost ? hi->fakehost : handle_find_message(hi, "MSG_NONE")));
1405
1406     if (hi->last_quit_host[0])
1407         reply("NSMSG_HANDLEINFO_LAST_HOST", hi->last_quit_host);
1408     else
1409         reply("NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN");
1410
1411     if (nickserv_conf.disable_nicks) {
1412         /* nicks disabled; don't show anything about registered nicks */
1413     } else if (hi->nicks) {
1414         struct nick_info *ni, *next_ni;
1415         for (ni = hi->nicks; ni; ni = next_ni) {
1416             herelen = strlen(ni->nick);
1417             if (pos + herelen + 1 > ArrayLength(buff)) {
1418                 next_ni = ni;
1419                 goto print_nicks_buff;
1420             } else {
1421                 next_ni = ni->next;
1422             }
1423             memcpy(buff+pos, ni->nick, herelen);
1424             pos += herelen; buff[pos++] = ' ';
1425             if (!next_ni) {
1426               print_nicks_buff:
1427                 buff[pos-1] = 0;
1428                 reply("NSMSG_HANDLEINFO_NICKS", buff);
1429                 pos = 0;
1430             }
1431         }
1432     } else {
1433         reply("NSMSG_HANDLEINFO_NICKS", nsmsg_none);
1434     }
1435
1436     if (hi->masks->used) {
1437         for (i=0; i < hi->masks->used; i++) {
1438             herelen = strlen(hi->masks->list[i]);
1439             if (pos + herelen + 1 > ArrayLength(buff)) {
1440                 i--;
1441                 goto print_mask_buff;
1442             }
1443             memcpy(buff+pos, hi->masks->list[i], herelen);
1444             pos += herelen; buff[pos++] = ' ';
1445             if (i+1 == hi->masks->used) {
1446               print_mask_buff:
1447                 buff[pos-1] = 0;
1448                 reply("NSMSG_HANDLEINFO_MASKS", buff);
1449                 pos = 0;
1450             }
1451         }
1452     } else {
1453         reply("NSMSG_HANDLEINFO_MASKS", nsmsg_none);
1454     }
1455
1456     if (hi->channels) {
1457         struct userData *channel, *next;
1458         char *name;
1459
1460         for (channel = hi->channels; channel; channel = next) {
1461             next = channel->u_next;
1462             name = channel->channel->channel->name;
1463             herelen = strlen(name);
1464             if (pos + herelen + 7 > ArrayLength(buff)) {
1465                 next = channel;
1466                 goto print_chans_buff;
1467             }
1468             if (IsUserSuspended(channel))
1469                 buff[pos++] = '-';
1470             pos += sprintf(buff+pos, "%d:%s ", channel->access, name);
1471             if (next == NULL) {
1472               print_chans_buff:
1473                 buff[pos-1] = 0;
1474                 reply("NSMSG_HANDLEINFO_CHANNELS", buff);
1475                 pos = 0;
1476             }
1477         }
1478     } else {
1479         reply("NSMSG_HANDLEINFO_CHANNELS", nsmsg_none);
1480     }
1481
1482     for (target = hi->users; target; target = next_un) {
1483         herelen = strlen(target->nick);
1484         if (pos + herelen + 1 > ArrayLength(buff)) {
1485             next_un = target;
1486             goto print_cnick_buff;
1487         } else {
1488             next_un = target->next_authed;
1489         }
1490         memcpy(buff+pos, target->nick, herelen);
1491         pos += herelen; buff[pos++] = ' ';
1492         if (!next_un) {
1493           print_cnick_buff:
1494             buff[pos-1] = 0;
1495             reply("NSMSG_HANDLEINFO_CURRENT", buff);
1496             pos = 0;
1497         }
1498     }
1499
1500     return 1;
1501 }
1502
1503 static NICKSERV_FUNC(cmd_userinfo)
1504 {
1505     struct userNode *target;
1506
1507     NICKSERV_MIN_PARMS(2);
1508     if (!(target = GetUserH(argv[1]))) {
1509         reply("MSG_NICK_UNKNOWN", argv[1]);
1510         return 0;
1511     }
1512     if (target->handle_info)
1513         reply("NSMSG_USERINFO_AUTHED_AS", target->nick, target->handle_info->handle);
1514     else
1515         reply("NSMSG_USERINFO_NOT_AUTHED", target->nick);
1516     return 1;
1517 }
1518
1519 static NICKSERV_FUNC(cmd_nickinfo)
1520 {
1521     struct nick_info *ni;
1522
1523     NICKSERV_MIN_PARMS(2);
1524     if (!(ni = get_nick_info(argv[1]))) {
1525         reply("MSG_NICK_UNKNOWN", argv[1]);
1526         return 0;
1527     }
1528     reply("NSMSG_NICKINFO_OWNER", ni->nick, ni->owner->handle);
1529     return 1;
1530 }
1531
1532 static NICKSERV_FUNC(cmd_notes)
1533 {
1534     struct handle_info *hi;
1535     struct handle_note *prev, *note;
1536     unsigned int hits;
1537
1538     NICKSERV_MIN_PARMS(2);
1539     if (!(hi = get_victim_oper(user, argv[1])))
1540         return 0;
1541     hits = 0;
1542     WALK_NOTES(hi, prev, note) {
1543         char set_time[INTERVALLEN];
1544         intervalString(set_time, now - note->set, user->handle_info);
1545         if (note->expires) {
1546             char exp_time[INTERVALLEN];
1547             intervalString(exp_time, note->expires - now, user->handle_info);
1548             reply("NSMSG_NOTE_EXPIRES", note->id, set_time, note->setter, exp_time, note->note);
1549         } else {
1550             reply("NSMSG_NOTE", note->id, set_time, note->setter, note->note);
1551         }
1552         ++hits;
1553     }
1554     reply("NSMSG_NOTE_COUNT", hits, argv[1]);
1555     return 1;
1556 }
1557
1558 static NICKSERV_FUNC(cmd_rename_handle)
1559 {
1560     struct handle_info *hi;
1561     char msgbuf[MAXLEN], *old_handle;
1562     unsigned int nn;
1563
1564     NICKSERV_MIN_PARMS(3);
1565     if (!(hi = get_victim_oper(user, argv[1])))
1566         return 0;
1567     if (!is_valid_handle(argv[2])) {
1568         reply("NSMSG_FAIL_RENAME", argv[1], argv[2]);
1569         return 0;
1570     }
1571     if (get_handle_info(argv[2])) {
1572         reply("NSMSG_HANDLE_EXISTS", argv[2]);
1573         return 0;
1574     }
1575
1576     dict_remove2(nickserv_handle_dict, old_handle = hi->handle, 1);
1577     hi->handle = strdup(argv[2]);
1578     dict_insert(nickserv_handle_dict, hi->handle, hi);
1579     for (nn=0; nn<rf_list_used; nn++)
1580         rf_list[nn](hi, old_handle);
1581     snprintf(msgbuf, sizeof(msgbuf), "%s renamed account %s to %s.", user->handle_info->handle, old_handle, hi->handle);
1582     reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
1583     global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
1584     free(old_handle);
1585     return 1;
1586 }
1587
1588 static failpw_func_t *failpw_func_list;
1589 static unsigned int failpw_func_size = 0, failpw_func_used = 0;
1590
1591 void
1592 reg_failpw_func(failpw_func_t func)
1593 {
1594     if (failpw_func_used == failpw_func_size) {
1595         if (failpw_func_size) {
1596             failpw_func_size <<= 1;
1597             failpw_func_list = realloc(failpw_func_list, failpw_func_size*sizeof(failpw_func_t));
1598         } else {
1599             failpw_func_size = 8;
1600             failpw_func_list = malloc(failpw_func_size*sizeof(failpw_func_t));
1601         }
1602     }
1603     failpw_func_list[failpw_func_used++] = func;
1604 }
1605
1606 static NICKSERV_FUNC(cmd_auth)
1607 {
1608     int pw_arg, used, maxlogins;
1609     struct handle_info *hi;
1610     const char *passwd;
1611     struct userNode *other;
1612
1613     if (user->handle_info) {
1614         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1615         return 0;
1616     }
1617     if (IsStamped(user)) {
1618         /* Unauthenticated users might still have been stamped
1619            previously and could therefore have a hidden host;
1620            do not allow them to authenticate. */
1621         reply("NSMSG_STAMPED_AUTH");
1622         return 0;
1623     }
1624     if (argc == 3) {
1625         hi = dict_find(nickserv_handle_dict, argv[1], NULL);
1626         pw_arg = 2;
1627     } else if (argc == 2) {
1628         if (nickserv_conf.disable_nicks) {
1629             if (!(hi = get_handle_info(user->nick))) {
1630                 reply("NSMSG_HANDLE_NOT_FOUND");
1631                 return 0;
1632             }
1633         } else {
1634             /* try to look up their handle from their nick */
1635             struct nick_info *ni;
1636             ni = get_nick_info(user->nick);
1637             if (!ni) {
1638                 reply("NSMSG_NICK_NOT_REGISTERED", user->nick);
1639                 return 0;
1640             }
1641             hi = ni->owner;
1642         }
1643         pw_arg = 1;
1644     } else {
1645         reply("MSG_MISSING_PARAMS", argv[0]);
1646         svccmd_send_help(user, nickserv, cmd);
1647         return 0;
1648     }
1649     if (!hi) {
1650         reply("NSMSG_HANDLE_NOT_FOUND");
1651         return 0;
1652     }
1653     /* Responses from here on look up the language used by the handle they asked about. */
1654     passwd = argv[pw_arg];
1655     if (!valid_user_for(user, hi)) {
1656         if (hi->email_addr && nickserv_conf.email_enabled)
1657             send_message_type(4, user, cmd->parent->bot,
1658                               handle_find_message(hi, "NSMSG_USE_AUTHCOOKIE"),
1659                               hi->handle);
1660         else
1661             send_message_type(4, user, cmd->parent->bot,
1662                               handle_find_message(hi, "NSMSG_HOSTMASK_INVALID"),
1663                               hi->handle);
1664         argv[pw_arg] = "BADMASK";
1665         return 1;
1666     }
1667     if (!checkpass(passwd, hi->passwd)) {
1668         unsigned int n;
1669         send_message_type(4, user, cmd->parent->bot,
1670                           handle_find_message(hi, "NSMSG_PASSWORD_INVALID"));
1671         argv[pw_arg] = "BADPASS";
1672         for (n=0; n<failpw_func_used; n++) failpw_func_list[n](user, hi);
1673         if (nickserv_conf.autogag_enabled) {
1674             if (!user->auth_policer.params) {
1675                 user->auth_policer.last_req = now;
1676                 user->auth_policer.params = nickserv_conf.auth_policer_params;
1677             }
1678             if (!policer_conforms(&user->auth_policer, now, 1.0)) {
1679                 char *hostmask;
1680                 hostmask = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_BYIP|GENMASK_NO_HIDING);
1681                 log_module(NS_LOG, LOG_INFO, "%s auto-gagged for repeated password guessing.", hostmask);
1682                 gag_create(hostmask, nickserv->nick, "Repeated password guessing.", now+nickserv_conf.autogag_duration);
1683                 free(hostmask);
1684                 argv[pw_arg] = "GAGGED";
1685             }
1686         }
1687         return 1;
1688     }
1689     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1690         send_message_type(4, user, cmd->parent->bot,
1691                           handle_find_message(hi, "NSMSG_HANDLE_SUSPENDED"));
1692         argv[pw_arg] = "SUSPENDED";
1693         return 1;
1694     }
1695     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
1696     for (used = 0, other = hi->users; other; other = other->next_authed) {
1697         if (++used >= maxlogins) {
1698             send_message_type(4, user, cmd->parent->bot,
1699                               handle_find_message(hi, "NSMSG_MAX_LOGINS"),
1700                               maxlogins);
1701             argv[pw_arg] = "MAXLOGINS";
1702             return 1;
1703         }
1704     }
1705
1706     set_user_handle_info(user, hi, 1);
1707     if (nickserv_conf.email_required && !hi->email_addr)
1708         reply("NSMSG_PLEASE_SET_EMAIL");
1709     if (!is_secure_password(hi->handle, passwd, NULL))
1710         reply("NSMSG_WEAK_PASSWORD");
1711     if (hi->passwd[0] != '$')
1712         cryptpass(passwd, hi->passwd);
1713     if (!hi->masks->used) {
1714         irc_in_addr_t ip;
1715         string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1716         if (irc_in_addr_is_valid(user->ip) && irc_pton(&ip, NULL, user->hostname))
1717             string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
1718     }
1719     argv[pw_arg] = "****";
1720     reply("NSMSG_AUTH_SUCCESS");
1721     return 1;
1722 }
1723
1724 static allowauth_func_t *allowauth_func_list;
1725 static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
1726
1727 void
1728 reg_allowauth_func(allowauth_func_t func)
1729 {
1730     if (allowauth_func_used == allowauth_func_size) {
1731         if (allowauth_func_size) {
1732             allowauth_func_size <<= 1;
1733             allowauth_func_list = realloc(allowauth_func_list, allowauth_func_size*sizeof(allowauth_func_t));
1734         } else {
1735             allowauth_func_size = 8;
1736             allowauth_func_list = malloc(allowauth_func_size*sizeof(allowauth_func_t));
1737         }
1738     }
1739     allowauth_func_list[allowauth_func_used++] = func;
1740 }
1741
1742 static NICKSERV_FUNC(cmd_allowauth)
1743 {
1744     struct userNode *target;
1745     struct handle_info *hi;
1746     unsigned int n;
1747
1748     NICKSERV_MIN_PARMS(2);
1749     if (!(target = GetUserH(argv[1]))) {
1750         reply("MSG_NICK_UNKNOWN", argv[1]);
1751         return 0;
1752     }
1753     if (target->handle_info) {
1754         reply("NSMSG_USER_PREV_AUTH", target->nick);
1755         return 0;
1756     }
1757     if (IsStamped(target)) {
1758         /* Unauthenticated users might still have been stamped
1759            previously and could therefore have a hidden host;
1760            do not allow them to authenticate to an account. */
1761         reply("NSMSG_USER_PREV_STAMP", target->nick);
1762         return 0;
1763     }
1764     if (argc == 2)
1765         hi = NULL;
1766     else if (!(hi = get_handle_info(argv[2]))) {
1767         reply("MSG_HANDLE_UNKNOWN", argv[2]);
1768         return 0;
1769     }
1770     if (hi) {
1771         if (hi->opserv_level > user->handle_info->opserv_level) {
1772             reply("MSG_USER_OUTRANKED", hi->handle);
1773             return 0;
1774         }
1775         if (((hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER))
1776              || (hi->opserv_level > 0))
1777             && ((argc < 4) || irccasecmp(argv[3], "staff"))) {
1778             reply("NSMSG_ALLOWAUTH_STAFF", hi->handle);
1779             return 0;
1780         }
1781         dict_insert(nickserv_allow_auth_dict, target->nick, hi);
1782         reply("NSMSG_AUTH_ALLOWED", target->nick, hi->handle);
1783         send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_MSG", hi->handle, hi->handle);
1784         if (nickserv_conf.email_enabled)
1785             send_message(target, nickserv, "NSMSG_AUTH_ALLOWED_EMAIL");
1786     } else {
1787         if (dict_remove(nickserv_allow_auth_dict, target->nick))
1788             reply("NSMSG_AUTH_NORMAL_ONLY", target->nick);
1789         else
1790             reply("NSMSG_AUTH_UNSPECIAL", target->nick);
1791     }
1792     for (n=0; n<allowauth_func_used; n++)
1793         allowauth_func_list[n](user, target, hi);
1794     return 1;
1795 }
1796
1797 static NICKSERV_FUNC(cmd_authcookie)
1798 {
1799     struct handle_info *hi;
1800
1801     NICKSERV_MIN_PARMS(2);
1802     if (user->handle_info) {
1803         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1804         return 0;
1805     }
1806     if (IsStamped(user)) {
1807         /* Unauthenticated users might still have been stamped
1808            previously and could therefore have a hidden host;
1809            do not allow them to authenticate to an account. */
1810         reply("NSMSG_STAMPED_AUTHCOOKIE");
1811         return 0;
1812     }
1813     if (!(hi = get_handle_info(argv[1]))) {
1814         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1815         return 0;
1816     }
1817     if (!hi->email_addr) {
1818         reply("MSG_SET_EMAIL_ADDR");
1819         return 0;
1820     }
1821     nickserv_make_cookie(user, hi, ALLOWAUTH, NULL);
1822     return 1;
1823 }
1824
1825 static NICKSERV_FUNC(cmd_delcookie)
1826 {
1827     struct handle_info *hi;
1828
1829     hi = user->handle_info;
1830     if (!hi->cookie) {
1831         reply("NSMSG_NO_COOKIE");
1832         return 0;
1833     }
1834     switch (hi->cookie->type) {
1835     case ACTIVATION:
1836     case EMAIL_CHANGE:
1837         reply("NSMSG_MUST_TIME_OUT");
1838         break;
1839     default:
1840         nickserv_eat_cookie(hi->cookie);
1841         reply("NSMSG_ATE_COOKIE");
1842         break;
1843     }
1844     return 1;
1845 }
1846
1847 static NICKSERV_FUNC(cmd_resetpass)
1848 {
1849     struct handle_info *hi;
1850     char crypted[MD5_CRYPT_LENGTH];
1851
1852     NICKSERV_MIN_PARMS(3);
1853     if (user->handle_info) {
1854         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
1855         return 0;
1856     }
1857     if (IsStamped(user)) {
1858         /* Unauthenticated users might still have been stamped
1859            previously and could therefore have a hidden host;
1860            do not allow them to activate an account. */
1861         reply("NSMSG_STAMPED_RESETPASS");
1862         return 0;
1863     }
1864     if (!(hi = get_handle_info(argv[1]))) {
1865         reply("MSG_HANDLE_UNKNOWN", argv[1]);
1866         return 0;
1867     }
1868     if (!hi->email_addr) {
1869         reply("MSG_SET_EMAIL_ADDR");
1870         return 0;
1871     }
1872     cryptpass(argv[2], crypted);
1873     argv[2] = "****";
1874     nickserv_make_cookie(user, hi, PASSWORD_CHANGE, crypted);
1875     return 1;
1876 }
1877
1878 static NICKSERV_FUNC(cmd_cookie)
1879 {
1880     struct handle_info *hi;
1881     const char *cookie;
1882
1883     if ((argc == 2) && (hi = user->handle_info) && hi->cookie && (hi->cookie->type == EMAIL_CHANGE)) {
1884         cookie = argv[1];
1885     } else {
1886         NICKSERV_MIN_PARMS(3);
1887         if (!(hi = get_handle_info(argv[1]))) {
1888             reply("MSG_HANDLE_UNKNOWN", argv[1]);
1889             return 0;
1890         }
1891         cookie = argv[2];
1892     }
1893
1894     if (HANDLE_FLAGGED(hi, SUSPENDED)) {
1895         reply("NSMSG_HANDLE_SUSPENDED");
1896         return 0;
1897     }
1898
1899     if (!hi->cookie) {
1900         reply("NSMSG_NO_COOKIE");
1901         return 0;
1902     }
1903
1904     /* Check validity of operation before comparing cookie to
1905      * prohibit guessing by authed users. */
1906     if (user->handle_info
1907         && (hi->cookie->type != EMAIL_CHANGE)
1908         && (hi->cookie->type != PASSWORD_CHANGE)) {
1909         reply("NSMSG_CANNOT_COOKIE");
1910         return 0;
1911     }
1912
1913     if (strcmp(cookie, hi->cookie->cookie)) {
1914         reply("NSMSG_BAD_COOKIE");
1915         return 0;
1916     }
1917
1918     switch (hi->cookie->type) {
1919     case ACTIVATION:
1920         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1921         set_user_handle_info(user, hi, 1);
1922         reply("NSMSG_HANDLE_ACTIVATED");
1923         break;
1924     case PASSWORD_CHANGE:
1925         set_user_handle_info(user, hi, 1);
1926         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
1927         reply("NSMSG_PASSWORD_CHANGED");
1928         break;
1929     case EMAIL_CHANGE:
1930         nickserv_set_email_addr(hi, hi->cookie->data);
1931         reply("NSMSG_EMAIL_CHANGED");
1932         break;
1933     case ALLOWAUTH: {
1934         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
1935         set_user_handle_info(user, hi, 1);
1936         nickserv_addmask(user, hi, mask);
1937         reply("NSMSG_AUTH_SUCCESS");
1938         free(mask);
1939         break;
1940     }
1941     default:
1942         reply("NSMSG_BAD_COOKIE_TYPE", hi->cookie->type);
1943         log_module(NS_LOG, LOG_ERROR, "Bad cookie type %d for account %s.", hi->cookie->type, hi->handle);
1944         break;
1945     }
1946
1947     nickserv_eat_cookie(hi->cookie);
1948
1949     return 1;
1950 }
1951
1952 static NICKSERV_FUNC(cmd_oregnick) {
1953     const char *nick;
1954     struct handle_info *target;
1955     struct nick_info *ni;
1956
1957     NICKSERV_MIN_PARMS(3);
1958     if (!(target = modcmd_get_handle_info(user, argv[1])))
1959         return 0;
1960     nick = argv[2];
1961     if (!is_registerable_nick(nick)) {
1962         reply("NSMSG_BAD_NICK", nick);
1963         return 0;
1964     }
1965     ni = dict_find(nickserv_nick_dict, nick, NULL);
1966     if (ni) {
1967         reply("NSMSG_NICK_EXISTS", nick);
1968         return 0;
1969     }
1970     register_nick(nick, target);
1971     reply("NSMSG_OREGNICK_SUCCESS", nick, target->handle);
1972     return 1;
1973 }
1974
1975 static NICKSERV_FUNC(cmd_regnick) {
1976     unsigned n;
1977     struct nick_info *ni;
1978
1979     if (!is_registerable_nick(user->nick)) {
1980         reply("NSMSG_BAD_NICK", user->nick);
1981         return 0;
1982     }
1983     /* count their nicks, see if it's too many */
1984     for (n=0,ni=user->handle_info->nicks; ni; n++,ni=ni->next) ;
1985     if (n >= nickserv_conf.nicks_per_handle) {
1986         reply("NSMSG_TOO_MANY_NICKS");
1987         return 0;
1988     }
1989     ni = dict_find(nickserv_nick_dict, user->nick, NULL);
1990     if (ni) {
1991         reply("NSMSG_NICK_EXISTS", user->nick);
1992         return 0;
1993     }
1994     register_nick(user->nick, user->handle_info);
1995     reply("NSMSG_REGNICK_SUCCESS", user->nick);
1996     return 1;
1997 }
1998
1999 static NICKSERV_FUNC(cmd_pass)
2000 {
2001     struct handle_info *hi;
2002     const char *old_pass, *new_pass;
2003
2004     NICKSERV_MIN_PARMS(3);
2005     hi = user->handle_info;
2006     old_pass = argv[1];
2007     new_pass = argv[2];
2008     argv[2] = "****";
2009     if (!is_secure_password(hi->handle, new_pass, user)) return 0;
2010     if (!checkpass(old_pass, hi->passwd)) {
2011         argv[1] = "BADPASS";
2012         reply("NSMSG_PASSWORD_INVALID");
2013         return 0;
2014     }
2015     cryptpass(new_pass, hi->passwd);
2016     argv[1] = "****";
2017     reply("NSMSG_PASS_SUCCESS");
2018     return 1;
2019 }
2020
2021 static int
2022 nickserv_addmask(struct userNode *user, struct handle_info *hi, const char *mask)
2023 {
2024     unsigned int i;
2025     char *new_mask = canonicalize_hostmask(strdup(mask));
2026     for (i=0; i<hi->masks->used; i++) {
2027         if (!irccasecmp(new_mask, hi->masks->list[i])) {
2028             send_message(user, nickserv, "NSMSG_ADDMASK_ALREADY", new_mask);
2029             free(new_mask);
2030             return 0;
2031         }
2032     }
2033     string_list_append(hi->masks, new_mask);
2034     send_message(user, nickserv, "NSMSG_ADDMASK_SUCCESS", new_mask);
2035     return 1;
2036 }
2037
2038 static NICKSERV_FUNC(cmd_addmask)
2039 {
2040     if (argc < 2) {
2041         char *mask = generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
2042         int res = nickserv_addmask(user, user->handle_info, mask);
2043         free(mask);
2044         return res;
2045     } else {
2046         if (!is_gline(argv[1])) {
2047             reply("NSMSG_MASK_INVALID", argv[1]);
2048             return 0;
2049         }
2050         return nickserv_addmask(user, user->handle_info, argv[1]);
2051     }
2052 }
2053
2054 static NICKSERV_FUNC(cmd_oaddmask)
2055 {
2056     struct handle_info *hi;
2057
2058     NICKSERV_MIN_PARMS(3);
2059     if (!(hi = get_victim_oper(user, argv[1])))
2060         return 0;
2061     return nickserv_addmask(user, hi, argv[2]);
2062 }
2063
2064 static int
2065 nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask, int force)
2066 {
2067     unsigned int i;
2068     for (i=0; i<hi->masks->used; i++) {
2069         if (!strcmp(del_mask, hi->masks->list[i])) {
2070             char *old_mask = hi->masks->list[i];
2071             if (hi->masks->used == 1 && !force) {
2072                 send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
2073                 return 0;
2074             }
2075             hi->masks->list[i] = hi->masks->list[--hi->masks->used];
2076             send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
2077             free(old_mask);
2078             return 1;
2079         }
2080     }
2081     send_message(user, nickserv, "NSMSG_DELMASK_NOT_FOUND");
2082     return 0;
2083 }
2084
2085 static NICKSERV_FUNC(cmd_delmask)
2086 {
2087     NICKSERV_MIN_PARMS(2);
2088     return nickserv_delmask(user, user->handle_info, argv[1], 0);
2089 }
2090
2091 static NICKSERV_FUNC(cmd_odelmask)
2092 {
2093     struct handle_info *hi;
2094     NICKSERV_MIN_PARMS(3);
2095     if (!(hi = get_victim_oper(user, argv[1])))
2096         return 0;
2097     return nickserv_delmask(user, hi, argv[2], 1);
2098 }
2099
2100 int
2101 nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *padded, unsigned long *premoved) {
2102     unsigned int nn, add = 1, pos;
2103     unsigned long added, removed, flag;
2104
2105     for (added=removed=nn=0; str[nn]; nn++) {
2106         switch (str[nn]) {
2107         case '+': add = 1; break;
2108         case '-': add = 0; break;
2109         default:
2110             if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
2111                 send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
2112                 return 0;
2113             }
2114             if (user && (user->handle_info->opserv_level < flag_access_levels[pos-1])) {
2115                 /* cheesy avoidance of looking up the flag name.. */
2116                 send_message(user, bot, "NSMSG_FLAG_PRIVILEGED", str[nn]);
2117                 return 0;
2118             }
2119             flag = 1 << (pos - 1);
2120             if (add)
2121                 added |= flag, removed &= ~flag;
2122             else
2123                 removed |= flag, added &= ~flag;
2124             break;
2125         }
2126     }
2127     *padded = added;
2128     *premoved = removed;
2129     return 1;
2130 }
2131
2132 static int
2133 nickserv_apply_flags(struct userNode *user, struct handle_info *hi, const char *flags)
2134 {
2135     unsigned long before, after, added, removed;
2136     struct userNode *uNode;
2137
2138     before = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2139     if (!nickserv_modify_handle_flags(user, nickserv, flags, &added, &removed))
2140         return 0;
2141     hi->flags = (hi->flags | added) & ~removed;
2142     after = hi->flags & (HI_FLAG_SUPPORT_HELPER|HI_FLAG_NETWORK_HELPER);
2143
2144     /* Strip helping flag if they're only a support helper and not
2145      * currently in #support. */
2146     if (HANDLE_FLAGGED(hi, HELPING) && (after == HI_FLAG_SUPPORT_HELPER)) {
2147         struct channelList *schannels;
2148         unsigned int ii;
2149         schannels = chanserv_support_channels();
2150         for (uNode = hi->users; uNode; uNode = uNode->next_authed) {
2151             for (ii = 0; ii < schannels->used; ++ii)
2152                 if (GetUserMode(schannels->list[ii], uNode))
2153                     break;
2154             if (ii < schannels->used)
2155                 break;
2156         }
2157         if (!uNode)
2158             HANDLE_CLEAR_FLAG(hi, HELPING);
2159     }
2160
2161     if (after && !before) {
2162         /* Add user to current helper list. */
2163         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2164             userList_append(&curr_helpers, uNode);
2165     } else if (!after && before) {
2166         /* Remove user from current helper list. */
2167         for (uNode = hi->users; uNode; uNode = uNode->next_authed)
2168             userList_remove(&curr_helpers, uNode);
2169     }
2170
2171     return 1;
2172 }
2173
2174 static void
2175 set_list(struct userNode *user, struct handle_info *hi, int override)
2176 {
2177     option_func_t *opt;
2178     unsigned int i;
2179     char *set_display[] = {
2180         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
2181         "EMAIL", "MAXLOGINS", "LANGUAGE"
2182     };
2183
2184     send_message(user, nickserv, "NSMSG_SETTING_LIST");
2185
2186     /* Do this so options are presented in a consistent order. */
2187     for (i = 0; i < ArrayLength(set_display); ++i)
2188         if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
2189             opt(user, hi, override, 0, NULL);
2190 }
2191
2192 static NICKSERV_FUNC(cmd_set)
2193 {
2194     struct handle_info *hi;
2195     option_func_t *opt;
2196
2197     hi = user->handle_info;
2198     if (argc < 2) {
2199         set_list(user, hi, 0);
2200         return 1;
2201     }
2202     if (!(opt = dict_find(nickserv_opt_dict, argv[1], NULL))) {
2203         reply("NSMSG_INVALID_OPTION", argv[1]);
2204         return 0;
2205     }
2206     return opt(user, hi, 0, argc-1, argv+1);
2207 }
2208
2209 static NICKSERV_FUNC(cmd_oset)
2210 {
2211     struct handle_info *hi;
2212     struct svccmd *subcmd;
2213     option_func_t *opt;
2214     char cmdname[MAXLEN];
2215
2216     NICKSERV_MIN_PARMS(2);
2217
2218     if (!(hi = get_victim_oper(user, argv[1])))
2219         return 0;
2220
2221     if (argc < 3) {
2222         set_list(user, hi, 0);
2223         return 1;
2224     }
2225
2226     if (!(opt = dict_find(nickserv_opt_dict, argv[2], NULL))) {
2227         reply("NSMSG_INVALID_OPTION", argv[2]);
2228         return 0;
2229     }
2230
2231     sprintf(cmdname, "%s %s", cmd->name, argv[2]);
2232     subcmd = dict_find(cmd->parent->commands, cmdname, NULL);
2233     if (subcmd && !svccmd_can_invoke(user, cmd->parent->bot, subcmd, NULL, SVCCMD_NOISY))
2234         return 0;
2235
2236     return opt(user, hi, 1, argc-2, argv+2);
2237 }
2238
2239 static OPTION_FUNC(opt_info)
2240 {
2241     const char *info;
2242     if (argc > 1) {
2243         if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
2244             free(hi->infoline);
2245             hi->infoline = NULL;
2246         } else {
2247             hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
2248         }
2249     }
2250
2251     info = hi->infoline ? hi->infoline : user_find_message(user, "MSG_NONE");
2252     send_message(user, nickserv, "NSMSG_SET_INFO", info);
2253     return 1;
2254 }
2255
2256 static OPTION_FUNC(opt_width)
2257 {
2258     if (argc > 1)
2259         hi->screen_width = strtoul(argv[1], NULL, 0);
2260
2261     if ((hi->screen_width > 0) && (hi->screen_width < MIN_LINE_SIZE))
2262         hi->screen_width = MIN_LINE_SIZE;
2263     else if (hi->screen_width > MAX_LINE_SIZE)
2264         hi->screen_width = MAX_LINE_SIZE;
2265
2266     send_message(user, nickserv, "NSMSG_SET_WIDTH", hi->screen_width);
2267     return 1;
2268 }
2269
2270 static OPTION_FUNC(opt_tablewidth)
2271 {
2272     if (argc > 1)
2273         hi->table_width = strtoul(argv[1], NULL, 0);
2274
2275     if ((hi->table_width > 0) && (hi->table_width < MIN_LINE_SIZE))
2276         hi->table_width = MIN_LINE_SIZE;
2277     else if (hi->screen_width > MAX_LINE_SIZE)
2278         hi->table_width = MAX_LINE_SIZE;
2279
2280     send_message(user, nickserv, "NSMSG_SET_TABLEWIDTH", hi->table_width);
2281     return 1;
2282 }
2283
2284 static OPTION_FUNC(opt_color)
2285 {
2286     if (argc > 1) {
2287         if (enabled_string(argv[1]))
2288             HANDLE_SET_FLAG(hi, MIRC_COLOR);
2289         else if (disabled_string(argv[1]))
2290             HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
2291         else {
2292             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2293             return 0;
2294         }
2295     }
2296
2297     send_message(user, nickserv, "NSMSG_SET_COLOR", user_find_message(user, HANDLE_FLAGGED(hi, MIRC_COLOR) ? "MSG_ON" : "MSG_OFF"));
2298     return 1;
2299 }
2300
2301 static OPTION_FUNC(opt_privmsg)
2302 {
2303     if (argc > 1) {
2304         if (enabled_string(argv[1]))
2305             HANDLE_SET_FLAG(hi, USE_PRIVMSG);
2306         else if (disabled_string(argv[1]))
2307             HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
2308         else {
2309             send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
2310             return 0;
2311         }
2312     }
2313
2314     send_message(user, nickserv, "NSMSG_SET_PRIVMSG", user_find_message(user, HANDLE_FLAGGED(hi, USE_PRIVMSG) ? "MSG_ON" : "MSG_OFF"));
2315     return 1;
2316 }
2317
2318 static OPTION_FUNC(opt_style)
2319 {
2320     char *style;
2321
2322     if (argc > 1) {
2323         if (!irccasecmp(argv[1], "Zoot"))
2324             hi->userlist_style = HI_STYLE_ZOOT;
2325         else if (!irccasecmp(argv[1], "def"))
2326             hi->userlist_style = HI_STYLE_DEF;
2327     }
2328
2329     switch (hi->userlist_style) {
2330     case HI_STYLE_DEF:
2331         style = "def";
2332         break;
2333     case HI_STYLE_ZOOT:
2334     default:
2335         style = "Zoot";
2336     }
2337
2338     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
2339     return 1;
2340 }
2341
2342 static OPTION_FUNC(opt_password)
2343 {
2344     if (!override) {
2345         send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
2346         return 0;
2347     }
2348
2349     if (argc > 1)
2350         cryptpass(argv[1], hi->passwd);
2351
2352     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
2353     return 1;
2354 }
2355
2356 static OPTION_FUNC(opt_flags)
2357 {
2358     char flags[33];
2359     unsigned int ii, flen;
2360
2361     if (!override) {
2362         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2363         return 0;
2364     }
2365
2366     if (argc > 1)
2367         nickserv_apply_flags(user, hi, argv[1]);
2368
2369     for (ii = flen = 0; handle_flags[ii]; ii++)
2370         if (hi->flags & (1 << ii))
2371             flags[flen++] = handle_flags[ii];
2372     flags[flen] = '\0';
2373     if (hi->flags)
2374         send_message(user, nickserv, "NSMSG_SET_FLAGS", flags);
2375     else
2376         send_message(user, nickserv, "NSMSG_SET_FLAGS", user_find_message(user, "MSG_NONE"));
2377     return 1;
2378 }
2379
2380 static OPTION_FUNC(opt_email)
2381 {
2382     if (argc > 1) {
2383         const char *str;
2384         if (!is_valid_email_addr(argv[1])) {
2385             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
2386             return 0;
2387         }
2388         if ((str = mail_prohibited_address(argv[1]))) {
2389             send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", argv[1], str);
2390             return 0;
2391         }
2392         if (hi->email_addr && !irccasecmp(hi->email_addr, argv[1]))
2393             send_message(user, nickserv, "NSMSG_EMAIL_SAME");
2394         else if (!override)
2395                 nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1]);
2396         else {
2397             nickserv_set_email_addr(hi, argv[1]);
2398             if (hi->cookie)
2399                 nickserv_eat_cookie(hi->cookie);
2400             send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2401         }
2402     } else
2403         send_message(user, nickserv, "NSMSG_SET_EMAIL", visible_email_addr(user, hi));
2404     return 1;
2405 }
2406
2407 static OPTION_FUNC(opt_maxlogins)
2408 {
2409     unsigned char maxlogins;
2410     if (argc > 1) {
2411         maxlogins = strtoul(argv[1], NULL, 0);
2412         if ((maxlogins > nickserv_conf.hard_maxlogins) && !override) {
2413             send_message(user, nickserv, "NSMSG_BAD_MAX_LOGINS", nickserv_conf.hard_maxlogins);
2414             return 0;
2415         }
2416         hi->maxlogins = maxlogins;
2417     }
2418     maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
2419     send_message(user, nickserv, "NSMSG_SET_MAXLOGINS", maxlogins);
2420     return 1;
2421 }
2422
2423 static OPTION_FUNC(opt_language)
2424 {
2425     struct language *lang;
2426     if (argc > 1) {
2427         lang = language_find(argv[1]);
2428         if (irccasecmp(lang->name, argv[1]))
2429             send_message(user, nickserv, "NSMSG_LANGUAGE_NOT_FOUND", argv[1], lang->name);
2430         hi->language = lang;
2431     }
2432     send_message(user, nickserv, "NSMSG_SET_LANGUAGE", hi->language->name);
2433     return 1;
2434 }
2435
2436 static OPTION_FUNC(opt_karma)
2437 {
2438     if (!override) {
2439         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2440         return 0;
2441     }
2442
2443     if (argc > 1) {
2444         if (argv[1][0] == '+' && isdigit(argv[1][1])) {
2445             hi->karma += strtoul(argv[1] + 1, NULL, 10);
2446         } else if (argv[1][0] == '-' && isdigit(argv[1][1])) {
2447             hi->karma -= strtoul(argv[1] + 1, NULL, 10);
2448         } else {
2449             send_message(user, nickserv, "NSMSG_INVALID_KARMA", argv[1]);
2450         }
2451     }
2452
2453     send_message(user, nickserv, "NSMSG_SET_KARMA", hi->karma);
2454     return 1;
2455 }
2456
2457 int
2458 oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
2459     if (!oper_has_access(user, bot, nickserv_conf.modoper_level, 0))
2460         return 0;
2461     if ((user->handle_info->opserv_level < target->opserv_level)
2462         || ((user->handle_info->opserv_level == target->opserv_level)
2463             && (user->handle_info->opserv_level < 1000))) {
2464         send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
2465         return 0;
2466     }
2467     if ((user->handle_info->opserv_level < new_level)
2468         || ((user->handle_info->opserv_level == new_level)
2469             && (user->handle_info->opserv_level < 1000))) {
2470         send_message(user, bot, "NSMSG_OPSERV_LEVEL_BAD");
2471         return 0;
2472     }
2473     if (user->handle_info == target) {
2474         send_message(user, bot, "MSG_STUPID_ACCESS_CHANGE");
2475         return 0;
2476     }
2477     if (target->opserv_level == new_level)
2478         return 0;
2479     log_module(NS_LOG, LOG_INFO, "Account %s setting oper level for account %s to %d (from %d).",
2480         user->handle_info->handle, target->handle, new_level, target->opserv_level);
2481     target->opserv_level = new_level;
2482     return 1;
2483 }
2484
2485 static OPTION_FUNC(opt_level)
2486 {
2487     int res;
2488
2489     if (!override) {
2490         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2491         return 0;
2492     }
2493
2494     res = (argc > 1) ? oper_try_set_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
2495     send_message(user, nickserv, "NSMSG_SET_LEVEL", hi->opserv_level);
2496     return res;
2497 }
2498
2499 static OPTION_FUNC(opt_epithet)
2500 {
2501     if (!override) {
2502         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2503         return 0;
2504     }
2505
2506     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_epithet_level, 0)) {
2507         char *epithet = unsplit_string(argv+1, argc-1, NULL);
2508         if (hi->epithet)
2509             free(hi->epithet);
2510         if ((epithet[0] == '*') && !epithet[1])
2511             hi->epithet = NULL;
2512         else
2513             hi->epithet = strdup(epithet);
2514     }
2515
2516     if (hi->epithet)
2517         send_message(user, nickserv, "NSMSG_SET_EPITHET", hi->epithet);
2518     else
2519         send_message(user, nickserv, "NSMSG_SET_EPITHET", user_find_message(user, "MSG_NONE"));
2520     return 1;
2521 }
2522
2523 static OPTION_FUNC(opt_title)
2524 {
2525     const char *title;
2526
2527     if (!override) {
2528         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2529         return 0;
2530     }
2531
2532     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_title_level, 0)) {
2533         title = argv[1];
2534         if (strchr(title, '.')) {
2535             send_message(user, nickserv, "NSMSG_TITLE_INVALID");
2536             return 0;
2537         }
2538         if ((strlen(user->handle_info->handle) + strlen(title) +
2539              strlen(nickserv_conf.titlehost_suffix) + 2) > HOSTLEN) {
2540             send_message(user, nickserv, "NSMSG_TITLE_TRUNCATED");
2541             return 0;
2542         }
2543
2544         free(hi->fakehost);
2545         if (!strcmp(title, "*")) {
2546             hi->fakehost = NULL;
2547         } else {
2548             hi->fakehost = malloc(strlen(title)+2);
2549             hi->fakehost[0] = '.';
2550             strcpy(hi->fakehost+1, title);
2551         }
2552         apply_fakehost(hi);
2553     } else if (hi->fakehost && (hi->fakehost[0] == '.'))
2554         title = hi->fakehost + 1;
2555     else
2556         title = NULL;
2557     if (!title)
2558         title = user_find_message(user, "MSG_NONE");
2559     send_message(user, nickserv, "NSMSG_SET_TITLE", title);
2560     return 1;
2561 }
2562
2563 static OPTION_FUNC(opt_fakehost)
2564 {
2565     const char *fake;
2566
2567     if (!override) {
2568         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
2569         return 0;
2570     }
2571
2572     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakehost_level, 0)) {
2573         fake = argv[1];
2574         if ((strlen(fake) > HOSTLEN) || (fake[0] == '.')) {
2575             send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
2576             return 0;
2577         }
2578         free(hi->fakehost);
2579         if (!strcmp(fake, "*"))
2580             hi->fakehost = NULL;
2581         else
2582             hi->fakehost = strdup(fake);
2583         fake = hi->fakehost;
2584         apply_fakehost(hi);
2585     } else
2586         fake = generate_fakehost(hi);
2587     if (!fake)
2588         fake = user_find_message(user, "MSG_NONE");
2589     send_message(user, nickserv, "NSMSG_SET_FAKEHOST", fake);
2590     return 1;
2591 }
2592
2593 static NICKSERV_FUNC(cmd_reclaim)
2594 {
2595     struct handle_info *hi;
2596     struct nick_info *ni;
2597     struct userNode *victim;
2598
2599     NICKSERV_MIN_PARMS(2);
2600     hi = user->handle_info;
2601     ni = dict_find(nickserv_nick_dict, argv[1], 0);
2602     if (!ni) {
2603         reply("NSMSG_UNKNOWN_NICK", argv[1]);
2604         return 0;
2605     }
2606     if (ni->owner != user->handle_info) {
2607         reply("NSMSG_NOT_YOUR_NICK", ni->nick);
2608         return 0;
2609     }
2610     victim = GetUserH(ni->nick);
2611     if (!victim) {
2612         reply("MSG_NICK_UNKNOWN", ni->nick);
2613         return 0;
2614     }
2615     if (victim == user) {
2616         reply("NSMSG_NICK_USER_YOU");
2617         return 0;
2618     }
2619     nickserv_reclaim(victim, ni, nickserv_conf.reclaim_action);
2620     switch (nickserv_conf.reclaim_action) {
2621     case RECLAIM_NONE: reply("NSMSG_RECLAIMED_NONE"); break;
2622     case RECLAIM_WARN: reply("NSMSG_RECLAIMED_WARN", victim->nick); break;
2623     case RECLAIM_SVSNICK: reply("NSMSG_RECLAIMED_SVSNICK", victim->nick); break;
2624     case RECLAIM_KILL: reply("NSMSG_RECLAIMED_KILL", victim->nick); break;
2625     }
2626     return 1;
2627 }
2628
2629 static NICKSERV_FUNC(cmd_unregnick)
2630 {
2631     const char *nick;
2632     struct handle_info *hi;
2633     struct nick_info *ni;
2634
2635     hi = user->handle_info;
2636     nick = (argc < 2) ? user->nick : (const char*)argv[1];
2637     ni = dict_find(nickserv_nick_dict, nick, NULL);
2638     if (!ni) {
2639         reply("NSMSG_UNKNOWN_NICK", nick);
2640         return 0;
2641     }
2642     if (hi != ni->owner) {
2643         reply("NSMSG_NOT_YOUR_NICK", nick);
2644         return 0;
2645     }
2646     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2647     delete_nick(ni);
2648     return 1;
2649 }
2650
2651 static NICKSERV_FUNC(cmd_ounregnick)
2652 {
2653     struct nick_info *ni;
2654
2655     NICKSERV_MIN_PARMS(2);
2656     if (!(ni = get_nick_info(argv[1]))) {
2657         reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
2658         return 0;
2659     }
2660     if (!oper_outranks(user, ni->owner))
2661         return 0;
2662     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
2663     delete_nick(ni);
2664     return 1;
2665 }
2666
2667 static NICKSERV_FUNC(cmd_unregister)
2668 {
2669     struct handle_info *hi;
2670     char *passwd;
2671
2672     NICKSERV_MIN_PARMS(2);
2673     hi = user->handle_info;
2674     passwd = argv[1];
2675     argv[1] = "****";
2676     if (checkpass(passwd, hi->passwd)) {
2677         nickserv_unregister_handle(hi, user);
2678         return 1;
2679     } else {
2680         log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
2681         reply("NSMSG_PASSWORD_INVALID");
2682         return 0;
2683     }
2684 }
2685
2686 static NICKSERV_FUNC(cmd_ounregister)
2687 {
2688     struct handle_info *hi;
2689     char reason[MAXLEN];
2690
2691     NICKSERV_MIN_PARMS(2);
2692     if (!(hi = get_victim_oper(user, argv[1])))
2693         return 0;
2694     snprintf(reason, sizeof(reason), "%s unregistered account %s.", user->handle_info->handle, hi->handle);
2695     global_message(MESSAGE_RECIPIENT_STAFF, reason);
2696     nickserv_unregister_handle(hi, user);
2697     return 1;
2698 }
2699
2700 static NICKSERV_FUNC(cmd_status)
2701 {
2702     if (nickserv_conf.disable_nicks) {
2703         reply("NSMSG_GLOBAL_STATS_NONICK",
2704                         dict_size(nickserv_handle_dict));
2705     } else {
2706         if (user->handle_info) {
2707             int cnt=0;
2708             struct nick_info *ni;
2709             for (ni=user->handle_info->nicks; ni; ni=ni->next) cnt++;
2710             reply("NSMSG_HANDLE_STATS", cnt);
2711         } else {
2712             reply("NSMSG_HANDLE_NONE");
2713         }
2714         reply("NSMSG_GLOBAL_STATS",
2715               dict_size(nickserv_handle_dict),
2716               dict_size(nickserv_nick_dict));
2717     }
2718     return 1;
2719 }
2720
2721 static NICKSERV_FUNC(cmd_ghost)
2722 {
2723     struct userNode *target;
2724     char reason[MAXLEN];
2725
2726     NICKSERV_MIN_PARMS(2);
2727     if (!(target = GetUserH(argv[1]))) {
2728         reply("MSG_NICK_UNKNOWN", argv[1]);
2729         return 0;
2730     }
2731     if (target == user) {
2732         reply("NSMSG_CANNOT_GHOST_SELF");
2733         return 0;
2734     }
2735     if (!target->handle_info || (target->handle_info != user->handle_info)) {
2736         reply("NSMSG_CANNOT_GHOST_USER", target->nick);
2737         return 0;
2738     }
2739     snprintf(reason, sizeof(reason), "Ghost kill on account %s (requested by %s).", target->handle_info->handle, user->nick);
2740     DelUser(target, nickserv, 1, reason);
2741     reply("NSMSG_GHOST_KILLED", argv[1]);
2742     return 1;
2743 }
2744
2745 static NICKSERV_FUNC(cmd_vacation)
2746 {
2747     HANDLE_SET_FLAG(user->handle_info, FROZEN);
2748     reply("NSMSG_ON_VACATION");
2749     return 1;
2750 }
2751
2752 static NICKSERV_FUNC(cmd_addnote)
2753 {
2754     struct handle_info *hi;
2755     unsigned long duration;
2756     char text[MAXLEN];
2757     unsigned int id;
2758     struct handle_note *prev;
2759     struct handle_note *note;
2760
2761     /* Parse parameters and figure out values for note's fields. */
2762     NICKSERV_MIN_PARMS(4);
2763     hi = get_victim_oper(user, argv[1]);
2764     if (!hi)
2765         return 0;
2766     duration = ParseInterval(argv[2]);
2767     if (duration > 2*365*86400) {
2768         reply("NSMSG_EXCESSIVE_DURATION", argv[2]);
2769         return 0;
2770     }
2771     unsplit_string(argv + 3, argc - 3, text);
2772     WALK_NOTES(hi, prev, note) {}
2773     id = prev ? (prev->id + 1) : 1;
2774
2775     /* Create the new note structure. */
2776     note = calloc(1, sizeof(*note) + strlen(text));
2777     note->next = NULL;
2778     note->expires = duration ? (now + duration) : 0;
2779     note->set = now;
2780     note->id = id;
2781     safestrncpy(note->setter, user->handle_info->handle, sizeof(note->setter));
2782     strcpy(note->note, text);
2783     if (prev)
2784         prev->next = note;
2785     else
2786         hi->notes = note;
2787     reply("NSMSG_NOTE_ADDED", id, hi->handle);
2788     return 1;
2789 }
2790
2791 static NICKSERV_FUNC(cmd_delnote)
2792 {
2793     struct handle_info *hi;
2794     struct handle_note *prev;
2795     struct handle_note *note;
2796     int id;
2797
2798     NICKSERV_MIN_PARMS(3);
2799     hi = get_victim_oper(user, argv[1]);
2800     if (!hi)
2801         return 0;
2802     id = strtoul(argv[2], NULL, 10);
2803     WALK_NOTES(hi, prev, note) {
2804         if (id == note->id) {
2805             if (prev)
2806                 prev->next = note->next;
2807             else
2808                 hi->notes = note->next;
2809             free(note);
2810             reply("NSMSG_NOTE_REMOVED", id, hi->handle);
2811             return 1;
2812         }
2813     }
2814     reply("NSMSG_NO_SUCH_NOTE", hi->handle, id);
2815     return 0;
2816 }
2817
2818 static int
2819 nickserv_saxdb_write(struct saxdb_context *ctx) {
2820     dict_iterator_t it;
2821     struct handle_info *hi;
2822     char flags[33];
2823
2824     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
2825         hi = iter_data(it);
2826 #ifdef WITH_PROTOCOL_BAHAMUT
2827         assert(hi->id);
2828 #endif
2829         saxdb_start_record(ctx, iter_key(it), 0);
2830         if (hi->cookie) {
2831             struct handle_cookie *cookie = hi->cookie;
2832             char *type;
2833
2834             switch (cookie->type) {
2835             case ACTIVATION: type = KEY_ACTIVATION; break;
2836             case PASSWORD_CHANGE: type = KEY_PASSWORD_CHANGE; break;
2837             case EMAIL_CHANGE: type = KEY_EMAIL_CHANGE; break;
2838             case ALLOWAUTH: type = KEY_ALLOWAUTH; break;
2839             default: type = NULL; break;
2840             }
2841             if (type) {
2842                 saxdb_start_record(ctx, KEY_COOKIE, 0);
2843                 saxdb_write_string(ctx, KEY_COOKIE_TYPE, type);
2844                 saxdb_write_int(ctx, KEY_COOKIE_EXPIRES, cookie->expires);
2845                 if (cookie->data)
2846                     saxdb_write_string(ctx, KEY_COOKIE_DATA, cookie->data);
2847                 saxdb_write_string(ctx, KEY_COOKIE, cookie->cookie);
2848                 saxdb_end_record(ctx);
2849             }
2850         }
2851         if (hi->notes) {
2852             struct handle_note *prev, *note;
2853             saxdb_start_record(ctx, KEY_NOTES, 0);
2854             WALK_NOTES(hi, prev, note) {
2855                 snprintf(flags, sizeof(flags), "%d", note->id);
2856                 saxdb_start_record(ctx, flags, 0);
2857                 if (note->expires)
2858                     saxdb_write_int(ctx, KEY_NOTE_EXPIRES, note->expires);
2859                 saxdb_write_int(ctx, KEY_NOTE_SET, note->set);
2860                 saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
2861                 saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
2862                 saxdb_end_record(ctx);
2863             }
2864             saxdb_end_record(ctx);
2865         }
2866         if (hi->email_addr)
2867             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
2868         if (hi->epithet)
2869             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
2870         if (hi->fakehost)
2871             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
2872         if (hi->flags) {
2873             int ii, flen;
2874
2875             for (ii=flen=0; handle_flags[ii]; ++ii)
2876                 if (hi->flags & (1 << ii))
2877                     flags[flen++] = handle_flags[ii];
2878             flags[flen] = 0;
2879             saxdb_write_string(ctx, KEY_FLAGS, flags);
2880         }
2881 #ifdef WITH_PROTOCOL_BAHAMUT
2882         saxdb_write_int(ctx, KEY_ID, hi->id);
2883 #endif
2884         if (hi->infoline)
2885             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
2886         if (hi->last_quit_host[0])
2887             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
2888         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
2889         if (hi->karma != 0)
2890             saxdb_write_sint(ctx, KEY_KARMA, hi->karma);
2891         if (hi->masks->used)
2892             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
2893         if (hi->maxlogins)
2894             saxdb_write_int(ctx, KEY_MAXLOGINS, hi->maxlogins);
2895         if (hi->nicks) {
2896             struct string_list *slist;
2897             struct nick_info *ni;
2898
2899             slist = alloc_string_list(nickserv_conf.nicks_per_handle);
2900             for (ni = hi->nicks; ni; ni = ni->next) string_list_append(slist, ni->nick);
2901             saxdb_write_string_list(ctx, KEY_NICKS, slist);
2902             free(slist->list);
2903             free(slist);
2904         }
2905         if (hi->opserv_level)
2906             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
2907         if (hi->language != lang_C)
2908             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
2909         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
2910         saxdb_write_int(ctx, KEY_REGISTER_ON, hi->registered);
2911         if (hi->screen_width)
2912             saxdb_write_int(ctx, KEY_SCREEN_WIDTH, hi->screen_width);
2913         if (hi->table_width)
2914             saxdb_write_int(ctx, KEY_TABLE_WIDTH, hi->table_width);
2915         flags[0] = hi->userlist_style;
2916         flags[1] = 0;
2917         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
2918         saxdb_end_record(ctx);
2919     }
2920     return 0;
2921 }
2922
2923 static handle_merge_func_t *handle_merge_func_list;
2924 static unsigned int handle_merge_func_size = 0, handle_merge_func_used = 0;
2925
2926 void
2927 reg_handle_merge_func(handle_merge_func_t func)
2928 {
2929     if (handle_merge_func_used == handle_merge_func_size) {
2930         if (handle_merge_func_size) {
2931             handle_merge_func_size <<= 1;
2932             handle_merge_func_list = realloc(handle_merge_func_list, handle_merge_func_size*sizeof(handle_merge_func_t));
2933         } else {
2934             handle_merge_func_size = 8;
2935             handle_merge_func_list = malloc(handle_merge_func_size*sizeof(handle_merge_func_t));
2936         }
2937     }
2938     handle_merge_func_list[handle_merge_func_used++] = func;
2939 }
2940
2941 static NICKSERV_FUNC(cmd_merge)
2942 {
2943     struct handle_info *hi_from, *hi_to;
2944     struct userNode *last_user;
2945     struct userData *cList, *cListNext;
2946     unsigned int ii, jj, n;
2947     char buffer[MAXLEN];
2948
2949     NICKSERV_MIN_PARMS(3);
2950
2951     if (!(hi_from = get_victim_oper(user, argv[1])))
2952         return 0;
2953     if (!(hi_to = get_victim_oper(user, argv[2])))
2954         return 0;
2955     if (hi_to == hi_from) {
2956         reply("NSMSG_CANNOT_MERGE_SELF", hi_to->handle);
2957         return 0;
2958     }
2959
2960     for (n=0; n<handle_merge_func_used; n++)
2961         handle_merge_func_list[n](user, hi_to, hi_from);
2962
2963     /* Append "from" handle's nicks to "to" handle's nick list. */
2964     if (hi_to->nicks) {
2965         struct nick_info *last_ni;
2966         for (last_ni=hi_to->nicks; last_ni->next; last_ni=last_ni->next) ;
2967         last_ni->next = hi_from->nicks;
2968     }
2969     while (hi_from->nicks) {
2970         hi_from->nicks->owner = hi_to;
2971         hi_from->nicks = hi_from->nicks->next;
2972     }
2973
2974     /* Merge the hostmasks. */
2975     for (ii=0; ii<hi_from->masks->used; ii++) {
2976         char *mask = hi_from->masks->list[ii];
2977         for (jj=0; jj<hi_to->masks->used; jj++)
2978             if (match_ircglobs(hi_to->masks->list[jj], mask))
2979                 break;
2980         if (jj==hi_to->masks->used) /* Nothing from the "to" handle covered this mask, so add it. */
2981             string_list_append(hi_to->masks, strdup(mask));
2982     }
2983
2984     /* Merge the lists of authed users. */
2985     if (hi_to->users) {
2986         for (last_user=hi_to->users; last_user->next_authed; last_user=last_user->next_authed) ;
2987         last_user->next_authed = hi_from->users;
2988     } else {
2989         hi_to->users = hi_from->users;
2990     }
2991     /* Repoint the old "from" handle's users. */
2992     for (last_user=hi_from->users; last_user; last_user=last_user->next_authed) {
2993         last_user->handle_info = hi_to;
2994     }
2995     hi_from->users = NULL;
2996
2997     /* Merge channel userlists. */
2998     for (cList=hi_from->channels; cList; cList=cListNext) {
2999         struct userData *cList2;
3000         cListNext = cList->u_next;
3001         for (cList2=hi_to->channels; cList2; cList2=cList2->u_next)
3002             if (cList->channel == cList2->channel)
3003                 break;
3004         if (cList2 && (cList2->access >= cList->access)) {
3005             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);
3006             /* keep cList2 in hi_to; remove cList from hi_from */
3007             del_channel_user(cList, 1);
3008         } else {
3009             if (cList2) {
3010                 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);
3011                 /* remove the lower-ranking cList2 from hi_to */
3012                 del_channel_user(cList2, 1);
3013             } else {
3014                 log_module(NS_LOG, LOG_INFO, "Merge: %s had no access in %s", hi_to->handle, cList->channel->channel->name);
3015             }
3016             /* cList needs to be moved from hi_from to hi_to */
3017             cList->handle = hi_to;
3018             /* Remove from linked list for hi_from */
3019             assert(!cList->u_prev);
3020             hi_from->channels = cList->u_next;
3021             if (cList->u_next)
3022                 cList->u_next->u_prev = cList->u_prev;
3023             /* Add to linked list for hi_to */
3024             cList->u_prev = NULL;
3025             cList->u_next = hi_to->channels;
3026             if (hi_to->channels)
3027                 hi_to->channels->u_prev = cList;
3028             hi_to->channels = cList;
3029         }
3030     }
3031
3032     /* Do they get an OpServ level promotion? */
3033     if (hi_from->opserv_level > hi_to->opserv_level)
3034         hi_to->opserv_level = hi_from->opserv_level;
3035
3036     /* What about last seen time? */
3037     if (hi_from->lastseen > hi_to->lastseen)
3038         hi_to->lastseen = hi_from->lastseen;
3039
3040     /* New karma is the sum of the two original karmas. */
3041     hi_to->karma += hi_from->karma;
3042
3043     /* Does a fakehost carry over?  (This intentionally doesn't set it
3044      * for users previously attached to hi_to.  They'll just have to
3045      * reconnect.)
3046      */
3047     if (hi_from->fakehost && !hi_to->fakehost)
3048         hi_to->fakehost = strdup(hi_from->fakehost);
3049
3050     /* Notify of success. */
3051     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
3052     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
3053     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
3054
3055     /* Unregister the "from" handle. */
3056     nickserv_unregister_handle(hi_from, NULL);
3057
3058     return 1;
3059 }
3060
3061 struct nickserv_discrim {
3062     unsigned long flags_on, flags_off;
3063     time_t min_registered, max_registered;
3064     time_t lastseen;
3065     unsigned int limit;
3066     int min_level, max_level;
3067     int min_karma, max_karma;
3068     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
3069     const char *nickmask;
3070     const char *hostmask;
3071     const char *fakehostmask;
3072     const char *handlemask;
3073     const char *emailmask;
3074 };
3075
3076 typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
3077
3078 struct discrim_apply_info {
3079     struct nickserv_discrim *discrim;
3080     discrim_search_func func;
3081     struct userNode *source;
3082     unsigned int matched;
3083 };
3084
3085 static struct nickserv_discrim *
3086 nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
3087 {
3088     unsigned int i;
3089     struct nickserv_discrim *discrim;
3090
3091     discrim = malloc(sizeof(*discrim));
3092     memset(discrim, 0, sizeof(*discrim));
3093     discrim->min_level = 0;
3094     discrim->max_level = INT_MAX;
3095     discrim->limit = 50;
3096     discrim->min_registered = 0;
3097     discrim->max_registered = INT_MAX;
3098     discrim->lastseen = LONG_MAX;
3099     discrim->min_karma = INT_MIN;
3100     discrim->max_karma = INT_MAX;
3101
3102     for (i=0; i<argc; i++) {
3103         if (i == argc - 1) {
3104             send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3105             goto fail;
3106         }
3107         if (!irccasecmp(argv[i], "limit")) {
3108             discrim->limit = strtoul(argv[++i], NULL, 0);
3109         } else if (!irccasecmp(argv[i], "flags")) {
3110             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
3111         } else if (!irccasecmp(argv[i], "registered")) {
3112             const char *cmp = argv[++i];
3113             if (cmp[0] == '<') {
3114                 if (cmp[1] == '=') {
3115                     discrim->min_registered = now - ParseInterval(cmp+2);
3116                 } else {
3117                     discrim->min_registered = now - ParseInterval(cmp+1) + 1;
3118                 }
3119             } else if (cmp[0] == '=') {
3120                 discrim->min_registered = discrim->max_registered = now - ParseInterval(cmp+1);
3121             } else if (cmp[0] == '>') {
3122                 if (cmp[1] == '=') {
3123                     discrim->max_registered = now - ParseInterval(cmp+2);
3124                 } else {
3125                     discrim->max_registered = now - ParseInterval(cmp+1) - 1;
3126                 }
3127             } else {
3128                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3129             }
3130         } else if (!irccasecmp(argv[i], "seen")) {
3131             discrim->lastseen = now - ParseInterval(argv[++i]);
3132         } else if (!nickserv_conf.disable_nicks && !irccasecmp(argv[i], "nickmask")) {
3133             discrim->nickmask = argv[++i];
3134         } else if (!irccasecmp(argv[i], "hostmask")) {
3135             i++;
3136             if (!irccasecmp(argv[i], "exact")) {
3137                 if (i == argc - 1) {
3138                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3139                     goto fail;
3140                 }
3141                 discrim->hostmask_type = EXACT;
3142             } else if (!irccasecmp(argv[i], "subset")) {
3143                 if (i == argc - 1) {
3144                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3145                     goto fail;
3146                 }
3147                 discrim->hostmask_type = SUBSET;
3148             } else if (!irccasecmp(argv[i], "superset")) {
3149                 if (i == argc - 1) {
3150                     send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3151                     goto fail;
3152                 }
3153                 discrim->hostmask_type = SUPERSET;
3154             } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
3155                if (i == argc - 1) {
3156                    send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
3157                    goto fail;
3158                }
3159                discrim->hostmask_type = LASTQUIT;
3160             } else {
3161                 i--;
3162                 discrim->hostmask_type = SUPERSET;
3163             }
3164             discrim->hostmask = argv[++i];
3165         } else if (!irccasecmp(argv[i], "fakehost")) {
3166             if (!irccasecmp(argv[++i], "*")) {
3167                 discrim->fakehostmask = 0;
3168             } else {
3169                 discrim->fakehostmask = argv[i];
3170             }
3171         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
3172             if (!irccasecmp(argv[++i], "*")) {
3173                 discrim->handlemask = 0;
3174             } else {
3175                 discrim->handlemask = argv[i];
3176             }
3177         } else if (!irccasecmp(argv[i], "email")) {
3178             if (user->handle_info->opserv_level < nickserv_conf.email_search_level) {
3179                 send_message(user, nickserv, "MSG_NO_SEARCH_ACCESS", "email");
3180                 goto fail;
3181             } else if (!irccasecmp(argv[++i], "*")) {
3182                 discrim->emailmask = 0;
3183             } else {
3184                 discrim->emailmask = argv[i];
3185             }
3186         } else if (!irccasecmp(argv[i], "access")) {
3187             const char *cmp = argv[++i];
3188             if (cmp[0] == '<') {
3189                 if (discrim->min_level == 0) discrim->min_level = 1;
3190                 if (cmp[1] == '=') {
3191                     discrim->max_level = strtoul(cmp+2, NULL, 0);
3192                 } else {
3193                     discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
3194                 }
3195             } else if (cmp[0] == '=') {
3196                 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
3197             } else if (cmp[0] == '>') {
3198                 if (cmp[1] == '=') {
3199                     discrim->min_level = strtoul(cmp+2, NULL, 0);
3200                 } else {
3201                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
3202                 }
3203             } else {
3204                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3205             }
3206         } else if (!irccasecmp(argv[i], "karma")) {
3207             const char *cmp = argv[++i];
3208             if (cmp[0] == '<') {
3209                 if (cmp[1] == '=') {
3210                     discrim->max_karma = strtoul(cmp+2, NULL, 0);
3211                 } else {
3212                     discrim->max_karma = strtoul(cmp+1, NULL, 0) - 1;
3213                 }
3214             } else if (cmp[0] == '=') {
3215                 discrim->min_karma = discrim->max_karma = strtoul(cmp+1, NULL, 0);
3216             } else if (cmp[0] == '>') {
3217                 if (cmp[1] == '=') {
3218                     discrim->min_karma = strtoul(cmp+2, NULL, 0);
3219                 } else {
3220                     discrim->min_karma = strtoul(cmp+1, NULL, 0) + 1;
3221                 }
3222             } else {
3223                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
3224             }
3225         } else {
3226             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
3227             goto fail;
3228         }
3229     }
3230     return discrim;
3231   fail:
3232     free(discrim);
3233     return NULL;
3234 }
3235
3236 static int
3237 nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
3238 {
3239     if (((discrim->flags_on & hi->flags) != discrim->flags_on)
3240         || (discrim->flags_off & hi->flags)
3241         || (discrim->min_registered > hi->registered)
3242         || (discrim->max_registered < hi->registered)
3243         || (discrim->lastseen < (hi->users?now:hi->lastseen))
3244         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
3245         || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
3246         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
3247         || (discrim->min_level > hi->opserv_level)
3248         || (discrim->max_level < hi->opserv_level)
3249         || (discrim->min_karma > hi->karma)
3250         || (discrim->max_karma < hi->karma)
3251         ) {
3252         return 0;
3253     }
3254     if (discrim->hostmask) {
3255         unsigned int i;
3256         for (i=0; i<hi->masks->used; i++) {
3257             const char *mask = hi->masks->list[i];
3258             if ((discrim->hostmask_type == SUBSET)
3259                 && (match_ircglobs(discrim->hostmask, mask))) break;
3260             else if ((discrim->hostmask_type == EXACT)
3261                      && !irccasecmp(discrim->hostmask, mask)) break;
3262             else if ((discrim->hostmask_type == SUPERSET)
3263                      && (match_ircglobs(mask, discrim->hostmask))) break;
3264             else if ((discrim->hostmask_type == LASTQUIT)
3265                      && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
3266         }
3267         if (i==hi->masks->used) return 0;
3268     }
3269     if (discrim->nickmask) {
3270         struct nick_info *nick = hi->nicks;
3271         while (nick) {
3272             if (match_ircglob(nick->nick, discrim->nickmask)) break;
3273             nick = nick->next;
3274         }
3275         if (!nick) return 0;
3276     }
3277     return 1;
3278 }
3279
3280 static unsigned int
3281 nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func dsf, struct userNode *source)
3282 {
3283     dict_iterator_t it, next;
3284     unsigned int matched;
3285
3286     for (it = dict_first(nickserv_handle_dict), matched = 0;
3287          it && (matched < discrim->limit);
3288          it = next) {
3289         next = iter_next(it);
3290         if (nickserv_discrim_match(discrim, iter_data(it))) {
3291             dsf(source, iter_data(it));
3292             matched++;
3293         }
3294     }
3295     return matched;
3296 }
3297
3298 static void
3299 search_print_func(struct userNode *source, struct handle_info *match)
3300 {
3301     send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
3302 }
3303
3304 static void
3305 search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
3306 {
3307 }
3308
3309 static void
3310 search_unregister_func (struct userNode *source, struct handle_info *match)
3311 {
3312     if (oper_has_access(source, nickserv, match->opserv_level, 0))
3313         nickserv_unregister_handle(match, source);
3314 }
3315
3316 static int
3317 nickserv_sort_accounts_by_access(const void *a, const void *b)
3318 {
3319     const struct handle_info *hi_a = *(const struct handle_info**)a;
3320     const struct handle_info *hi_b = *(const struct handle_info**)b;
3321     if (hi_a->opserv_level != hi_b->opserv_level)
3322         return hi_b->opserv_level - hi_a->opserv_level;
3323     return irccasecmp(hi_a->handle, hi_b->handle);
3324 }
3325
3326 void
3327 nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd)
3328 {
3329     struct handle_info_list hil;
3330     struct helpfile_table tbl;
3331     unsigned int ii;
3332     dict_iterator_t it;
3333     const char **ary;
3334
3335     memset(&hil, 0, sizeof(hil));
3336     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
3337         struct handle_info *hi = iter_data(it);
3338         if (hi->opserv_level)
3339             handle_info_list_append(&hil, hi);
3340     }
3341     qsort(hil.list, hil.used, sizeof(hil.list[0]), nickserv_sort_accounts_by_access);
3342     tbl.length = hil.used + 1;
3343     tbl.width = 2;
3344     tbl.flags = TABLE_NO_FREE;
3345     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
3346     tbl.contents[0] = ary = malloc(tbl.width * sizeof(ary[0]));
3347     ary[0] = "Account";
3348     ary[1] = "Level";
3349     for (ii = 0; ii < hil.used; ) {
3350         ary = malloc(tbl.width * sizeof(ary[0]));
3351         ary[0] = hil.list[ii]->handle;
3352         ary[1] = strtab(hil.list[ii]->opserv_level);
3353         tbl.contents[++ii] = ary;
3354     }
3355     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3356     reply("MSG_MATCH_COUNT", hil.used);
3357     for (ii = 0; ii < hil.used; ii++)
3358         free(tbl.contents[ii]);
3359     free(tbl.contents);
3360     free(hil.list);
3361 }
3362
3363 static NICKSERV_FUNC(cmd_search)
3364 {
3365     struct nickserv_discrim *discrim;
3366     discrim_search_func action;
3367     struct svccmd *subcmd;
3368     unsigned int matches;
3369     char buf[MAXLEN];
3370
3371     NICKSERV_MIN_PARMS(3);
3372     sprintf(buf, "search %s", argv[1]);
3373     subcmd = dict_find(nickserv_service->commands, buf, NULL);
3374     if (!irccasecmp(argv[1], "print"))
3375         action = search_print_func;
3376     else if (!irccasecmp(argv[1], "count"))
3377         action = search_count_func;
3378     else if (!irccasecmp(argv[1], "unregister"))
3379         action = search_unregister_func;
3380     else {
3381         reply("NSMSG_INVALID_ACTION", argv[1]);
3382         return 0;
3383     }
3384
3385     if (subcmd && !svccmd_can_invoke(user, nickserv, subcmd, NULL, SVCCMD_NOISY))
3386         return 0;
3387
3388     discrim = nickserv_discrim_create(user, argc-2, argv+2);
3389     if (!discrim)
3390         return 0;
3391
3392     if (action == search_print_func)
3393         reply("NSMSG_ACCOUNT_SEARCH_RESULTS");
3394     else if (action == search_count_func)
3395         discrim->limit = INT_MAX;
3396
3397     matches = nickserv_discrim_search(discrim, action, user);
3398
3399     if (matches)
3400         reply("MSG_MATCH_COUNT", matches);
3401     else
3402         reply("MSG_NO_MATCHES");
3403
3404     free(discrim);
3405     return 0;
3406 }
3407
3408 static MODCMD_FUNC(cmd_checkpass)
3409 {
3410     struct handle_info *hi;
3411
3412     NICKSERV_MIN_PARMS(3);
3413     if (!(hi = get_handle_info(argv[1]))) {
3414         reply("MSG_HANDLE_UNKNOWN", argv[1]);
3415         return 0;
3416     }
3417     if (checkpass(argv[2], hi->passwd))
3418         reply("CHECKPASS_YES");
3419     else
3420         reply("CHECKPASS_NO");
3421     argv[2] = "****";
3422     return 1;
3423 }
3424
3425 static void
3426 nickserv_db_read_handle(const char *handle, dict_t obj)
3427 {
3428     const char *str;
3429     struct string_list *masks, *slist;
3430     struct handle_info *hi;
3431     struct userNode *authed_users;
3432     struct userData *channels;
3433     unsigned long int id;
3434     unsigned int ii;
3435     dict_t subdb;
3436
3437     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
3438     id = str ? strtoul(str, NULL, 0) : 0;
3439     str = database_get_data(obj, KEY_PASSWD, RECDB_QSTRING);
3440     if (!str) {
3441         log_module(NS_LOG, LOG_WARNING, "did not find a password for %s -- skipping user.", handle);
3442         return;
3443     }
3444     if ((hi = get_handle_info(handle))) {
3445         authed_users = hi->users;
3446         channels = hi->channels;
3447         hi->users = NULL;
3448         hi->channels = NULL;
3449         dict_remove(nickserv_handle_dict, hi->handle);
3450     } else {
3451         authed_users = NULL;
3452         channels = NULL;
3453     }
3454     hi = register_handle(handle, str, id);
3455     if (authed_users) {
3456         hi->users = authed_users;
3457         while (authed_users) {
3458             authed_users->handle_info = hi;
3459             authed_users = authed_users->next_authed;
3460         }
3461     }
3462     hi->channels = channels;
3463     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
3464     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
3465     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
3466     hi->maxlogins = str ? strtoul(str, NULL, 0) : 0;
3467     str = database_get_data(obj, KEY_LANGUAGE, RECDB_QSTRING);
3468     hi->language = language_find(str ? str : "C");
3469     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
3470     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
3471     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
3472     if (str)
3473         hi->infoline = strdup(str);
3474     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
3475     hi->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
3476     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
3477     hi->lastseen = str ? (time_t)strtoul(str, NULL, 0) : hi->registered;
3478     str = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
3479     hi->karma = str ? strtoul(str, NULL, 0) : 0;
3480     /* We want to read the nicks even if disable_nicks is set.  This is so
3481      * that we don't lose the nick data entirely. */
3482     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
3483     if (slist) {
3484         for (ii=0; ii<slist->used; ii++)
3485             register_nick(slist->list[ii], hi);
3486     }
3487     str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING);
3488     if (str) {
3489         for (ii=0; str[ii]; ii++)
3490             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
3491     }
3492     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
3493     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
3494     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
3495     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
3496     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
3497     hi->table_width = str ? strtoul(str, NULL, 0) : 0;
3498     str = database_get_data(obj, KEY_LAST_QUIT_HOST, RECDB_QSTRING);
3499     if (!str)
3500         str = database_get_data(obj, KEY_LAST_AUTHED_HOST, RECDB_QSTRING);
3501     if (str)
3502         safestrncpy(hi->last_quit_host, str, sizeof(hi->last_quit_host));
3503     str = database_get_data(obj, KEY_EMAIL_ADDR, RECDB_QSTRING);
3504     if (str)
3505         nickserv_set_email_addr(hi, str);
3506     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
3507     if (str)
3508         hi->epithet = strdup(str);
3509     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
3510     if (str)
3511         hi->fakehost = strdup(str);
3512     /* Read the "cookie" sub-database (if it exists). */
3513     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
3514     if (subdb) {
3515         const char *data, *type, *expires, *cookie_str;
3516         struct handle_cookie *cookie;
3517
3518         cookie = calloc(1, sizeof(*cookie));
3519         type = database_get_data(subdb, KEY_COOKIE_TYPE, RECDB_QSTRING);
3520         data = database_get_data(subdb, KEY_COOKIE_DATA, RECDB_QSTRING);
3521         expires = database_get_data(subdb, KEY_COOKIE_EXPIRES, RECDB_QSTRING);
3522         cookie_str = database_get_data(subdb, KEY_COOKIE, RECDB_QSTRING);
3523         if (!type || !expires || !cookie_str) {
3524             log_module(NS_LOG, LOG_ERROR, "Missing field(s) from cookie for account %s; dropping cookie.", hi->handle);
3525             goto cookie_out;
3526         }
3527         if (!irccasecmp(type, KEY_ACTIVATION))
3528             cookie->type = ACTIVATION;
3529         else if (!irccasecmp(type, KEY_PASSWORD_CHANGE))
3530             cookie->type = PASSWORD_CHANGE;
3531         else if (!irccasecmp(type, KEY_EMAIL_CHANGE))
3532             cookie->type = EMAIL_CHANGE;
3533         else if (!irccasecmp(type, KEY_ALLOWAUTH))
3534             cookie->type = ALLOWAUTH;
3535         else {
3536             log_module(NS_LOG, LOG_ERROR, "Invalid cookie type %s for account %s; dropping cookie.", type, handle);
3537             goto cookie_out;
3538         }
3539         cookie->expires = strtoul(expires, NULL, 0);
3540         if (cookie->expires < now)
3541             goto cookie_out;
3542         if (data)
3543             cookie->data = strdup(data);
3544         safestrncpy(cookie->cookie, cookie_str, sizeof(cookie->cookie));
3545         cookie->hi = hi;
3546       cookie_out:
3547         if (cookie->hi)
3548             nickserv_bake_cookie(cookie);
3549         else
3550             nickserv_free_cookie(cookie);
3551     }
3552     /* Read the "notes" sub-database (if it exists). */
3553     subdb = database_get_data(obj, KEY_NOTES, RECDB_OBJECT);
3554     if (subdb) {
3555         dict_iterator_t it;
3556         struct handle_note *last_note;
3557         struct handle_note *note;
3558
3559         last_note = NULL;
3560         for (it = dict_first(subdb); it; it = iter_next(it)) {
3561             const char *expires;
3562             const char *setter;
3563             const char *text;
3564             const char *set;
3565             const char *id;
3566             dict_t notedb;
3567
3568             id = iter_key(it);
3569             notedb = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
3570             if (!notedb) {
3571                 log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", id, hi->handle);
3572                 continue;
3573             }
3574             expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
3575             setter = database_get_data(notedb, KEY_NOTE_SETTER, RECDB_QSTRING);
3576             text = database_get_data(notedb, KEY_NOTE_NOTE, RECDB_QSTRING);
3577             set = database_get_data(notedb, KEY_NOTE_SET, RECDB_QSTRING);
3578             if (!setter || !text || !set) {
3579                 log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", id, hi->handle);
3580                 continue;
3581             }
3582             note = calloc(1, sizeof(*note) + strlen(text));
3583             note->next = NULL;
3584             note->expires = expires ? strtoul(expires, NULL, 10) : 0;
3585             note->set = strtoul(set, NULL, 10);
3586             note->id = strtoul(id, NULL, 10);
3587             safestrncpy(note->setter, setter, sizeof(note->setter));
3588             strcpy(note->note, text);
3589             if (last_note)
3590                 last_note->next = note;
3591             else
3592                 hi->notes = note;
3593             last_note = note;
3594         }
3595     }
3596 }
3597
3598 static int
3599 nickserv_saxdb_read(dict_t db) {
3600     dict_iterator_t it;
3601     struct record_data *rd;
3602
3603     for (it=dict_first(db); it; it=iter_next(it)) {
3604         rd = iter_data(it);
3605         nickserv_db_read_handle(iter_key(it), rd->d.object);
3606     }
3607     return 0;
3608 }
3609
3610 static NICKSERV_FUNC(cmd_mergedb)
3611 {
3612     struct timeval start, stop;
3613     dict_t db;
3614
3615     NICKSERV_MIN_PARMS(2);
3616     gettimeofday(&start, NULL);
3617     if (!(db = parse_database(argv[1]))) {
3618         reply("NSMSG_DB_UNREADABLE", argv[1]);
3619         return 0;
3620     }
3621     nickserv_saxdb_read(db);
3622     free_database(db);
3623     gettimeofday(&stop, NULL);
3624     stop.tv_sec -= start.tv_sec;
3625     stop.tv_usec -= start.tv_usec;
3626     if (stop.tv_usec < 0) {
3627         stop.tv_sec -= 1;
3628         stop.tv_usec += 1000000;
3629     }
3630     reply("NSMSG_DB_MERGED", argv[1], stop.tv_sec, stop.tv_usec/1000);
3631     return 1;
3632 }
3633
3634 static void
3635 expire_handles(UNUSED_ARG(void *data))
3636 {
3637     dict_iterator_t it, next;
3638     time_t expiry;
3639     struct handle_info *hi;
3640
3641     for (it=dict_first(nickserv_handle_dict); it; it=next) {
3642         next = iter_next(it);
3643         hi = iter_data(it);
3644         if ((hi->opserv_level > 0)
3645             || hi->users
3646             || HANDLE_FLAGGED(hi, FROZEN)
3647             || HANDLE_FLAGGED(hi, NODELETE)) {
3648             continue;
3649         }
3650         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
3651         if ((now - hi->lastseen) > expiry) {
3652             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
3653             nickserv_unregister_handle(hi, NULL);
3654         }
3655     }
3656
3657     if (nickserv_conf.handle_expire_frequency)
3658         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
3659 }
3660
3661 static void
3662 nickserv_load_dict(const char *fname)
3663 {
3664     FILE *file;
3665     char line[128];
3666     if (!(file = fopen(fname, "r"))) {
3667         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
3668         return;
3669     }
3670     while (!feof(file)) {
3671         fgets(line, sizeof(line), file);
3672         if (!line[0])
3673             continue;
3674         if (line[strlen(line)-1] == '\n')
3675             line[strlen(line)-1] = 0;
3676         dict_insert(nickserv_conf.weak_password_dict, strdup(line), NULL);
3677     }
3678     fclose(file);
3679     log_module(NS_LOG, LOG_INFO, "Loaded %d words into weak password dictionary.", dict_size(nickserv_conf.weak_password_dict));
3680 }
3681
3682 static enum reclaim_action
3683 reclaim_action_from_string(const char *str) {
3684     if (!str)
3685         return RECLAIM_NONE;
3686     else if (!irccasecmp(str, "warn"))
3687         return RECLAIM_WARN;
3688     else if (!irccasecmp(str, "svsnick"))
3689         return RECLAIM_SVSNICK;
3690     else if (!irccasecmp(str, "kill"))
3691         return RECLAIM_KILL;
3692     else
3693         return RECLAIM_NONE;
3694 }
3695
3696 static void
3697 nickserv_conf_read(void)
3698 {
3699     dict_t conf_node, child;
3700     const char *str;
3701     dict_iterator_t it;
3702
3703     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
3704         log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
3705         return;
3706     }
3707     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
3708     if (!str)
3709         str = database_get_data(conf_node, KEY_VALID_ACCOUNT_REGEX, RECDB_QSTRING);
3710     if (nickserv_conf.valid_handle_regex_set)
3711         regfree(&nickserv_conf.valid_handle_regex);
3712     if (str) {
3713         int err = regcomp(&nickserv_conf.valid_handle_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3714         nickserv_conf.valid_handle_regex_set = !err;
3715         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_account_regex (error %d)", err);
3716     } else {
3717         nickserv_conf.valid_handle_regex_set = 0;
3718     }
3719     str = database_get_data(conf_node, KEY_VALID_NICK_REGEX, RECDB_QSTRING);
3720     if (nickserv_conf.valid_nick_regex_set)
3721         regfree(&nickserv_conf.valid_nick_regex);
3722     if (str) {
3723         int err = regcomp(&nickserv_conf.valid_nick_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
3724         nickserv_conf.valid_nick_regex_set = !err;
3725         if (err) log_module(NS_LOG, LOG_ERROR, "Bad valid_nick_regex (error %d)", err);
3726     } else {
3727         nickserv_conf.valid_nick_regex_set = 0;
3728     }
3729     str = database_get_data(conf_node, KEY_NICKS_PER_HANDLE, RECDB_QSTRING);
3730     if (!str)
3731         str = database_get_data(conf_node, KEY_NICKS_PER_ACCOUNT, RECDB_QSTRING);
3732     nickserv_conf.nicks_per_handle = str ? strtoul(str, NULL, 0) : 4;
3733     str = database_get_data(conf_node, KEY_DISABLE_NICKS, RECDB_QSTRING);
3734     nickserv_conf.disable_nicks = str ? strtoul(str, NULL, 0) : 0;
3735     str = database_get_data(conf_node, KEY_DEFAULT_HOSTMASK, RECDB_QSTRING);
3736     nickserv_conf.default_hostmask = str ? !disabled_string(str) : 0;
3737     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LENGTH, RECDB_QSTRING);
3738     nickserv_conf.password_min_length = str ? strtoul(str, NULL, 0) : 0;
3739     str = database_get_data(conf_node, KEY_PASSWORD_MIN_DIGITS, RECDB_QSTRING);
3740     nickserv_conf.password_min_digits = str ? strtoul(str, NULL, 0) : 0;
3741     str = database_get_data(conf_node, KEY_PASSWORD_MIN_UPPER, RECDB_QSTRING);
3742     nickserv_conf.password_min_upper = str ? strtoul(str, NULL, 0) : 0;
3743     str = database_get_data(conf_node, KEY_PASSWORD_MIN_LOWER, RECDB_QSTRING);
3744     nickserv_conf.password_min_lower = str ? strtoul(str, NULL, 0) : 0;
3745     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
3746     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3747     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
3748     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
3749     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
3750     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
3751     str = database_get_data(conf_node, KEY_SET_TITLE_LEVEL, RECDB_QSTRING);
3752     nickserv_conf.set_title_level = str ? strtoul(str, NULL, 0) : 900;
3753     str = database_get_data(conf_node, KEY_SET_FAKEHOST_LEVEL, RECDB_QSTRING);
3754     nickserv_conf.set_fakehost_level = str ? strtoul(str, NULL, 0) : 1000;
3755     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
3756     if (!str)
3757         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
3758     nickserv_conf.handle_expire_frequency = str ? ParseInterval(str) : 86400;
3759     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3760     if (!str)
3761         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3762     nickserv_conf.handle_expire_delay = str ? ParseInterval(str) : 86400*30;
3763     str = database_get_data(conf_node, KEY_NOCHAN_HANDLE_EXPIRE_DELAY, RECDB_QSTRING);
3764     if (!str)
3765         str = database_get_data(conf_node, KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY, RECDB_QSTRING);
3766     nickserv_conf.nochan_handle_expire_delay = str ? ParseInterval(str) : 86400*15;
3767     str = database_get_data(conf_node, "warn_clone_auth", RECDB_QSTRING);
3768     nickserv_conf.warn_clone_auth = str ? !disabled_string(str) : 1;
3769     str = database_get_data(conf_node, "default_maxlogins", RECDB_QSTRING);
3770     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
3771     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
3772     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
3773     if (!nickserv_conf.disable_nicks) {
3774         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
3775         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3776         str = database_get_data(conf_node, "warn_nick_owned", RECDB_QSTRING);
3777         nickserv_conf.warn_nick_owned = str ? enabled_string(str) : 0;
3778         str = database_get_data(conf_node, "auto_reclaim_action", RECDB_QSTRING);
3779         nickserv_conf.auto_reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
3780         str = database_get_data(conf_node, "auto_reclaim_delay", RECDB_QSTRING);
3781         nickserv_conf.auto_reclaim_delay = str ? ParseInterval(str) : 0;
3782     }
3783     child = database_get_data(conf_node, KEY_FLAG_LEVELS, RECDB_OBJECT);
3784     for (it=dict_first(child); it; it=iter_next(it)) {
3785         const char *key = iter_key(it), *value;
3786         unsigned char flag;
3787         int pos;
3788
3789         if (!strncasecmp(key, "uc_", 3))
3790             flag = toupper(key[3]);
3791         else if (!strncasecmp(key, "lc_", 3))
3792             flag = tolower(key[3]);
3793         else
3794             flag = key[0];
3795
3796         if ((pos = handle_inverse_flags[flag])) {
3797             value = GET_RECORD_QSTRING((struct record_data*)iter_data(it));
3798             flag_access_levels[pos - 1] = strtoul(value, NULL, 0);
3799         }
3800     }
3801     if (nickserv_conf.weak_password_dict)
3802         dict_delete(nickserv_conf.weak_password_dict);
3803     nickserv_conf.weak_password_dict = dict_new();
3804     dict_set_free_keys(nickserv_conf.weak_password_dict, free);
3805     dict_insert(nickserv_conf.weak_password_dict, strdup("password"), NULL);
3806     dict_insert(nickserv_conf.weak_password_dict, strdup("<password>"), NULL);
3807     str = database_get_data(conf_node, KEY_DICT_FILE, RECDB_QSTRING);
3808     if (str)
3809         nickserv_load_dict(str);
3810     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
3811     if (nickserv && str)
3812         NickChange(nickserv, str, 0);
3813     str = database_get_data(conf_node, KEY_AUTOGAG_ENABLED, RECDB_QSTRING);
3814     nickserv_conf.autogag_enabled = str ? strtoul(str, NULL, 0) : 1;
3815     str = database_get_data(conf_node, KEY_AUTOGAG_DURATION, RECDB_QSTRING);
3816     nickserv_conf.autogag_duration = str ? ParseInterval(str) : 1800;
3817     str = database_get_data(conf_node, KEY_EMAIL_VISIBLE_LEVEL, RECDB_QSTRING);
3818     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
3819     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
3820     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
3821     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
3822     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
3823     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
3824     nickserv_conf.email_required = (nickserv_conf.email_enabled && str) ? enabled_string(str) : 0;
3825     str = database_get_data(conf_node, KEY_ACCOUNTS_PER_EMAIL, RECDB_QSTRING);
3826     nickserv_conf.handles_per_email = str ? strtoul(str, NULL, 0) : 1;
3827     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
3828     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
3829     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
3830     nickserv_conf.titlehost_suffix = str ? str : "example.net";
3831     str = conf_get_data("server/network", RECDB_QSTRING);
3832     nickserv_conf.network_name = str ? str : "some IRC network";
3833     if (!nickserv_conf.auth_policer_params) {
3834         nickserv_conf.auth_policer_params = policer_params_new();
3835         policer_params_set(nickserv_conf.auth_policer_params, "size", "5");
3836         policer_params_set(nickserv_conf.auth_policer_params, "drain-rate", "0.05");
3837     }
3838     child = database_get_data(conf_node, KEY_AUTH_POLICER, RECDB_OBJECT);
3839     for (it=dict_first(child); it; it=iter_next(it))
3840         set_policer_param(iter_key(it), iter_data(it), nickserv_conf.auth_policer_params);
3841 }
3842
3843 static void
3844 nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum reclaim_action action) {
3845     const char *msg;
3846     char newnick[NICKLEN+1];
3847
3848     assert(user);
3849     assert(ni);
3850     switch (action) {
3851     case RECLAIM_NONE:
3852         /* do nothing */
3853         break;
3854     case RECLAIM_WARN:
3855         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3856         break;
3857     case RECLAIM_SVSNICK:
3858         do {
3859             snprintf(newnick, sizeof(newnick), "Guest%d", rand()%10000);
3860         } while (GetUserH(newnick));
3861         irc_svsnick(nickserv, user, newnick);
3862         break;
3863     case RECLAIM_KILL:
3864         msg = user_find_message(user, "NSMSG_RECLAIM_KILL");
3865         DelUser(user, nickserv, 1, msg);
3866         break;
3867     }
3868 }
3869
3870 static void
3871 nickserv_reclaim_p(void *data) {
3872     struct userNode *user = data;
3873     struct nick_info *ni = get_nick_info(user->nick);
3874     if (ni)
3875         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3876 }
3877
3878 static int
3879 check_user_nick(struct userNode *user) {
3880     struct nick_info *ni;
3881     user->modes &= ~FLAGS_REGNICK;
3882     if (!(ni = get_nick_info(user->nick)))
3883         return 0;
3884     if (user->handle_info == ni->owner) {
3885         user->modes |= FLAGS_REGNICK;
3886         irc_regnick(user);
3887         return 0;
3888     }
3889     if (nickserv_conf.warn_nick_owned)
3890         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
3891     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
3892         return 0;
3893     if (nickserv_conf.auto_reclaim_delay)
3894         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
3895     else
3896         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
3897     return 0;
3898 }
3899
3900 int
3901 handle_new_user(struct userNode *user)
3902 {
3903     return check_user_nick(user);
3904 }
3905
3906 void
3907 handle_account(struct userNode *user, const char *stamp)
3908 {
3909     struct handle_info *hi;
3910
3911 #ifdef WITH_PROTOCOL_P10
3912     hi = dict_find(nickserv_handle_dict, stamp, NULL);
3913 #else
3914     hi = dict_find(nickserv_id_dict, stamp, NULL);
3915 #endif
3916
3917     if (hi) {
3918         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
3919             return;
3920         }
3921         set_user_handle_info(user, hi, 0);
3922     } else {
3923         log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s.", user->nick, stamp);
3924     }
3925 }
3926
3927 void
3928 handle_nick_change(struct userNode *user, const char *old_nick)
3929 {
3930     struct handle_info *hi;
3931
3932     if ((hi = dict_find(nickserv_allow_auth_dict, old_nick, 0))) {
3933         dict_remove(nickserv_allow_auth_dict, old_nick);
3934         dict_insert(nickserv_allow_auth_dict, user->nick, hi);
3935     }
3936     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
3937     check_user_nick(user);
3938 }
3939
3940 void
3941 nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
3942 {
3943     dict_remove(nickserv_allow_auth_dict, user->nick);
3944     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
3945     set_user_handle_info(user, NULL, 0);
3946 }
3947
3948 static struct modcmd *
3949 nickserv_define_func(const char *name, modcmd_func_t func, int min_level, int must_auth, int must_be_qualified)
3950 {
3951     if (min_level > 0) {
3952         char buf[16];
3953         sprintf(buf, "%u", min_level);
3954         if (must_be_qualified) {
3955             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, "flags", "+qualified,+loghostmask", NULL);
3956         } else {
3957             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "level", buf, NULL);
3958         }
3959     } else if (min_level == 0) {
3960         if (must_be_qualified) {
3961             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
3962         } else {
3963             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+helping", NULL);
3964         }
3965     } else {
3966         if (must_be_qualified) {
3967             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), "flags", "+qualified,+loghostmask", NULL);
3968         } else {
3969             return modcmd_register(nickserv_module, name, func, 1, (must_auth ? MODCMD_REQUIRE_AUTHED : 0), NULL);
3970         }
3971     }
3972 }
3973
3974 static void
3975 nickserv_db_cleanup(void)
3976 {
3977     unreg_del_user_func(nickserv_remove_user);
3978     userList_clean(&curr_helpers);
3979     policer_params_delete(nickserv_conf.auth_policer_params);
3980     dict_delete(nickserv_handle_dict);
3981     dict_delete(nickserv_nick_dict);
3982     dict_delete(nickserv_opt_dict);
3983     dict_delete(nickserv_allow_auth_dict);
3984     dict_delete(nickserv_email_dict);
3985     dict_delete(nickserv_id_dict);
3986     dict_delete(nickserv_conf.weak_password_dict);
3987     free(auth_func_list);
3988     free(unreg_func_list);
3989     free(rf_list);
3990     free(allowauth_func_list);
3991     free(handle_merge_func_list);
3992     free(failpw_func_list);
3993     if (nickserv_conf.valid_handle_regex_set)
3994         regfree(&nickserv_conf.valid_handle_regex);
3995     if (nickserv_conf.valid_nick_regex_set)
3996         regfree(&nickserv_conf.valid_nick_regex);
3997 }
3998
3999 void
4000 init_nickserv(const char *nick)
4001 {
4002     unsigned int i;
4003     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
4004     reg_new_user_func(handle_new_user);
4005     reg_nick_change_func(handle_nick_change);
4006     reg_del_user_func(nickserv_remove_user);
4007     reg_account_func(handle_account);
4008
4009     /* set up handle_inverse_flags */
4010     memset(handle_inverse_flags, 0, sizeof(handle_inverse_flags));
4011     for (i=0; handle_flags[i]; i++) {
4012         handle_inverse_flags[(unsigned char)handle_flags[i]] = i + 1;
4013         flag_access_levels[i] = 0;
4014     }
4015
4016     conf_register_reload(nickserv_conf_read);
4017     nickserv_opt_dict = dict_new();
4018     nickserv_email_dict = dict_new();
4019     dict_set_free_keys(nickserv_email_dict, free);
4020     dict_set_free_data(nickserv_email_dict, nickserv_free_email_addr);
4021
4022     nickserv_module = module_register("NickServ", NS_LOG, "nickserv.help", NULL);
4023     modcmd_register(nickserv_module, "AUTH", cmd_auth, 2, MODCMD_KEEP_BOUND, "flags", "+qualified,+loghostmask", NULL);
4024     nickserv_define_func("ALLOWAUTH", cmd_allowauth, 0, 1, 0);
4025     nickserv_define_func("REGISTER", cmd_register, -1, 0, 1);
4026     nickserv_define_func("OREGISTER", cmd_oregister, 0, 1, 0);
4027     nickserv_define_func("UNREGISTER", cmd_unregister, -1, 1, 1);
4028     nickserv_define_func("OUNREGISTER", cmd_ounregister, 0, 1, 0);
4029     nickserv_define_func("ADDMASK", cmd_addmask, -1, 1, 0);
4030     nickserv_define_func("OADDMASK", cmd_oaddmask, 0, 1, 0);
4031     nickserv_define_func("DELMASK", cmd_delmask, -1, 1, 0);
4032     nickserv_define_func("ODELMASK", cmd_odelmask, 0, 1, 0);
4033     nickserv_define_func("PASS", cmd_pass, -1, 1, 1);
4034     nickserv_define_func("SET", cmd_set, -1, 1, 0);
4035     nickserv_define_func("OSET", cmd_oset, 0, 1, 0);
4036     nickserv_define_func("ACCOUNTINFO", cmd_handleinfo, -1, 0, 0);
4037     nickserv_define_func("USERINFO", cmd_userinfo, -1, 1, 0);
4038     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
4039     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
4040     nickserv_define_func("MERGE", cmd_merge, 750, 1, 0);
4041     nickserv_define_func("ADDNOTE", cmd_addnote, 0, 1, 0);
4042     nickserv_define_func("DELNOTE", cmd_delnote, 0, 1, 0);
4043     nickserv_define_func("NOTES", cmd_notes, 0, 1, 0);
4044     if (!nickserv_conf.disable_nicks) {
4045         /* nick management commands */
4046         nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
4047         nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
4048         nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
4049         nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
4050         nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
4051         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
4052     }
4053     if (nickserv_conf.email_enabled) {
4054         nickserv_define_func("AUTHCOOKIE", cmd_authcookie, -1, 0, 0);
4055         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
4056         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
4057         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
4058         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
4059     }
4060     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
4061     /* miscellaneous commands */
4062     nickserv_define_func("STATUS", cmd_status, -1, 0, 0);
4063     nickserv_define_func("SEARCH", cmd_search, 100, 1, 0);
4064     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
4065     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
4066     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
4067     /* other options */
4068     dict_insert(nickserv_opt_dict, "INFO", opt_info);
4069     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
4070     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
4071     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
4072     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
4073     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
4074     dict_insert(nickserv_opt_dict, "PASS", opt_password);
4075     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
4076     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
4077     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
4078     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
4079     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
4080     if (nickserv_conf.titlehost_suffix) {
4081         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
4082         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
4083     }
4084     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
4085     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
4086     dict_insert(nickserv_opt_dict, "KARMA", opt_karma);
4087     nickserv_define_func("OSET KARMA", NULL, 0, 1, 0);
4088
4089     nickserv_handle_dict = dict_new();
4090     dict_set_free_keys(nickserv_handle_dict, free);
4091     dict_set_free_data(nickserv_handle_dict, free_handle_info);
4092
4093     nickserv_id_dict = dict_new();
4094     dict_set_free_keys(nickserv_id_dict, free);
4095
4096     nickserv_nick_dict = dict_new();
4097     dict_set_free_data(nickserv_nick_dict, free);
4098
4099     nickserv_allow_auth_dict = dict_new();
4100
4101     userList_init(&curr_helpers);
4102
4103     if (nick) {
4104         const char *modes = conf_get_data("services/nickserv/modes", RECDB_QSTRING);
4105         nickserv = AddLocalUser(nick, nick, NULL, "Nick Services", modes);
4106         nickserv_service = service_register(nickserv);
4107     }
4108     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);
4109     reg_exit_func(nickserv_db_cleanup);
4110     if(nickserv_conf.handle_expire_frequency)
4111         timeq_add(now + nickserv_conf.handle_expire_frequency, expire_handles, NULL);
4112     message_register_table(msgtab);
4113 }