Remove extraneous semicolons from end of macro uses.
[srvx.git] / src / nickserv.c
index 95085006a4874ea9cf590a8b347f93dd1a0f50a6..9ab83c80d7f67702153cd9100580ffc55a9d129e 100644 (file)
@@ -24,7 +24,7 @@
 #include "modcmd.h"
 #include "opserv.h" /* for gag_create(), opserv_bad_channel() */
 #include "saxdb.h"
-#include "sendmail.h"
+#include "mail.h"
 #include "timeq.h"
 
 #ifdef HAVE_REGEX_H
@@ -53,9 +53,9 @@
 #define KEY_SET_FAKEHOST_LEVEL "set_fakehost_level"
 #define KEY_TITLEHOST_SUFFIX "titlehost_suffix"
 #define KEY_FLAG_LEVELS "flag_levels"
-#define KEY_HANDLE_EXPIRE_FREQ "handle_expire_freq"
+#define KEY_HANDLE_EXPIRE_FREQ "handle_expire_freq"
 #define KEY_ACCOUNT_EXPIRE_FREQ "account_expire_freq"
-#define KEY_HANDLE_EXPIRE_DELAY        "handle_expire_delay"
+#define KEY_HANDLE_EXPIRE_DELAY "handle_expire_delay"
 #define KEY_ACCOUNT_EXPIRE_DELAY "account_expire_delay"
 #define KEY_NOCHAN_HANDLE_EXPIRE_DELAY "nochan_handle_expire_delay"
 #define KEY_NOCHAN_ACCOUNT_EXPIRE_DELAY "nochan_account_expire_delay"
@@ -71,6 +71,8 @@
 #define KEY_COOKIE_TIMEOUT "cookie_timeout"
 #define KEY_ACCOUNTS_PER_EMAIL "accounts_per_email"
 #define KEY_EMAIL_SEARCH_LEVEL "email_search_level"
+#define KEY_OUNREGISTER_INACTIVE "ounregister_inactive"
+#define KEY_OUNREGISTER_FLAGS "ounregister_flags"
 
 #define KEY_ID "id"
 #define KEY_PASSWD "passwd"
 #define KEY_ALLOWAUTH "allowauth"
 #define KEY_EPITHET "epithet"
 #define KEY_TABLE_WIDTH "table_width"
-#define KEY_ANNOUNCEMENTS "announcements"
 #define KEY_MAXLOGINS "maxlogins"
 #define KEY_FAKEHOST "fakehost"
+#define KEY_NOTES "notes"
+#define KEY_NOTE_EXPIRES "expires"
+#define KEY_NOTE_SET "set"
+#define KEY_NOTE_SETTER "setter"
+#define KEY_NOTE_NOTE "note"
+#define KEY_KARMA "karma"
 
-#define NICKSERV_VALID_CHARS   "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
+#define NICKSERV_VALID_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
 
 #define NICKSERV_FUNC(NAME) MODCMD_FUNC(NAME)
 #define OPTION_FUNC(NAME) int NAME(struct userNode *user, struct handle_info *hi, UNUSED_ARG(unsigned int override), unsigned int argc, char *argv[])
 typedef OPTION_FUNC(option_func_t);
 
