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