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