-DEFINE_LIST(handle_info_list, struct handle_info*);
+DEFINE_LIST(handle_info_list, struct handle_info*)
 
 #define NICKSERV_MIN_PARMS(N) do { \
   if (argc < N) { \
@@ -151,6 +158,7 @@ static const struct message_entry msgtab[] = {
     { "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." },
     { "NSMSG_EMAIL_UNACTIVATED", "That email address already has an unused cookie outstanding.  Please use the cookie or wait for it to expire." },
     { "NSMSG_NO_COOKIE", "Your account does not have any cookie issued right now." },
+    { "NSMSG_NO_COOKIE_FOREIGN", "The account $b%s$b does not have any cookie issued right now." },
     { "NSMSG_CANNOT_COOKIE", "You cannot use that kind of cookie when you are logged in." },
     { "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." },
     { "NSMSG_HANDLE_ACTIVATED", "Your account is now activated (with the password you entered when you registered).  You are now authenticated to your account." },
@@ -162,6 +170,7 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_BAD_COOKIE_TYPE", "Your account had bad cookie type %d; sorry.  I am confused.  Please report this bug." },
     { "NSMSG_MUST_TIME_OUT", "You must wait for cookies of that type to time out." },
     { "NSMSG_ATE_COOKIE", "I ate the cookie for your account.  You may now have another." },
+    { "NSMSG_ATE_COOKIE_FOREIGN", "I ate the cookie for account $b%s$b.  It may now have another." },
     { "NSMSG_USE_RENAME", "You are already authenticated to account $b%s$b -- contact the support staff to rename your account." },
     { "NSMSG_ALREADY_REGISTERING", "You have already used $bREGISTER$b once this session; you may not use it again." },
     { "NSMSG_REGISTER_BAD_NICKMASK", "Could not recognize $b%s$b as either a current nick or a hostmask." },
@@ -188,6 +197,7 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_HANDLEINFO_REGGED", "  Registered on: %s" },
     { "NSMSG_HANDLEINFO_LASTSEEN", "  Last seen: %s" },
     { "NSMSG_HANDLEINFO_LASTSEEN_NOW", "  Last seen: Right now!" },
+    { "NSMSG_HANDLEINFO_KARMA", "  Karma: %d" },
     { "NSMSG_HANDLEINFO_VACATION", "  On vacation." },
     { "NSMSG_HANDLEINFO_EMAIL_ADDR", "  Email address: %s" },
     { "NSMSG_HANDLEINFO_COOKIE_ACTIVATION", "  Cookie: There is currently an activation cookie issued for this account" },
@@ -200,6 +210,9 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_HANDLEINFO_EPITHET", "  Epithet: %s" },
     { "NSMSG_HANDLEINFO_FAKEHOST", "  Fake host: %s" },
     { "NSMSG_HANDLEINFO_LAST_HOST", "  Last quit hostmask: %s" },
+    { "NSMSG_HANDLEINFO_NO_NOTES", "  Notes: None" },
+    { "NSMSG_HANDLEINFO_NOTE_EXPIRES", "  Note %d (%s ago by %s, expires %s): %s" },
+    { "NSMSG_HANDLEINFO_NOTE", "  Note %d (%s ago by %s): %s" },
     { "NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN", "  Last quit hostmask: Unknown" },
     { "NSMSG_HANDLEINFO_NICKS", "  Nickname(s): %s" },
     { "NSMSG_HANDLEINFO_MASKS", "  Hostmask(s): %s" },
@@ -209,6 +222,9 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_USERINFO_AUTHED_AS", "$b%s$b is authenticated to account $b%s$b." },
     { "NSMSG_USERINFO_NOT_AUTHED", "$b%s$b is not authenticated to any account." },
     { "NSMSG_NICKINFO_OWNER", "Nick $b%s$b is owned by account $b%s$b." },
+    { "NSMSG_NOTE_EXPIRES", "Note %d (%s ago by %s, expires %s): %s" },
+    { "NSMSG_NOTE", "Note %d (%s ago by %s): %s" },
+    { "NSMSG_NOTE_COUNT", "%u note(s) for %s." },
     { "NSMSG_PASSWORD_INVALID", "Incorrect password; please try again." },
     { "NSMSG_PLEASE_SET_EMAIL", "We now require email addresses for users.  Please use the $bset email$b command to set your email address!" },
     { "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)." },
@@ -240,6 +256,9 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_UNREGNICK_SUCCESS", "Nick $b%s$b has been unregistered." },
     { "NSMSG_UNREGISTER_SUCCESS", "Account $b%s$b has been unregistered." },
     { "NSMSG_UNREGISTER_NICKS_SUCCESS", "Account $b%s$b and all its nicks have been unregistered." },
+    { "NSMSG_UNREGISTER_MUST_FORCE", "Account $b%s$b is not inactive or has special flags set; use FORCE to unregister it." },
+    { "NSMSG_UNREGISTER_CANNOT_FORCE", "Account $b%s$b is not inactive or has special flags set; have an IRCOp use FORCE to unregister it." },
+    { "NSMSG_UNREGISTER_NODELETE", "Account $b%s$b is protected from unregistration." },
     { "NSMSG_HANDLE_STATS", "There are %d nicks registered to your account." },
     { "NSMSG_HANDLE_NONE", "You are not authenticated against any account." },
     { "NSMSG_GLOBAL_STATS", "There are %d accounts and %d nicks registered globally." },
@@ -248,12 +267,16 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_CANNOT_GHOST_USER", "$b%s$b is not authed to your account; you may not ghost-kill them." },
     { "NSMSG_GHOST_KILLED", "$b%s$b has been killed as a ghost." },
     { "NSMSG_ON_VACATION", "You are now on vacation.  Your account will be preserved until you authenticate again." },
+    { "NSMSG_EXCESSIVE_DURATION", "$b%s$b is too long for this command." },
+    { "NSMSG_NOTE_ADDED", "Note $b%d$b added to $b%s$b." },
+    { "NSMSG_NOTE_REMOVED", "Note $b%d$b removed from $b%s$b." },
+    { "NSMSG_NO_SUCH_NOTE", "Account $b%s$b does not have a note with ID $b%d$b." },
     { "NSMSG_NO_ACCESS", "Access denied." },
     { "NSMSG_INVALID_FLAG", "$b%c$b is not a valid $N account flag." },
     { "NSMSG_SET_FLAG", "Applied flags $b%s$b to %s's $N account." },
     { "NSMSG_FLAG_PRIVILEGED", "You have insufficient access to set flag %c." },
     { "NSMSG_DB_UNREADABLE", "Unable to read database file %s; check the log for more information." },
-    { "NSMSG_DB_MERGED", "$N merged DB from %s (in "FMT_TIME_T".%03lu seconds)." },
+    { "NSMSG_DB_MERGED", "$N merged DB from %s (in %lu.%03lu seconds)." },
     { "NSMSG_HANDLE_CHANGED", "$b%s$b's account name has been changed to $b%s$b." },
     { "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." },
     { "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,14 +296,12 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_CLONE_AUTH", "Warning: %s (%s@%s) authed to your account." },
     { "NSMSG_SETTING_LIST", "$b$N account settings:$b" },
     { "NSMSG_INVALID_OPTION", "$b%s$b is an invalid account setting." },
-    { "NSMSG_INVALID_ANNOUNCE", "$b%s$b is an invalid announcements value." },
     { "NSMSG_SET_INFO", "$bINFO:         $b%s" },
     { "NSMSG_SET_WIDTH", "$bWIDTH:        $b%d" },
     { "NSMSG_SET_TABLEWIDTH", "$bTABLEWIDTH:   $b%d" },
     { "NSMSG_SET_COLOR", "$bCOLOR:        $b%s" },
     { "NSMSG_SET_PRIVMSG", "$bPRIVMSG:      $b%s" },
     { "NSMSG_SET_STYLE", "$bSTYLE:        $b%s" },
-    { "NSMSG_SET_ANNOUNCEMENTS", "$bANNOUNCEMENTS: $b%s" },
     { "NSMSG_SET_PASSWORD", "$bPASSWORD:     $b%s" },
     { "NSMSG_SET_FLAGS", "$bFLAGS:        $b%s" },
     { "NSMSG_SET_EMAIL", "$bEMAIL:        $b%s" },
@@ -290,6 +311,8 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_SET_EPITHET", "$bEPITHET:      $b%s" },
     { "NSMSG_SET_TITLE", "$bTITLE:        $b%s" },
     { "NSMSG_SET_FAKEHOST", "$bFAKEHOST:    $b%s" },
+    { "NSMSG_INVALID_KARMA", "$b%s$b is not a valid karma modifier." },
+    { "NSMSG_SET_KARMA", "$bKARMA:       $b%d$b" },
     { "NSEMAIL_ACTIVATION_SUBJECT", "Account verification for %s" },
     { "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." },
     { "NSEMAIL_PASSWORD_CHANGE_SUBJECT", "Password change verification on %s" },
@@ -303,6 +326,9 @@ static const struct message_entry msgtab[] = {
     { "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." },
     { "CHECKPASS_YES", "Yes." },
     { "CHECKPASS_NO", "No." },
+    { "CHECKEMAIL_NOT_SET", "No email set." },
+    { "CHECKEMAIL_YES", "Yes." },
+    { "CHECKEMAIL_NO", "No." },
     { NULL, NULL }
 };
 
@@ -355,19 +381,29 @@ static struct {
     unsigned long auto_reclaim_delay;
     unsigned char default_maxlogins;
     unsigned char hard_maxlogins;
+    unsigned long ounregister_inactive;
+    unsigned long ounregister_flags;
 } nickserv_conf;
 
 /* We have 2^32 unique account IDs to use. */
 unsigned long int highest_id = 0;
 
+#define WALK_NOTES(HANDLE, PREV, NOTE) \
+    for (PREV = NULL, NOTE = (HANDLE)->notes; NOTE != NULL; PREV = NOTE, NOTE = NOTE->next) \
+        if (NOTE->expires && NOTE->expires < now) { \
+            if (PREV) PREV->next = NOTE->next; else (HANDLE)->notes = NOTE->next; \
+            free(NOTE); \
+            if (!(NOTE = PREV ? PREV : (HANDLE)->notes)) break; \
+        } else
+
 static char *
 canonicalize_hostmask(char *mask)
 {
     char *out = mask, *temp;
     if ((temp = strchr(mask, '!'))) {
-       temp++;
-       while (*temp) *out++ = *temp++;
-       *out++ = 0;
+        temp++;
+        while (*temp) *out++ = *temp++;
+        *out++ = 0;
     }
     return mask;
 }
@@ -387,7 +423,7 @@ register_handle(const char *handle, const char *passwd, UNUSED_ARG(unsigned long
             id = 1 + highest_id++;
         } else {
             /* Note: highest_id is and must always be the highest ID. */
-            if(id > highest_id) {
+            if (id > highest_id) {
                 highest_id = id;
             }
         }
@@ -405,7 +441,6 @@ register_handle(const char *handle, const char *passwd, UNUSED_ARG(unsigned long
 
     hi = calloc(1, sizeof(*hi));
     hi->userlist_style = HI_DEFAULT_STYLE;
-    hi->announcements = '?';
     hi->handle = strdup(handle);
     safestrncpy(hi->passwd, passwd, sizeof(hi->passwd));
     hi->infoline = NULL;
@@ -443,15 +478,15 @@ delete_nick(struct nick_info *ni)
     }
     /* Remove ni from the nick_info linked list. */
     if (ni == ni->owner->nicks) {
-       ni->owner->nicks = ni->next;
+        ni->owner->nicks = ni->next;
     } else {
-       last = ni->owner->nicks;
-       next = last->next;
-       while (next != ni) {
-           last = next;
-           next = last->next;
-       }
-       last->next = next->next;
+        last = ni->owner->nicks;
+        next = last->next;
+        while (next != ni) {
+            last = next;
+            next = last->next;
+        }
+        last->next = next->next;
     }
     dict_remove(nickserv_nick_dict, ni->nick);
 }
@@ -463,13 +498,13 @@ void
 reg_unreg_func(unreg_func_t func)
 {
     if (unreg_func_used == unreg_func_size) {
-       if (unreg_func_size) {
-           unreg_func_size <<= 1;
-           unreg_func_list = realloc(unreg_func_list, unreg_func_size*sizeof(unreg_func_t));
-       } else {
-           unreg_func_size = 8;
-           unreg_func_list = malloc(unreg_func_size*sizeof(unreg_func_t));
-       }
+        if (unreg_func_size) {
+            unreg_func_size <<= 1;
+            unreg_func_list = realloc(unreg_func_list, unreg_func_size*sizeof(unreg_func_t));
+        } else {
+            unreg_func_size = 8;
+            unreg_func_list = malloc(unreg_func_size*sizeof(unreg_func_t));
+        }
     }
     unreg_func_list[unreg_func_used++] = func;
 }
@@ -507,6 +542,11 @@ free_handle_info(void *vhi)
         timeq_del(hi->cookie->expires, nickserv_free_cookie, hi->cookie, 0);
         nickserv_free_cookie(hi->cookie);
     }
+    while (hi->notes) {
+        struct handle_note *note = hi->notes;
+        hi->notes = note->next;
+        free(note);
+    }
     if (hi->email_addr) {
         struct handle_info_list *hil = dict_find(nickserv_email_dict, hi->email_addr, NULL);
         handle_info_list_remove(hil, hi);
@@ -571,21 +611,21 @@ oper_has_access(struct userNode *user, struct userNode *bot, unsigned int min_le
     }
 
     if (!IsOper(user) && (!IsHelping(user) || min_level)) {
-       if (!quiet)
+        if (!quiet)
             send_message(user, bot, "NSMSG_NO_ACCESS");
-       return 0;
+        return 0;
     }
 
     if (HANDLE_FLAGGED(user->handle_info, OPER_SUSPENDED)) {
-       if (!quiet)
+        if (!quiet)
             send_message(user, bot, "MSG_OPER_SUSPENDED");
-       return 0;
+        return 0;
     }
 
     if (user->handle_info->opserv_level < min_level) {
-       if (!quiet)
+        if (!quiet)
             send_message(user, bot, "NSMSG_NO_ACCESS");
-       return 0;
+        return 0;
     }
 
     return 1;
@@ -601,7 +641,7 @@ is_valid_handle(const char *handle)
         return 0;
     /* check against maximum length */
     if (strlen(handle) > NICKSERV_HANDLE_LEN)
-       return 0;
+        return 0;
     /* for consistency, only allow account names that could be nicks */
     if (!is_valid_nick(handle))
         return 0;
@@ -683,9 +723,9 @@ smart_get_handle_info(struct userNode *service, struct userNode *user, const cha
             return 0;
         }
         if (IsLocal(target)) {
-           if (IsService(target))
+            if (IsService(target))
                 send_message(user, service, "NSMSG_USER_IS_SERVICE", target->nick);
-           else
+            else
                 send_message(user, service, "MSG_USER_AUTHENTICATE", target->nick);
             return 0;
         }
@@ -721,8 +761,8 @@ get_victim_oper(struct userNode *user, const char *target)
     if (!(hi = smart_get_handle_info(nickserv, user, target)))
         return 0;
     if (HANDLE_FLAGGED(user->handle_info, OPER_SUSPENDED)) {
-       send_message(user, nickserv, "MSG_OPER_SUSPENDED");
-       return 0;
+        send_message(user, nickserv, "MSG_OPER_SUSPENDED");
+        return 0;
     }
     return oper_outranks(user, hi) ? hi : NULL;
 }
@@ -741,8 +781,8 @@ valid_user_for(struct userNode *user, struct handle_info *hi)
             return 1;
     /* If they are allowauthed to this account, allow it (removing the aa). */
     if (dict_find(nickserv_allow_auth_dict, user->nick, NULL) == hi) {
-       dict_remove(nickserv_allow_auth_dict, user->nick);
-       return 2;
+        dict_remove(nickserv_allow_auth_dict, user->nick);
+        return 2;
     }
     /* The user is not allowed to use this account. */
     return 0;
@@ -773,16 +813,16 @@ is_secure_password(const char *handle, const char *pass, struct userNode *user)
         return 0;
     }
     for (i=0; i<len; i++) {
-       if (isdigit(pass[i]))
+        if (isdigit(pass[i]))
             cnt_digits++;
-       if (isupper(pass[i]))
+        if (isupper(pass[i]))
             cnt_upper++;
-       if (islower(pass[i]))
+        if (islower(pass[i]))
             cnt_lower++;
     }
     if ((cnt_lower < nickserv_conf.password_min_lower)
-       || (cnt_upper < nickserv_conf.password_min_upper)
-       || (cnt_digits < nickserv_conf.password_min_digits)) {
+        || (cnt_upper < nickserv_conf.password_min_upper)
+        || (cnt_digits < nickserv_conf.password_min_digits)) {
         if (user)
             send_message(user, nickserv, "NSMSG_PASSWORD_READABLE", nickserv_conf.password_min_digits, nickserv_conf.password_min_upper, nickserv_conf.password_min_lower);
         return 0;
@@ -797,13 +837,13 @@ void
 reg_auth_func(auth_func_t func)
 {
     if (auth_func_used == auth_func_size) {
-       if (auth_func_size) {
-           auth_func_size <<= 1;
-           auth_func_list = realloc(auth_func_list, auth_func_size*sizeof(auth_func_t));
-       } else {
-           auth_func_size = 8;
-           auth_func_list = malloc(auth_func_size*sizeof(auth_func_t));
-       }
+        if (auth_func_size) {
+            auth_func_size <<= 1;
+            auth_func_list = realloc(auth_func_list, auth_func_size*sizeof(auth_func_t));
+        } else {
+            auth_func_size = 8;
+            auth_func_list = malloc(auth_func_size*sizeof(auth_func_t));
+        }
     }
     auth_func_list[auth_func_used++] = func;
 }
@@ -869,25 +909,25 @@ set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
         return;
 
     if (user->handle_info) {
-       struct userNode *other;
+        struct userNode *other;
 
-       if (IsHelper(user))
+        if (IsHelper(user))
             userList_remove(&curr_helpers, user);
 
-       /* remove from next_authed linked list */
-       if (user->handle_info->users == user) {
-           user->handle_info->users = user->next_authed;
-       } else {
-           for (other = user->handle_info->users;
-                other->next_authed != user;
-                other = other->next_authed) ;
-           other->next_authed = user->next_authed;
-       }
+        /* remove from next_authed linked list */
+        if (user->handle_info->users == user) {
+            user->handle_info->users = user->next_authed;
+        } else {
+            for (other = user->handle_info->users;
+                 other->next_authed != user;
+                 other = other->next_authed) ;
+            other->next_authed = user->next_authed;
+        }
         /* if nobody left on old handle, and they're not an oper, remove !god */
         if (!user->handle_info->users && !user->handle_info->opserv_level)
             HANDLE_CLEAR_FLAG(user->handle_info, HELPING);
         /* record them as being last seen at this time */
-       user->handle_info->lastseen = now;
+        user->handle_info->lastseen = now;
         /* and record their hostmask */
         snprintf(user->handle_info->last_quit_host, sizeof(user->handle_info->last_quit_host), "%s@%s", user->ident, user->hostname);
     }
@@ -906,10 +946,10 @@ set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
             for (other = hi->users; other; other = other->next_authed)
                 send_message(other, nickserv, "NSMSG_CLONE_AUTH", user->nick, user->ident, user->hostname);
         }
-       user->next_authed = hi->users;
-       hi->users = user;
-       hi->lastseen = now;
-       if (IsHelper(user))
+        user->next_authed = hi->users;
+        hi->users = user;
+        hi->lastseen = now;
+        if (IsHelper(user) && !userList_contains(&curr_helpers, user))
             userList_append(&curr_helpers, user);
 
         if (hi->fakehost || old_info)
@@ -942,7 +982,7 @@ set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
             timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
     } else {
         /* We cannot clear the user's account ID, unfortunately. */
-       user->next_authed = NULL;
+        user->next_authed = NULL;
     }
 }
 
