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