@@ -954,8 +994,8 @@ nickserv_register(struct userNode *user, struct userNode *settee, const char *ha
     char crypted[MD5_CRYPT_LENGTH];
 
     if ((hi = dict_find(nickserv_handle_dict, handle, NULL))) {
-       send_message(user, nickserv, "NSMSG_HANDLE_EXISTS", handle);
-       return 0;
+        send_message(user, nickserv, "NSMSG_HANDLE_EXISTS", handle);
+        return 0;
     }
 
     if (!is_secure_password(handle, passwd, user))
@@ -1044,7 +1084,7 @@ nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_
             snprintf(subject, sizeof(subject), fmt, netname);
             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_BODY_NEW");
             snprintf(body, sizeof(body), fmt, netname, cookie->cookie+COOKIELEN/2, nickserv->nick, self->name, hi->handle, COOKIELEN/2);
-            sendmail(nickserv, hi, subject, body, 1);
+            mail_send(nickserv, hi, subject, body, 1);
             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_BODY_OLD");
             snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle, COOKIELEN/2, hi->email_addr);
         } else {
@@ -1053,7 +1093,7 @@ nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_
             snprintf(subject, sizeof(subject), fmt, netname);
             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_VERIFY_BODY");
             snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
-            sendmail(nickserv, hi, subject, body, 1);
+            mail_send(nickserv, hi, subject, body, 1);
             subject[0] = 0;
         }
         hi->email_addr = misc;
@@ -1070,7 +1110,7 @@ nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_
         break;
     }
     if (subject[0])
-        sendmail(nickserv, hi, subject, body, first_time);
+        mail_send(nickserv, hi, subject, body, first_time);
     nickserv_bake_cookie(cookie);
 }
 
@@ -1120,9 +1160,9 @@ static NICKSERV_FUNC(cmd_register)
     int no_auth;
 
     if (!IsOper(user) && !dict_size(nickserv_handle_dict)) {
-       /* Require the first handle registered to belong to someone +o. */
-       reply("NSMSG_REQUIRE_OPER");
-       return 0;
+        /* Require the first handle registered to belong to someone +o. */
+        reply("NSMSG_REQUIRE_OPER");
+        return 0;
     }
 
     if (user->handle_info) {
@@ -1132,7 +1172,7 @@ static NICKSERV_FUNC(cmd_register)
 
     if (IsRegistering(user)) {
         reply("NSMSG_ALREADY_REGISTERING");
-       return 0;
+        return 0;
     }
 
     if (IsStamped(user)) {
@@ -1164,7 +1204,7 @@ static NICKSERV_FUNC(cmd_register)
         }
 
         /* .. and that we are allowed to send to it. */
-        if ((str = sendmail_prohibited_address(email_addr))) {
+        if ((str = mail_prohibited_address(email_addr))) {
             reply("NSMSG_EMAIL_PROHIBITED", email_addr, str);
             return 0;
         }
@@ -1240,22 +1280,22 @@ static NICKSERV_FUNC(cmd_oregister)
         mask = NULL;
         settee = NULL;
     } else if (strchr(argv[3], '@')) {
-       mask = canonicalize_hostmask(strdup(argv[3]));
-       if (argc > 4) {
-           settee = GetUserH(argv[4]);
-           if (!settee) {
-               reply("MSG_NICK_UNKNOWN", argv[4]);
+        mask = canonicalize_hostmask(strdup(argv[3]));
+        if (argc > 4) {
+            settee = GetUserH(argv[4]);
+            if (!settee) {
+                reply("MSG_NICK_UNKNOWN", argv[4]);
                 free(mask);
-               return 0;
-           }
-       } else {
-           settee = NULL;
-       }
+                return 0;
+            }
+        } else {
+            settee = NULL;
+        }
     } else if ((settee = GetUserH(argv[3]))) {
-       mask = generate_hostmask(settee, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
+        mask = generate_hostmask(settee, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT);
     } else {
-       reply("NSMSG_REGISTER_BAD_NICKMASK", argv[3]);
-       return 0;
+        reply("NSMSG_REGISTER_BAD_NICKMASK", argv[3]);
+        return 0;
     }
     if (settee && settee->handle_info) {
         reply("NSMSG_USER_PREV_AUTH", settee->nick);
@@ -1278,6 +1318,7 @@ static NICKSERV_FUNC(cmd_handleinfo)
     struct userNode *target, *next_un;
     struct handle_info *hi;
     const char *nsmsg_none;
+    time_t feh;
 
     if (argc < 2) {
         if (!(hi = user->handle_info)) {
@@ -1293,13 +1334,14 @@ static NICKSERV_FUNC(cmd_handleinfo)
 #ifdef WITH_PROTOCOL_BAHAMUT
     reply("NSMSG_HANDLEINFO_ID", hi->id);
 #endif
-    reply("NSMSG_HANDLEINFO_REGGED", ctime(&hi->registered));
+    feh = hi->registered;
+    reply("NSMSG_HANDLEINFO_REGGED", ctime(&feh));
 
     if (!hi->users) {
-       intervalString(buff, now - hi->lastseen, user->handle_info);
-       reply("NSMSG_HANDLEINFO_LASTSEEN", buff);
+        intervalString(buff, now - hi->lastseen, user->handle_info);
+        reply("NSMSG_HANDLEINFO_LASTSEEN", buff);
     } else {
-       reply("NSMSG_HANDLEINFO_LASTSEEN_NOW");
+        reply("NSMSG_HANDLEINFO_LASTSEEN_NOW");
     }
 
     reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none));
@@ -1315,6 +1357,9 @@ static NICKSERV_FUNC(cmd_handleinfo)
     } else if (hi != user->handle_info)
         return 1;
 
+    if (IsOper(user))
+        reply("NSMSG_HANDLEINFO_KARMA", hi->karma);
+
     if (nickserv_conf.email_enabled)
         reply("NSMSG_HANDLEINFO_EMAIL_ADDR", visible_email_addr(user, hi));
 
@@ -1330,17 +1375,37 @@ static NICKSERV_FUNC(cmd_handleinfo)
         reply(type);
     }
 
+    if (oper_has_access(user, cmd->parent->bot, 0, 1) || IsStaff(user)) {
+        if (!hi->notes) {
+            reply("NSMSG_HANDLEINFO_NO_NOTES");
+        } else {
+            struct handle_note *prev, *note;
+
+            WALK_NOTES(hi, prev, note) {
+                char set_time[INTERVALLEN];
+                intervalString(set_time, now - note->set, user->handle_info);
+                if (note->expires) {
+                    char exp_time[INTERVALLEN];
+                    intervalString(exp_time, note->expires - now, user->handle_info);
+                    reply("NSMSG_HANDLEINFO_NOTE_EXPIRES", note->id, set_time, note->setter, exp_time, note->note);
+                } else {
+                    reply("NSMSG_HANDLEINFO_NOTE", note->id, set_time, note->setter, note->note);
+                }
+            }
+        }
+    }
+
     if (hi->flags) {
-       unsigned long flen = 1;
-       char flags[34]; /* 32 bits possible plus '+' and '\0' */
-       flags[0] = '+';
-       for (i=0, flen=1; handle_flags[i]; i++)
-           if (hi->flags & 1 << i)
+        unsigned long flen = 1;
+        char flags[34]; /* 32 bits possible plus '+' and '\0' */
+        flags[0] = '+';
+        for (i=0, flen=1; handle_flags[i]; i++)
+            if (hi->flags & 1 << i)
                 flags[flen++] = handle_flags[i];
-       flags[flen] = 0;
-       reply("NSMSG_HANDLEINFO_FLAGS", flags);
+        flags[flen] = 0;
+        reply("NSMSG_HANDLEINFO_FLAGS", flags);
     } else {
-       reply("NSMSG_HANDLEINFO_FLAGS", nsmsg_none);
+        reply("NSMSG_HANDLEINFO_FLAGS", nsmsg_none);
     }
 
     if (HANDLE_FLAGGED(hi, SUPPORT_HELPER)
@@ -1358,28 +1423,28 @@ static NICKSERV_FUNC(cmd_handleinfo)
         reply("NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN");
 
     if (nickserv_conf.disable_nicks) {
-       /* nicks disabled; don't show anything about registered nicks */
+        /* nicks disabled; don't show anything about registered nicks */
     } else if (hi->nicks) {
-       struct nick_info *ni, *next_ni;
-       for (ni = hi->nicks; ni; ni = next_ni) {
-           herelen = strlen(ni->nick);
-           if (pos + herelen + 1 > ArrayLength(buff)) {
-               next_ni = ni;
-               goto print_nicks_buff;
-           } else {
-               next_ni = ni->next;
-           }
-           memcpy(buff+pos, ni->nick, herelen);
-           pos += herelen; buff[pos++] = ' ';
-           if (!next_ni) {
-             print_nicks_buff:
-               buff[pos-1] = 0;
-               reply("NSMSG_HANDLEINFO_NICKS", buff);
-               pos = 0;
-           }
-       }
+        struct nick_info *ni, *next_ni;
+        for (ni = hi->nicks; ni; ni = next_ni) {
+            herelen = strlen(ni->nick);
+            if (pos + herelen + 1 > ArrayLength(buff)) {
+                next_ni = ni;
+                goto print_nicks_buff;
+            } else {
+                next_ni = ni->next;
+            }
+            memcpy(buff+pos, ni->nick, herelen);
+            pos += herelen; buff[pos++] = ' ';
+            if (!next_ni) {
+              print_nicks_buff:
+                buff[pos-1] = 0;
+                reply("NSMSG_HANDLEINFO_NICKS", buff);
+                pos = 0;
+            }
+        }
     } else {
-       reply("NSMSG_HANDLEINFO_NICKS", nsmsg_none);
+        reply("NSMSG_HANDLEINFO_NICKS", nsmsg_none);
     }
 
     if (hi->masks->used) {
@@ -1403,50 +1468,50 @@ static NICKSERV_FUNC(cmd_handleinfo)
     }
 
     if (hi->channels) {
-       struct userData *channel, *next;
-       char *name;
+        struct userData *channel, *next;
+        char *name;
 
-       for (channel = hi->channels; channel; channel = next) {
-           next = channel->u_next;
+        for (channel = hi->channels; channel; channel = next) {
+            next = channel->u_next;
             name = channel->channel->channel->name;
-           herelen = strlen(name);
-           if (pos + herelen + 7 > ArrayLength(buff)) {
-               next = channel;
+            herelen = strlen(name);
+            if (pos + herelen + 7 > ArrayLength(buff)) {
+                next = channel;
                 goto print_chans_buff;
-           }
+            }
             if (IsUserSuspended(channel))
                 buff[pos++] = '-';
             pos += sprintf(buff+pos, "%d:%s ", channel->access, name);
-           if (next == NULL) {
-             print_chans_buff:
-               buff[pos-1] = 0;
-               reply("NSMSG_HANDLEINFO_CHANNELS", buff);
-               pos = 0;
-           }
-       }
+            if (next == NULL) {
+              print_chans_buff:
+                buff[pos-1] = 0;
+                reply("NSMSG_HANDLEINFO_CHANNELS", buff);
+                pos = 0;
+            }
+        }
     } else {
-       reply("NSMSG_HANDLEINFO_CHANNELS", nsmsg_none);
+        reply("NSMSG_HANDLEINFO_CHANNELS", nsmsg_none);
     }
 
     for (target = hi->users; target; target = next_un) {
-       herelen = strlen(target->nick);
-       if (pos + herelen + 1 > ArrayLength(buff)) {
-           next_un = target;
-           goto print_cnick_buff;
-       } else {
-           next_un = target->next_authed;
-       }
-       memcpy(buff+pos, target->nick, herelen);
-       pos += herelen; buff[pos++] = ' ';
-       if (!next_un) {
-         print_cnick_buff:
-           buff[pos-1] = 0;
-           reply("NSMSG_HANDLEINFO_CURRENT", buff);
-           pos = 0;
-       }
+        herelen = strlen(target->nick);
+        if (pos + herelen + 1 > ArrayLength(buff)) {
+            next_un = target;
+            goto print_cnick_buff;
+        } else {
+            next_un = target->next_authed;
+        }
+        memcpy(buff+pos, target->nick, herelen);
+        pos += herelen; buff[pos++] = ' ';
+        if (!next_un) {
+            print_cnick_buff:
+            buff[pos-1] = 0;
+            reply("NSMSG_HANDLEINFO_CURRENT", buff);
+            pos = 0;
+        }
     }
 
-    return 1;
+    return 1 | ((hi != user->handle_info) ? CMD_LOG_STAFF : 0);
 }
 
 static NICKSERV_FUNC(cmd_userinfo)
@@ -1455,13 +1520,13 @@ static NICKSERV_FUNC(cmd_userinfo)
 
     NICKSERV_MIN_PARMS(2);
     if (!(target = GetUserH(argv[1]))) {
-       reply("MSG_NICK_UNKNOWN", argv[1]);
-       return 0;
+        reply("MSG_NICK_UNKNOWN", argv[1]);
+        return 0;
     }
     if (target->handle_info)
-       reply("NSMSG_USERINFO_AUTHED_AS", target->nick, target->handle_info->handle);
+        reply("NSMSG_USERINFO_AUTHED_AS", target->nick, target->handle_info->handle);
     else
-       reply("NSMSG_USERINFO_NOT_AUTHED", target->nick);
+        reply("NSMSG_USERINFO_NOT_AUTHED", target->nick);
     return 1;
 }
 
@@ -1471,13 +1536,39 @@ static NICKSERV_FUNC(cmd_nickinfo)
 
     NICKSERV_MIN_PARMS(2);
     if (!(ni = get_nick_info(argv[1]))) {
-       reply("MSG_NICK_UNKNOWN", argv[1]);
-       return 0;
+        reply("MSG_NICK_UNKNOWN", argv[1]);
+        return 0;
     }
     reply("NSMSG_NICKINFO_OWNER", ni->nick, ni->owner->handle);
     return 1;
 }
 
+static NICKSERV_FUNC(cmd_notes)
+{
+    struct handle_info *hi;
+    struct handle_note *prev, *note;
+    unsigned int hits;
+
+    NICKSERV_MIN_PARMS(2);
+    if (!(hi = get_victim_oper(user, argv[1])))
+        return 0;
+    hits = 0;
+    WALK_NOTES(hi, prev, note) {
+        char set_time[INTERVALLEN];
+        intervalString(set_time, now - note->set, user->handle_info);
+        if (note->expires) {
+            char exp_time[INTERVALLEN];
+            intervalString(exp_time, note->expires - now, user->handle_info);
+            reply("NSMSG_NOTE_EXPIRES", note->id, set_time, note->setter, exp_time, note->note);
+        } else {
+            reply("NSMSG_NOTE", note->id, set_time, note->setter, note->note);
+        }
+        ++hits;
+    }
+    reply("NSMSG_NOTE_COUNT", hits, argv[1]);
+    return 1;
+}
+
 static NICKSERV_FUNC(cmd_rename_handle)
 {
     struct handle_info *hi;
@@ -1767,6 +1858,26 @@ static NICKSERV_FUNC(cmd_delcookie)
     return 1;
 }
 
+static NICKSERV_FUNC(cmd_odelcookie)
+{
+    struct handle_info *hi;
+
+    NICKSERV_MIN_PARMS(2);
+
+    if (!(hi = get_victim_oper(user, argv[1])))
+        return 0;
+
+    if (!hi->cookie) {
+        reply("NSMSG_NO_COOKIE_FOREIGN", hi->handle);
+        return 0;
+    }
+
+    nickserv_eat_cookie(hi->cookie);
+    reply("NSMSG_ATE_COOKIE_FOREIGN", hi->handle);
+    return 1;
+}
+
+
 static NICKSERV_FUNC(cmd_resetpass)
 {
     struct handle_info *hi;
@@ -1887,8 +1998,8 @@ static NICKSERV_FUNC(cmd_oregnick) {
     }
     ni = dict_find(nickserv_nick_dict, nick, NULL);
     if (ni) {
-       reply("NSMSG_NICK_EXISTS", nick);
-       return 0;
+        reply("NSMSG_NICK_EXISTS", nick);
+        return 0;
     }
     register_nick(nick, target);
     reply("NSMSG_OREGNICK_SUCCESS", nick, target->handle);
@@ -1911,8 +2022,8 @@ static NICKSERV_FUNC(cmd_regnick) {
     }
     ni = dict_find(nickserv_nick_dict, user->nick, NULL);
     if (ni) {
-       reply("NSMSG_NICK_EXISTS", user->nick);
-       return 0;
+        reply("NSMSG_NICK_EXISTS", user->nick);
+        return 0;
     }
     register_nick(user->nick, user->handle_info);
     reply("NSMSG_REGNICK_SUCCESS", user->nick);
@@ -1932,8 +2043,8 @@ static NICKSERV_FUNC(cmd_pass)
     if (!is_secure_password(hi->handle, new_pass, user)) return 0;
     if (!checkpass(old_pass, hi->passwd)) {
         argv[1] = "BADPASS";
-       reply("NSMSG_PASSWORD_INVALID");
-       return 0;
+        reply("NSMSG_PASSWORD_INVALID");
+        return 0;
     }
     cryptpass(new_pass, hi->passwd);
     argv[1] = "****";
@@ -1985,21 +2096,21 @@ static NICKSERV_FUNC(cmd_oaddmask)
 }
 
 static int
-nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask)
+nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_mask, int force)
 {
     unsigned int i;
     for (i=0; i<hi->masks->used; i++) {
-       if (!strcmp(del_mask, hi->masks->list[i])) {
-           char *old_mask = hi->masks->list[i];
-           if (hi->masks->used == 1) {
-               send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
-               return 0;
-           }
-           hi->masks->list[i] = hi->masks->list[--hi->masks->used];
-           send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
-           free(old_mask);
-           return 1;
-       }
+        if (!strcmp(del_mask, hi->masks->list[i])) {
+            char *old_mask = hi->masks->list[i];
+            if (hi->masks->used == 1 && !force) {
+                send_message(user, nickserv, "NSMSG_DELMASK_NOTLAST");
+                return 0;
+            }
+            hi->masks->list[i] = hi->masks->list[--hi->masks->used];
+            send_message(user, nickserv, "NSMSG_DELMASK_SUCCESS", old_mask);
+            free(old_mask);
+            return 1;
+        }
     }
     send_message(user, nickserv, "NSMSG_DELMASK_NOT_FOUND");
     return 0;
@@ -2008,7 +2119,7 @@ nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_
 static NICKSERV_FUNC(cmd_delmask)
 {
     NICKSERV_MIN_PARMS(2);
-    return nickserv_delmask(user, user->handle_info, argv[1]);
+    return nickserv_delmask(user, user->handle_info, argv[1], 0);
 }
 
 static NICKSERV_FUNC(cmd_odelmask)
@@ -2017,7 +2128,7 @@ static NICKSERV_FUNC(cmd_odelmask)
     NICKSERV_MIN_PARMS(3);
     if (!(hi = get_victim_oper(user, argv[1])))
         return 0;
-    return nickserv_delmask(user, hi, argv[2]);
+    return nickserv_delmask(user, hi, argv[2], 1);
 }
 
 int
@@ -2026,26 +2137,26 @@ nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const
     unsigned long added, removed, flag;
 
     for (added=removed=nn=0; str[nn]; nn++) {
-       switch (str[nn]) {
-       case '+': add = 1; break;
-       case '-': add = 0; break;
-       default:
-           if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
-               send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
-               return 0;
-           }
+        switch (str[nn]) {
+        case '+': add = 1; break;
+        case '-': add = 0; break;
+        default:
+            if (!(pos = handle_inverse_flags[(unsigned char)str[nn]])) {
+                send_message(user, bot, "NSMSG_INVALID_FLAG", str[nn]);
+                return 0;
+            }
             if (user && (user->handle_info->opserv_level < flag_access_levels[pos-1])) {
                 /* cheesy avoidance of looking up the flag name.. */
                 send_message(user, bot, "NSMSG_FLAG_PRIVILEGED", str[nn]);
                 return 0;
             }
             flag = 1 << (pos - 1);
-           if (add)
+            if (add)
                 added |= flag, removed &= ~flag;
-           else
+            else
                 removed |= flag, added &= ~flag;
-           break;
-       }
+            break;
+        }
     }
     *padded = added;
     *premoved = removed;
@@ -2101,15 +2212,15 @@ set_list(struct userNode *user, struct handle_info *hi, int override)
     unsigned int i;
     char *set_display[] = {
         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
-        "EMAIL", "ANNOUNCEMENTS", "MAXLOGINS", "LANGUAGE"
+        "EMAIL", "MAXLOGINS", "LANGUAGE"
     };
 
     send_message(user, nickserv, "NSMSG_SETTING_LIST");
 
     /* Do this so options are presented in a consistent order. */
     for (i = 0; i < ArrayLength(set_display); ++i)
-       if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
-           opt(user, hi, override, 0, NULL);
+        if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
+            opt(user, hi, override, 0, NULL);
 }
 
 static NICKSERV_FUNC(cmd_set)
@@ -2119,11 +2230,11 @@ static NICKSERV_FUNC(cmd_set)
 
     hi = user->handle_info;
     if (argc < 2) {
-       set_list(user, hi, 0);
-       return 1;
+        set_list(user, hi, 0);
+        return 1;
     }
     if (!(opt = dict_find(nickserv_opt_dict, argv[1], NULL))) {
-       reply("NSMSG_INVALID_OPTION", argv[1]);
+        reply("NSMSG_INVALID_OPTION", argv[1]);
         return 0;
     }
     return opt(user, hi, 0, argc-1, argv+1);
@@ -2132,7 +2243,9 @@ static NICKSERV_FUNC(cmd_set)
 static NICKSERV_FUNC(cmd_oset)
 {
     struct handle_info *hi;
+    struct svccmd *subcmd;
     option_func_t *opt;
+    char cmdname[MAXLEN];
 
     NICKSERV_MIN_PARMS(2);
 
@@ -2140,15 +2253,20 @@ static NICKSERV_FUNC(cmd_oset)
         return 0;
 
     if (argc < 3) {
-       set_list(user, hi, 0);
-       return 1;
+        set_list(user, hi, 0);
+        return 1;
     }
 
     if (!(opt = dict_find(nickserv_opt_dict, argv[2], NULL))) {
-       reply("NSMSG_INVALID_OPTION", argv[2]);
+        reply("NSMSG_INVALID_OPTION", argv[2]);
         return 0;
     }
 
+    sprintf(cmdname, "%s %s", cmd->name, argv[2]);
+    subcmd = dict_find(cmd->parent->commands, cmdname, NULL);
+    if (subcmd && !svccmd_can_invoke(user, cmd->parent->bot, subcmd, NULL, SVCCMD_NOISY))
+        return 0;
+
     return opt(user, hi, 1, argc-2, argv+2);
 }
 
@@ -2156,12 +2274,12 @@ static OPTION_FUNC(opt_info)
 {
     const char *info;
     if (argc > 1) {
-       if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
+        if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
             free(hi->infoline);
             hi->infoline = NULL;
-       } else {
-           hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
-       }
+        } else {
+            hi->infoline = strdup(unsplit_string(argv+1, argc-1, NULL));
+        }
     }
 
     info = hi->infoline ? hi->infoline : user_find_message(user, "MSG_NONE");
@@ -2172,7 +2290,7 @@ static OPTION_FUNC(opt_info)
 static OPTION_FUNC(opt_width)
 {
     if (argc > 1)
-       hi->screen_width = strtoul(argv[1], NULL, 0);
+        hi->screen_width = strtoul(argv[1], NULL, 0);
 
     if ((hi->screen_width > 0) && (hi->screen_width < MIN_LINE_SIZE))
         hi->screen_width = MIN_LINE_SIZE;
@@ -2186,7 +2304,7 @@ static OPTION_FUNC(opt_width)
 static OPTION_FUNC(opt_tablewidth)
 {
     if (argc > 1)
-       hi->table_width = strtoul(argv[1], NULL, 0);
+        hi->table_width = strtoul(argv[1], NULL, 0);
 
     if ((hi->table_width > 0) && (hi->table_width < MIN_LINE_SIZE))
         hi->table_width = MIN_LINE_SIZE;
@@ -2200,14 +2318,14 @@ static OPTION_FUNC(opt_tablewidth)
 static OPTION_FUNC(opt_color)
 {
     if (argc > 1) {
-       if (enabled_string(argv[1]))
-           HANDLE_SET_FLAG(hi, MIRC_COLOR);
+        if (enabled_string(argv[1]))
+            HANDLE_SET_FLAG(hi, MIRC_COLOR);
         else if (disabled_string(argv[1]))
-           HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
-       else {
-           send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
-           return 0;
-       }
+            HANDLE_CLEAR_FLAG(hi, MIRC_COLOR);
+        else {
+            send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
+            return 0;
+        }
     }
 
     send_message(user, nickserv, "NSMSG_SET_COLOR", user_find_message(user, HANDLE_FLAGGED(hi, MIRC_COLOR) ? "MSG_ON" : "MSG_OFF"));
@@ -2217,14 +2335,14 @@ static OPTION_FUNC(opt_color)
 static OPTION_FUNC(opt_privmsg)
 {
     if (argc > 1) {
-       if (enabled_string(argv[1]))
-           HANDLE_SET_FLAG(hi, USE_PRIVMSG);
+        if (enabled_string(argv[1]))
+            HANDLE_SET_FLAG(hi, USE_PRIVMSG);
         else if (disabled_string(argv[1]))
-           HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
-       else {
-           send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
-           return 0;
-       }
+            HANDLE_CLEAR_FLAG(hi, USE_PRIVMSG);
+        else {
+            send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
+            return 0;
+        }
     }
 
     send_message(user, nickserv, "NSMSG_SET_PRIVMSG", user_find_message(user, HANDLE_FLAGGED(hi, USE_PRIVMSG) ? "MSG_ON" : "MSG_OFF"));
@@ -2236,61 +2354,34 @@ static OPTION_FUNC(opt_style)
     char *style;
 
     if (argc > 1) {
-       if (!irccasecmp(argv[1], "Zoot"))
-           hi->userlist_style = HI_STYLE_ZOOT;
-       else if (!irccasecmp(argv[1], "def"))
-           hi->userlist_style = HI_STYLE_DEF;
+        if (!irccasecmp(argv[1], "Zoot"))
+            hi->userlist_style = HI_STYLE_ZOOT;
+        else if (!irccasecmp(argv[1], "def"))
+            hi->userlist_style = HI_STYLE_DEF;
     }
 
     switch (hi->userlist_style) {
     case HI_STYLE_DEF:
-       style = "def";
-       break;
+        style = "def";
+        break;
     case HI_STYLE_ZOOT:
     default:
-       style = "Zoot";
+        style = "Zoot";
     }
 
     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
     return 1;
 }
 
-static OPTION_FUNC(opt_announcements)
-{
-    const char *choice;
-
-    if (argc > 1) {
-        if (enabled_string(argv[1]))
-            hi->announcements = 'y';
-        else if (disabled_string(argv[1]))
-            hi->announcements = 'n';
-        else if (!strcmp(argv[1], "?") || !irccasecmp(argv[1], "default"))
-            hi->announcements = '?';
-        else {
-            send_message(user, nickserv, "NSMSG_INVALID_ANNOUNCE", argv[1]);
-            return 0;
-        }
-    }
-
-    switch (hi->announcements) {
-    case 'y': choice = user_find_message(user, "MSG_ON"); break;
-    case 'n': choice = user_find_message(user, "MSG_OFF"); break;
-    case '?': choice = "default"; break;
-    default: choice = "unknown"; break;
-    }
-    send_message(user, nickserv, "NSMSG_SET_ANNOUNCEMENTS", choice);
-    return 1;
-}
-
 static OPTION_FUNC(opt_password)
 {
     if (!override) {
-       send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
-       return 0;
+        send_message(user, nickserv, "NSMSG_USE_CMD_PASS");
+        return 0;
     }
 
     if (argc > 1)
-       cryptpass(argv[1], hi->passwd);
+        cryptpass(argv[1], hi->passwd);
 
     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
     return 1;
@@ -2302,12 +2393,12 @@ static OPTION_FUNC(opt_flags)
     unsigned int ii, flen;
 
     if (!override) {
-       send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
-       return 0;
+        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+        return 0;
     }
 
     if (argc > 1)
-       nickserv_apply_flags(user, hi, argv[1]);
+        nickserv_apply_flags(user, hi, argv[1]);
 
     for (ii = flen = 0; handle_flags[ii]; ii++)
         if (hi->flags & (1 << ii))
@@ -2328,7 +2419,7 @@ static OPTION_FUNC(opt_email)
             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
             return 0;
         }
-        if ((str = sendmail_prohibited_address(argv[1]))) {
+        if ((str = mail_prohibited_address(argv[1]))) {
             send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", argv[1], str);
             return 0;
         }
@@ -2376,6 +2467,27 @@ static OPTION_FUNC(opt_language)
     return 1;
 }
 
+static OPTION_FUNC(opt_karma)
+{
+    if (!override) {
+        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+        return 0;
+    }
+
+    if (argc > 1) {
+        if (argv[1][0] == '+' && isdigit(argv[1][1])) {
+            hi->karma += strtoul(argv[1] + 1, NULL, 10);
+        } else if (argv[1][0] == '-' && isdigit(argv[1][1])) {
+            hi->karma -= strtoul(argv[1] + 1, NULL, 10);
+        } else {
+            send_message(user, nickserv, "NSMSG_INVALID_KARMA", argv[1]);
+        }
+    }
+
+    send_message(user, nickserv, "NSMSG_SET_KARMA", hi->karma);
+    return 1;
+}
+
 int
 oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
     if (!oper_has_access(user, bot, nickserv_conf.modoper_level, 0))
@@ -2409,8 +2521,8 @@ static OPTION_FUNC(opt_level)
     int res;
 
     if (!override) {
-       send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
-       return 0;
+        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+        return 0;
     }
 
     res = (argc > 1) ? oper_try_set_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
@@ -2558,12 +2670,12 @@ static NICKSERV_FUNC(cmd_unregnick)
     nick = (argc < 2) ? user->nick : (const char*)argv[1];
     ni = dict_find(nickserv_nick_dict, nick, NULL);
     if (!ni) {
-       reply("NSMSG_UNKNOWN_NICK", nick);
-       return 0;
+        reply("NSMSG_UNKNOWN_NICK", nick);
+        return 0;
     }
     if (hi != ni->owner) {
-       reply("NSMSG_NOT_YOUR_NICK", nick);
-       return 0;
+        reply("NSMSG_NOT_YOUR_NICK", nick);
+        return 0;
     }
     reply("NSMSG_UNREGNICK_SUCCESS", ni->nick);
     delete_nick(ni);
@@ -2576,8 +2688,8 @@ static NICKSERV_FUNC(cmd_ounregnick)
 
     NICKSERV_MIN_PARMS(2);
     if (!(ni = get_nick_info(argv[1]))) {
-       reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
-       return 0;
+        reply("NSMSG_NICK_NOT_REGISTERED", argv[1]);
+        return 0;
     }
     if (!oper_outranks(user, ni->owner))
         return 0;
@@ -2599,8 +2711,8 @@ static NICKSERV_FUNC(cmd_unregister)
         nickserv_unregister_handle(hi, user);
         return 1;
     } else {
-       log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
-       reply("NSMSG_PASSWORD_INVALID");
+        log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
+        reply("NSMSG_PASSWORD_INVALID");
         return 0;
     }
 }
@@ -2609,10 +2721,26 @@ static NICKSERV_FUNC(cmd_ounregister)
 {
     struct handle_info *hi;
     char reason[MAXLEN];
+    int force;
 
     NICKSERV_MIN_PARMS(2);
     if (!(hi = get_victim_oper(user, argv[1])))
         return 0;
+
+    if (HANDLE_FLAGGED(hi, NODELETE)) {
+        reply("NSMSG_UNREGISTER_NODELETE", hi->handle);
+        return 0;
+    }
+
+    force = IsOper(user) && (argc > 2) && !irccasecmp(argv[2], "force");
+    if (!force &&
+        ((hi->flags & nickserv_conf.ounregister_flags)
+         || hi->users
+         || (hi->last_quit_host[0] && ((unsigned)(now - hi->lastseen) < nickserv_conf.ounregister_inactive)))) {
+        reply((IsOper(user) ? "NSMSG_UNREGISTER_MUST_FORCE" : "NSMSG_UNREGISTER_CANNOT_FORCE"), hi->handle);
+        return 0;
+    }
+
     snprintf(reason, sizeof(reason), "%s unregistered account %s.", user->handle_info->handle, hi->handle);
     global_message(MESSAGE_RECIPIENT_STAFF, reason);
     nickserv_unregister_handle(hi, user);
@@ -2671,6 +2799,78 @@ static NICKSERV_FUNC(cmd_vacation)
     return 1;
 }
 
+static NICKSERV_FUNC(cmd_addnote)
+{
+    struct handle_info *hi;
+    unsigned long duration;
+    char text[MAXLEN];
+    unsigned int id;
+    struct handle_note *prev;
+    struct handle_note *note;
+
+    /* Parse parameters and figure out values for note's fields. */
+    NICKSERV_MIN_PARMS(4);
+    hi = get_victim_oper(user, argv[1]);
+    if (!hi)
+        return 0;
+    if(!strcmp(argv[2], "0"))
+        duration = 0;
+    else if(!(duration = ParseInterval(argv[2])))
+    {
+        reply("MSG_INVALID_DURATION", argv[2]);
+        return 0;
+    }
+    if (duration > 2*365*86400) {
+        reply("NSMSG_EXCESSIVE_DURATION", argv[2]);
+        return 0;
+    }
+    unsplit_string(argv + 3, argc - 3, text);
+    WALK_NOTES(hi, prev, note) {}
+    id = prev ? (prev->id + 1) : 1;
+
+    /* Create the new note structure. */
+    note = calloc(1, sizeof(*note) + strlen(text));
+    note->next = NULL;
+    note->expires = duration ? (now + duration) : 0;
+    note->set = now;
+    note->id = id;
+    safestrncpy(note->setter, user->handle_info->handle, sizeof(note->setter));
+    strcpy(note->note, text);
+    if (prev)
+        prev->next = note;
+    else
+        hi->notes = note;
+    reply("NSMSG_NOTE_ADDED", id, hi->handle);
+    return 1;
+}
+
+static NICKSERV_FUNC(cmd_delnote)
+{
+    struct handle_info *hi;
+    struct handle_note *prev;
+    struct handle_note *note;
+    int id;
+
+    NICKSERV_MIN_PARMS(3);
+    hi = get_victim_oper(user, argv[1]);
+    if (!hi)
+        return 0;
+    id = strtoul(argv[2], NULL, 10);
+    WALK_NOTES(hi, prev, note) {
+        if (id == note->id) {
+            if (prev)
+                prev->next = note->next;
+            else
+                hi->notes = note->next;
+            free(note);
+            reply("NSMSG_NOTE_REMOVED", id, hi->handle);
+            return 1;
+        }
+    }
+    reply("NSMSG_NO_SUCH_NOTE", hi->handle, id);
+    return 0;
+}
+
 static int
 nickserv_saxdb_write(struct saxdb_context *ctx) {
     dict_iterator_t it;
@@ -2683,11 +2883,6 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
         assert(hi->id);
 #endif
         saxdb_start_record(ctx, iter_key(it), 0);
-        if (hi->announcements != '?') {
-            flags[0] = hi->announcements;
-            flags[1] = 0;
-            saxdb_write_string(ctx, KEY_ANNOUNCEMENTS, flags);
-        }
         if (hi->cookie) {
             struct handle_cookie *cookie = hi->cookie;
             char *type;
@@ -2709,6 +2904,21 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
                 saxdb_end_record(ctx);
             }
         }
+        if (hi->notes) {
+            struct handle_note *prev, *note;
+            saxdb_start_record(ctx, KEY_NOTES, 0);
+            WALK_NOTES(hi, prev, note) {
+                snprintf(flags, sizeof(flags), "%d", note->id);
+                saxdb_start_record(ctx, flags, 0);
+                if (note->expires)
+                    saxdb_write_int(ctx, KEY_NOTE_EXPIRES, note->expires);
+                saxdb_write_int(ctx, KEY_NOTE_SET, note->set);
+                saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
+                saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
+                saxdb_end_record(ctx);
+            }
+            saxdb_end_record(ctx);
+        }
         if (hi->email_addr)
             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
         if (hi->epithet)
@@ -2732,6 +2942,8 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
         if (hi->last_quit_host[0])
             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
+        if (hi->karma != 0)
+            saxdb_write_sint(ctx, KEY_KARMA, hi->karma);
         if (hi->masks->used)
             saxdb_write_string_list(ctx, KEY_MASKS, hi->masks);
         if (hi->maxlogins)
@@ -2881,6 +3093,9 @@ static NICKSERV_FUNC(cmd_merge)
     if (hi_from->lastseen > hi_to->lastseen)
         hi_to->lastseen = hi_from->lastseen;
 
+    /* New karma is the sum of the two original karmas. */
+    hi_to->karma += hi_from->karma;
+
     /* Does a fakehost carry over?  (This intentionally doesn't set it
      * for users previously attached to hi_to.  They'll just have to
      * reconnect.)
@@ -2900,13 +3115,16 @@ static NICKSERV_FUNC(cmd_merge)
 }
 
 struct nickserv_discrim {
-    unsigned int limit, min_level, max_level;
     unsigned long flags_on, flags_off;
-    time_t min_registered, max_registered;
-    time_t lastseen;
+    unsigned long min_registered, max_registered;
+    unsigned long lastseen;
+    unsigned int limit;
+    int min_level, max_level;
+    int min_karma, max_karma;
     enum { SUBSET, EXACT, SUPERSET, LASTQUIT } hostmask_type;
     const char *nickmask;
     const char *hostmask;
+    const char *fakehostmask;
     const char *handlemask;
     const char *emailmask;
 };
@@ -2929,11 +3147,13 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
     discrim = malloc(sizeof(*discrim));
     memset(discrim, 0, sizeof(*discrim));
     discrim->min_level = 0;
-    discrim->max_level = ~0;
+    discrim->max_level = INT_MAX;
     discrim->limit = 50;
     discrim->min_registered = 0;
-    discrim->max_registered = INT_MAX;
-    discrim->lastseen = now;
+    discrim->max_registered = ULONG_MAX;
+    discrim->lastseen = ULONG_MAX;
+    discrim->min_karma = INT_MIN;
+    discrim->max_karma = INT_MAX;
 
     for (i=0; i<argc; i++) {
         if (i == argc - 1) {
@@ -2987,17 +3207,23 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
                     goto fail;
                 }
                 discrim->hostmask_type = SUPERSET;
-           } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
-              if (i == argc - 1) {
-                  send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
-                  goto fail;
-              }
-              discrim->hostmask_type = LASTQUIT;
+            } else if (!irccasecmp(argv[i], "lastquit") || !irccasecmp(argv[i], "lastauth")) {
+                if (i == argc - 1) {
+                    send_message(user, nickserv, "MSG_MISSING_PARAMS", argv[i]);
+                    goto fail;
+                }
+                discrim->hostmask_type = LASTQUIT;
             } else {
                 i--;
                 discrim->hostmask_type = SUPERSET;
             }
             discrim->hostmask = argv[++i];
+        } else if (!irccasecmp(argv[i], "fakehost")) {
+            if (!irccasecmp(argv[++i], "*")) {
+                discrim->fakehostmask = 0;
+            } else {
+                discrim->fakehostmask = argv[i];
+            }
         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
             if (!irccasecmp(argv[++i], "*")) {
                 discrim->handlemask = 0;
@@ -3033,6 +3259,25 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
             } else {
                 send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
             }
+        } else if (!irccasecmp(argv[i], "karma")) {
+            const char *cmp = argv[++i];
+            if (cmp[0] == '<') {
+                if (cmp[1] == '=') {
+                    discrim->max_karma = strtoul(cmp+2, NULL, 0);
+                } else {
+                    discrim->max_karma = strtoul(cmp+1, NULL, 0) - 1;
+                }
+            } else if (cmp[0] == '=') {
+                discrim->min_karma = discrim->max_karma = strtoul(cmp+1, NULL, 0);
+            } else if (cmp[0] == '>') {
+                if (cmp[1] == '=') {
+                    discrim->min_karma = strtoul(cmp+2, NULL, 0);
+                } else {
+                    discrim->min_karma = strtoul(cmp+1, NULL, 0) + 1;
+                }
+            } else {
+                send_message(user, nickserv, "MSG_INVALID_CRITERIA", cmp);
+            }
         } else {
             send_message(user, nickserv, "MSG_INVALID_CRITERIA", argv[i]);
             goto fail;
@@ -3053,9 +3298,13 @@ nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
         || (discrim->max_registered < hi->registered)
         || (discrim->lastseen < (hi->users?now:hi->lastseen))
         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
+        || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
         || (discrim->min_level > hi->opserv_level)
-        || (discrim->max_level < hi->opserv_level)) {
+        || (discrim->max_level < hi->opserv_level)
+        || (discrim->min_karma > hi->karma)
+        || (discrim->max_karma < hi->karma)
+        ) {
         return 0;
     }
     if (discrim->hostmask) {
@@ -3068,8 +3317,8 @@ nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
                      && !irccasecmp(discrim->hostmask, mask)) break;
             else if ((discrim->hostmask_type == SUPERSET)
                      && (match_ircglobs(mask, discrim->hostmask))) break;
-           else if ((discrim->hostmask_type == LASTQUIT)
-                    && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
+            else if ((discrim->hostmask_type == LASTQUIT)
+                     && (match_ircglobs(discrim->hostmask, hi->last_quit_host))) break;
         }
         if (i==hi->masks->used) return 0;
     }
@@ -3229,6 +3478,25 @@ static MODCMD_FUNC(cmd_checkpass)
     return 1;
 }
 
+static MODCMD_FUNC(cmd_checkemail)
+{
+    struct handle_info *hi;
+
+    NICKSERV_MIN_PARMS(3);
+    if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
+        reply("MSG_HANDLE_UNKNOWN", argv[1]);
+        return 0;
+    }
+    if (!hi->email_addr)
+        reply("CHECKEMAIL_NOT_SET");
+    else if (!irccasecmp(argv[2], hi->email_addr))
+        reply("CHECKEMAIL_YES");
+    else
+        reply("CHECKEMAIL_NO");
+    return 1;
+}
+
+
 static void
 nickserv_db_read_handle(const char *handle, dict_t obj)
 {
@@ -3279,9 +3547,11 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     if (str)
         hi->infoline = strdup(str);
     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
-    hi->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
+    hi->registered = str ? strtoul(str, NULL, 0) : now;
     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
-    hi->lastseen = str ? (time_t)strtoul(str, NULL, 0) : hi->registered;
+    hi->lastseen = str ? strtoul(str, NULL, 0) : hi->registered;
+    str = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
+    hi->karma = str ? strtoul(str, NULL, 0) : 0;
     /* We want to read the nicks even if disable_nicks is set.  This is so
      * that we don't lose the nick data entirely. */
     slist = database_get_data(obj, KEY_NICKS, RECDB_STRING_LIST);
@@ -3296,8 +3566,6 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     }
     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
     hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
-    str = database_get_data(obj, KEY_ANNOUNCEMENTS, RECDB_QSTRING);
-    hi->announcements = str ? str[0] : '?';
     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
     hi->screen_width = str ? strtoul(str, NULL, 0) : 0;
     str = database_get_data(obj, KEY_TABLE_WIDTH, RECDB_QSTRING);
@@ -3316,6 +3584,7 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
     if (str)
         hi->fakehost = strdup(str);
+    /* Read the "cookie" sub-database (if it exists). */
     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
     if (subdb) {
         const char *data, *type, *expires, *cookie_str;
@@ -3355,6 +3624,50 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
         else
             nickserv_free_cookie(cookie);
     }
+    /* Read the "notes" sub-database (if it exists). */
+    subdb = database_get_data(obj, KEY_NOTES, RECDB_OBJECT);
+    if (subdb) {
+        dict_iterator_t it;
+        struct handle_note *last_note;
+        struct handle_note *note;
+
+        last_note = NULL;
+        for (it = dict_first(subdb); it; it = iter_next(it)) {
+            const char *expires;
+            const char *setter;
+            const char *text;
+            const char *set;
+            const char *id;
+            dict_t notedb;
+
+            id = iter_key(it);
+            notedb = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
+            if (!notedb) {
+                log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", id, hi->handle);
+                continue;
+            }
+            expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
+            setter = database_get_data(notedb, KEY_NOTE_SETTER, RECDB_QSTRING);
+            text = database_get_data(notedb, KEY_NOTE_NOTE, RECDB_QSTRING);
+            set = database_get_data(notedb, KEY_NOTE_SET, RECDB_QSTRING);
+            if (!setter || !text || !set) {
+                log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", id, hi->handle);
+                continue;
+            }
+            note = calloc(1, sizeof(*note) + strlen(text));
+            note->next = NULL;
+            note->expires = expires ? strtoul(expires, NULL, 10) : 0;
+            note->set = strtoul(set, NULL, 10);
+            note->id = strtoul(id, NULL, 10);
+            safestrncpy(note->setter, setter, sizeof(note->setter));
+            strcpy(note->note, text);
+            if (last_note)
+                last_note->next = note;
+            else
+                hi->notes = note;
+            last_note = note;
+        }
+    }
 }
 
 static int
@@ -3386,10 +3699,10 @@ static NICKSERV_FUNC(cmd_mergedb)
     stop.tv_sec -= start.tv_sec;
     stop.tv_usec -= start.tv_usec;
     if (stop.tv_usec < 0) {
-       stop.tv_sec -= 1;
-       stop.tv_usec += 1000000;
+        stop.tv_sec -= 1;
+        stop.tv_usec += 1000000;
     }
-    reply("NSMSG_DB_MERGED", argv[1], stop.tv_sec, stop.tv_usec/1000);
+    reply("NSMSG_DB_MERGED", argv[1], (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
     return 1;
 }
 
@@ -3397,7 +3710,7 @@ static void
 expire_handles(UNUSED_ARG(void *data))
 {
     dict_iterator_t it, next;
-    time_t expiry;
+    unsigned long expiry;
     struct handle_info *hi;
 
     for (it=dict_first(nickserv_handle_dict); it; it=next) {
@@ -3463,8 +3776,8 @@ nickserv_conf_read(void)
     dict_iterator_t it;
 
     if (!(conf_node = conf_get_data(NICKSERV_CONF_NAME, RECDB_OBJECT))) {
-       log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
-       return;
+        log_module(NS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", NICKSERV_CONF_NAME);
+        return;
     }
     str = database_get_data(conf_node, KEY_VALID_HANDLE_REGEX, RECDB_QSTRING);
     if (!str)
@@ -3532,6 +3845,18 @@ nickserv_conf_read(void)
     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
+    str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
+    nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
+    str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
+    if (!str)
+        str = "ShgsfnHbu";
+    nickserv_conf.ounregister_flags = 0;
+    while(*str) {
+        unsigned int pos = handle_inverse_flags[(unsigned char)*str];
+        str++;
+        if(pos)
+            nickserv_conf.ounregister_flags |= 1 << (pos - 1);
+    }
     if (!nickserv_conf.disable_nicks) {
         str = database_get_data(conf_node, "reclaim_action", RECDB_QSTRING);
         nickserv_conf.reclaim_action = str ? reclaim_action_from_string(str) : RECLAIM_NONE;
@@ -3800,13 +4125,16 @@ init_nickserv(const char *nick)
     nickserv_define_func("RENAME", cmd_rename_handle, -1, 1, 0);
     nickserv_define_func("VACATION", cmd_vacation, -1, 1, 0);
     nickserv_define_func("MERGE", cmd_merge, 750, 1, 0);
+    nickserv_define_func("ADDNOTE", cmd_addnote, 0, 1, 0);
+    nickserv_define_func("DELNOTE", cmd_delnote, 0, 1, 0);
+    nickserv_define_func("NOTES", cmd_notes, 0, 1, 0);
     if (!nickserv_conf.disable_nicks) {
-       /* nick management commands */
-       nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
-       nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
-       nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
-       nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
-       nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
+        /* nick management commands */
+        nickserv_define_func("REGNICK", cmd_regnick, -1, 1, 0);
+        nickserv_define_func("OREGNICK", cmd_oregnick, 0, 1, 0);
+        nickserv_define_func("UNREGNICK", cmd_unregnick, -1, 1, 0);
+        nickserv_define_func("OUNREGNICK", cmd_ounregnick, 0, 1, 0);
+        nickserv_define_func("NICKINFO", cmd_nickinfo, -1, 1, 0);
         nickserv_define_func("RECLAIM", cmd_reclaim, -1, 1, 0);
     }
     if (nickserv_conf.email_enabled) {
@@ -3814,6 +4142,7 @@ init_nickserv(const char *nick)
         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 1);
         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 1);
         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
+        nickserv_define_func("ODELCOOKIE", cmd_odelcookie, 0, 1, 0);
         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
     }
     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
@@ -3823,6 +4152,7 @@ init_nickserv(const char *nick)
     nickserv_define_func("SEARCH UNREGISTER", NULL, 800, 1, 0);
     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
+    nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0);
     /* other options */
     dict_insert(nickserv_opt_dict, "INFO", opt_info);
     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
@@ -3840,9 +4170,10 @@ init_nickserv(const char *nick)
         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
     }
-    dict_insert(nickserv_opt_dict, "ANNOUNCEMENTS", opt_announcements);
     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);
+    dict_insert(nickserv_opt_dict, "KARMA", opt_karma);
+    nickserv_define_func("OSET KARMA", NULL, 0, 1, 0);
 
     nickserv_handle_dict = dict_new();
     dict_set_free_keys(nickserv_handle_dict, free);