Use nickserv_addmask when adding a new mask to an account (on auth/register). This...
[srvx.git] / src / nickserv.c
index 1cb13df261af2a0e641231940051c09409d01322..5da64a780a3291a57df60ac1684492d401cdaee7 100644 (file)
@@ -1,5 +1,5 @@
 /* nickserv.c - Nick/authentication service
- * Copyright 2000-2006 srvx Development Team
+ * Copyright 2000-2008 srvx Development Team
  *
  * This file is part of srvx.
  *
 #define KEY_VALID_NICK_REGEX "valid_nick_regex"
 #define KEY_DB_BACKUP_FREQ "db_backup_freq"
 #define KEY_MODOPER_LEVEL "modoper_level"
+#define KEY_MODSTAFF_LEVEL "modstaff_level"
 #define KEY_SET_EPITHET_LEVEL "set_epithet_level"
 #define KEY_SET_TITLE_LEVEL "set_title_level"
 #define KEY_SET_FAKEHOST_LEVEL "set_fakehost_level"
+#define KEY_SET_FAKEIDENT_LEVEL "set_fakeident_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"
 #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_HANDLE_TS_MODE "account_timestamp_mode"
+#define KEY_MAX_AUTHLOG_LEN "max_authlog_len"
 
 #define KEY_ID "id"
 #define KEY_PASSWD "passwd"
 #define KEY_NICKS "nicks"
 #define KEY_MASKS "masks"
 #define KEY_OPSERV_LEVEL "opserv_level"
+#define KEY_STAFF_LEVEL "staff_level"
 #define KEY_FLAGS "flags"
 #define KEY_REGISTER_ON "register"
 #define KEY_LAST_SEEN "lastseen"
 #define KEY_INFO "info"
+#define KEY_DEVNULL "devnull"
+#define KEY_WEBSITE "website"
 #define KEY_USERLIST_STYLE "user_style"
 #define KEY_SCREEN_WIDTH "screen_width"
 #define KEY_LAST_AUTHED_HOST "last_authed_host"
 #define KEY_TABLE_WIDTH "table_width"
 #define KEY_MAXLOGINS "maxlogins"
 #define KEY_FAKEHOST "fakehost"
+#define KEY_FAKEIDENT "fakeident"
 #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 KEY_AUTHLOG "authlog"
+#define KEY_AUTHLOG_LOGIN_TIME "login_time"
+#define KEY_AUTHLOG_LOGOUT_TIME "logout_time"
+#define KEY_AUTHLOG_HOSTMASK "hostmask"
+#define KEY_AUTHLOG_QUIT_REASON "quit_reason"
 
-#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) { \
@@ -156,6 +171,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." },
@@ -167,6 +183,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." },
@@ -187,7 +204,10 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_STAMPED_AUTHCOOKIE",  "You have already authenticated to an account once this session; you may not use a cookie to authenticate to another account." },
     { "NSMSG_TITLE_INVALID", "Titles cannot contain any dots; please choose another." },
     { "NSMSG_TITLE_TRUNCATED", "That title combined with the user's account name would result in a truncated host; please choose a shorter title." },
+    { "NSMSG_TITLE_TRUNCATED_RENAME", "That account name combined with the user's title would result in a truncated host; please choose a shorter account name." },
     { "NSMSG_FAKEHOST_INVALID", "Fake hosts must be shorter than %d characters and cannot start with a dot." },
+    { "NSMSG_FAKEIDENT_INVALID", "Fake idents must be shorter than %d characters." },
+    { "NSMSG_FAKEMASK_INVALID", "Fake ident@hosts must be shorter than %d characters." },
     { "NSMSG_HANDLEINFO_ON", "Account information for $b%s$b:" },
     { "NSMSG_HANDLEINFO_ID", "  Account ID: %lu" },
     { "NSMSG_HANDLEINFO_REGGED", "  Registered on: %s" },
@@ -202,9 +222,14 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH", "  Cookie: There is currently an allowauth cookie issued for this account" },
     { "NSMSG_HANDLEINFO_COOKIE_UNKNOWN", "  Cookie: There is currently an unknown cookie issued for this account" },
     { "NSMSG_HANDLEINFO_INFOLINE", "  Infoline: %s" },
+    { "NSMSG_HANDLEINFO_DEVNULL", "  DevNull Class: %s" },
+    { "NSMSG_HANDLEINFO_WEBSITE", "  Website: %s" },
+    { "NSMSG_HANDLEINFO_ACCESS", "  Access: %i" },
     { "NSMSG_HANDLEINFO_FLAGS", "  Flags: %s" },
     { "NSMSG_HANDLEINFO_EPITHET", "  Epithet: %s" },
+    { "NSMSG_HANDLEINFO_FAKEIDENT", "  Fake ident: %s" },
     { "NSMSG_HANDLEINFO_FAKEHOST", "  Fake host: %s" },
+    { "NSMSG_HANDLEINFO_FAKEIDENTHOST", "  Fake host: %s@%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" },
@@ -252,6 +277,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." },
@@ -269,7 +297,7 @@ static const struct message_entry msgtab[] = {
     { "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." },
@@ -290,6 +318,9 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_SETTING_LIST", "$b$N account settings:$b" },
     { "NSMSG_INVALID_OPTION", "$b%s$b is an invalid account setting." },
     { "NSMSG_SET_INFO", "$bINFO:         $b%s" },
+    { "NSMSG_SET_DEVNULL", "$bDEVNULL:      $b%s" },
+    { "NSMSG_SET_AUTOHIDE", "$bAUTOHIDE:     $b%s" },
+    { "NSMSG_SET_WEBSITE", "$bWEBSITE:      $b%s" },
     { "NSMSG_SET_WIDTH", "$bWIDTH:        $b%d" },
     { "NSMSG_SET_TABLEWIDTH", "$bTABLEWIDTH:   $b%d" },
     { "NSMSG_SET_COLOR", "$bCOLOR:        $b%s" },
@@ -301,9 +332,12 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_SET_MAXLOGINS", "$bMAXLOGINS:    $b%d" },
     { "NSMSG_SET_LANGUAGE", "$bLANGUAGE:     $b%s" },
     { "NSMSG_SET_LEVEL", "$bLEVEL:        $b%d" },
+    { "NSMSG_SET_STAFFLEVEL", "$bSTAFF_LEVEL:    $b%d" },
     { "NSMSG_SET_EPITHET", "$bEPITHET:      $b%s" },
     { "NSMSG_SET_TITLE", "$bTITLE:        $b%s" },
     { "NSMSG_SET_FAKEHOST", "$bFAKEHOST:    $b%s" },
+    { "NSMSG_SET_FAKEIDENT", "$bFAKEIDENT:   $b%s" },
+    { "NSMSG_SET_FAKEIDENTHOST", "$bFAKEHOST:    $b%s@%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" },
@@ -319,6 +353,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 }
 };
 
@@ -332,6 +369,11 @@ static void nickserv_reclaim(struct userNode *user, struct nick_info *ni, enum r
 static void nickserv_reclaim_p(void *data);
 static int nickserv_addmask(struct userNode *user, struct handle_info *hi, const char *mask);
 
+enum handle_ts_mode {
+    TS_IGNORE,
+    TS_IRCU
+};
+
 static struct {
     unsigned int disable_nicks : 1;
     unsigned int valid_handle_regex_set : 1;
@@ -355,24 +397,39 @@ static struct {
     unsigned long handle_expire_delay;
     unsigned long nochan_handle_expire_delay;
     unsigned long modoper_level;
+    unsigned long modstaff_level;
     unsigned long set_epithet_level;
     unsigned long set_title_level;
     unsigned long set_fakehost_level;
+    unsigned long set_fakeident_level;
     unsigned long handles_per_email;
     unsigned long email_search_level;
     const char *network_name;
-    const char *titlehost_suffix;
     regex_t valid_handle_regex;
     regex_t valid_nick_regex;
     dict_t weak_password_dict;
     struct policer_params *auth_policer_params;
     enum reclaim_action reclaim_action;
     enum reclaim_action auto_reclaim_action;
+    enum handle_ts_mode handle_ts_mode;
     unsigned long auto_reclaim_delay;
     unsigned char default_maxlogins;
     unsigned char hard_maxlogins;
+    unsigned long ounregister_inactive;
+    unsigned long ounregister_flags;
+    unsigned int max_authlog_len;
 } nickserv_conf;
 
+struct pendingLOCUser {
+    struct handle_info *handle_info;
+    unsigned long time;
+    struct authlogEntry *authlog;
+    struct pendingLOCUser *next;
+};
+
+const char *titlehost_suffix = NULL;
+static struct pendingLOCUser *pendingLOCUsers = NULL;
+
 /* We have 2^32 unique account IDs to use. */
 unsigned long int highest_id = 0;
 
@@ -389,19 +446,18 @@ 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;
 }
 
 static struct handle_info *
-register_handle(const char *handle, const char *passwd, UNUSED_ARG(unsigned long id))
+register_handle(const char *handle, const char *passwd, unsigned long id)
 {
     struct handle_info *hi;
 
-#ifdef WITH_PROTOCOL_BAHAMUT
     char id_base64[IDLEN + 1];
     do
     {
@@ -425,7 +481,6 @@ register_handle(const char *handle, const char *passwd, UNUSED_ARG(unsigned long
             id = 0;
         }
     } while(!id);
-#endif
 
     hi = calloc(1, sizeof(*hi));
     hi->userlist_style = HI_DEFAULT_STYLE;
@@ -434,10 +489,10 @@ register_handle(const char *handle, const char *passwd, UNUSED_ARG(unsigned long
     hi->infoline = NULL;
     dict_insert(nickserv_handle_dict, hi->handle, hi);
 
-#ifdef WITH_PROTOCOL_BAHAMUT
     hi->id = id;
+    hi->website = NULL;
+    hi->devnull = NULL;
     dict_insert(nickserv_id_dict, strdup(id_base64), hi);
-#endif
 
     return hi;
 }
@@ -466,15 +521,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);
 }
@@ -486,13 +541,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;
 }
@@ -510,13 +565,10 @@ static void
 free_handle_info(void *vhi)
 {
     struct handle_info *hi = vhi;
-
-#ifdef WITH_PROTOCOL_BAHAMUT
     char id[IDLEN + 1];
 
     inttobase64(id, hi->id, IDLEN);
     dict_remove(nickserv_id_dict, id);
-#endif
 
     free_string_list(hi->masks);
     assert(!hi->users);
@@ -526,6 +578,9 @@ free_handle_info(void *vhi)
     free(hi->infoline);
     free(hi->epithet);
     free(hi->fakehost);
+    free(hi->devnull);
+    free(hi->website);
+    free(hi->fakeident);
     if (hi->cookie) {
         timeq_del(hi->cookie->expires, nickserv_free_cookie, hi->cookie, 0);
         nickserv_free_cookie(hi->cookie);
@@ -541,6 +596,26 @@ free_handle_info(void *vhi)
         if (!hil->used)
             dict_remove(nickserv_email_dict, hi->email_addr);
     }
+    struct authlogEntry *authlog, *next;
+    for(authlog = hi->authlog; authlog; authlog = next) {
+        next = authlog->next;
+        struct pendingLOCUser *pending, *prev_pending = NULL;
+        for(pending = pendingLOCUsers; pending; pending = pending->next) {
+            if(pending->authlog == authlog) {
+                if(prev_pending)
+                    prev_pending->next = pending->next;
+                else
+                    pendingLOCUsers = pending->next;
+                free(pending);
+                break;
+            }
+            prev_pending = pending;
+        }
+        free((char *) authlog->hostmask);
+        if(authlog->quit_reason)
+            free((char *) authlog->quit_reason);
+        free(authlog);
+    }
     free(hi);
 }
 
@@ -599,21 +674,38 @@ 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;
+}
+
+int
+staff_has_access(struct userNode *user, struct userNode *bot, unsigned int min_level, unsigned int quiet) {
+    if (!user->handle_info) {
+        if (!quiet)
+            send_message(user, bot, "MSG_AUTHENTICATE");
+        return 0;
+    }
+
+    if (user->handle_info->staff_level < min_level) {
+        if (!quiet)
+            send_message(user, bot, "NSMSG_NO_ACCESS");
+        return 0;
     }
 
     return 1;
@@ -629,7 +721,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;
@@ -673,9 +765,51 @@ is_registerable_nick(const char *nick)
 }
 
 static int
-is_valid_email_addr(const char *email)
+is_valid_email_addr(const char *org_email)
 {
-    return strchr(email, '@') != NULL;
+    char email[strlen(org_email)+1];
+    strcpy(email, org_email);
+    //validate email address
+    //1st check: there need to be one @
+    char *p1 = strchr(email, '@');
+    if(!p1 || strchr(p1+1, '@')) return 0;
+    *p1 = '\0';
+    //2nd check: username (bevore @) must be at least 1 char long and out of part_chars
+    char *part_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._%+-";
+    int i;
+    if(p1 - email == 0) return 0;
+    for(i = 0; i < (p1 - email); i++) {
+        if(!strchr(part_chars, email[i])) return 0;
+    }
+    //3rd check: there need to be at least 1 dot in the domain part and all characters out of part_chars
+    part_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
+    char *p2 = NULL;
+    p1++;
+    i = 0;
+    while(*p1) {
+        if(*p1 == '.') {
+            if(!i) return 0;
+            i = 0;
+            p2 = p1;
+        } else if(!strchr(part_chars, *p1))
+            return 0;
+        else
+            i++;
+        p1++;
+    }
+    if(!p2) return 0;
+    //4th check: TLD must be <= 5 chars, no special chars
+    i = 0;
+    p2++;
+    while(*p2) {
+        if(!isalpha(*p2))
+            return 0;
+        else
+            i++;
+        p2++;
+    }
+    if(i > 5) return 0;
+    return 1;
 }
 
 static const char *
@@ -711,9 +845,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;
         }
@@ -749,8 +883,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;
 }
@@ -761,7 +895,7 @@ valid_user_for(struct userNode *user, struct handle_info *hi)
     unsigned int ii;
 
     /* If no hostmasks on the account, allow it. */
-    if (!hi->masks->used)
+    if (!hi->masks->used || IsDummy(user))
         return 1;
     /* If any hostmask matches, allow it. */
     for (ii=0; ii<hi->masks->used; ii++)
@@ -769,8 +903,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;
@@ -801,16 +935,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;
@@ -825,13 +959,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;
 }
@@ -865,23 +999,51 @@ generate_fakehost(struct handle_info *handle)
         return buffer;
     } else if (handle->fakehost[0] == '.') {
         /* A leading dot indicates the stored value is actually a title. */
-        snprintf(buffer, sizeof(buffer), "%s.%s.%s", handle->handle, handle->fakehost+1, nickserv_conf.titlehost_suffix);
+        snprintf(buffer, sizeof(buffer), "%s.%s.%s", handle->handle, handle->fakehost+1, titlehost_suffix);
+        return buffer;
+    } else if (handle->fakehost[0] == '$') {
+        /* A leading $ indicates the stored value begins with the user handle. */
+        snprintf(buffer, sizeof(buffer), "%s%s", handle->handle, handle->fakehost+1);
         return buffer;
     }
     return handle->fakehost;
 }
 
+static char *
+generate_fakeident(struct handle_info *handle, struct userNode *user)
+{
+    static char buffer[USERLEN+1];
+
+    if (!handle->fakeident) {
+        if (!user)
+            return NULL;
+        safestrncpy(buffer, user->ident, sizeof(buffer));
+        return buffer;
+    }
+    return handle->fakeident;
+}
+
 static void
-apply_fakehost(struct handle_info *handle)
+apply_fakehost(struct handle_info *handle, struct userNode *user)
 {
     struct userNode *target;
-    char *fake;
+    char *fakehost, *fakeident;
 
     if (!handle->users)
         return;
-    fake = generate_fakehost(handle);
-    for (target = handle->users; target; target = target->next_authed)
-        assign_fakehost(target, fake, 1);
+
+    fakehost = generate_fakehost(handle);
+
+    if (user) {
+        fakeident = generate_fakeident(handle, user);
+        assign_fakehost(user, fakehost, fakeident, 0, 1);
+        return;
+    }
+
+    for (target = handle->users; target; target = target->next_authed) {
+        fakeident = generate_fakeident(handle, target);
+        assign_fakehost(target, fakehost, fakeident, 0, 1);
+    }
 }
 
 static void
@@ -897,25 +1059,28 @@ 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 if (user->handle_info->users != NULL) {
+            for (other = user->handle_info->users;
+                 other->next_authed != user;
+                 other = other->next_authed) ;
+            other->next_authed = user->next_authed;
+        } else {
+            /* No users authed to the account - can happen if they get
+             * killed for authing. */
+        }
         /* 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);
     }
@@ -923,8 +1088,11 @@ set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
     user->handle_info = hi;
     if (hi && !hi->users && !hi->opserv_level)
         HANDLE_CLEAR_FLAG(hi, HELPING);
-    for (n=0; n<auth_func_used; n++)
+    for (n=0; n<auth_func_used; n++) {
         auth_func_list[n](user, old_info);
+        if (user->dead)
+            return;
+    }
     if (hi) {
         struct nick_info *ni;
 
@@ -934,43 +1102,33 @@ 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)
-            apply_fakehost(hi);
+        if (hi->fakehost || hi->fakeident || old_info)
+            apply_fakehost(hi, user);
 
         if (stamp) {
-#ifdef WITH_PROTOCOL_BAHAMUT
-            /* Stamp users with their account ID. */
-            char id[IDLEN + 1];
-            inttobase64(id, hi->id, IDLEN);
-#elif WITH_PROTOCOL_P10
-            /* Stamp users with their account name. */
-            char *id = hi->handle;
-#else
-            const char *id = "???";
-#endif
             if (!nickserv_conf.disable_nicks) {
-                struct nick_info *ni;
-                for (ni = hi->nicks; ni; ni = ni->next) {
-                    if (!irccasecmp(user->nick, ni->nick)) {
+                struct nick_info *ni2;
+                for (ni2 = hi->nicks; ni2; ni2 = ni2->next) {
+                    if (!irccasecmp(user->nick, ni2->nick)) {
                         user->modes |= FLAGS_REGNICK;
                         break;
                     }
                 }
             }
-            StampUser(user, id);
+            StampUser(user, hi->handle, hi->registered, hi->id);
         }
 
         if ((ni = get_nick_info(user->nick)) && (ni->owner == hi))
             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;
     }
 }
 
@@ -982,8 +1140,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))
@@ -1148,9 +1306,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) {
@@ -1160,7 +1318,7 @@ static NICKSERV_FUNC(cmd_register)
 
     if (IsRegistering(user)) {
         reply("NSMSG_ALREADY_REGISTERING");
-       return 0;
+        return 0;
     }
 
     if (IsStamped(user)) {
@@ -1224,16 +1382,17 @@ static NICKSERV_FUNC(cmd_register)
         return 0;
     /* Add any masks they should get. */
     if (nickserv_conf.default_hostmask) {
-        string_list_append(hi->masks, strdup("*@*"));
+        nickserv_addmask(NULL, hi, strdup("*@*"));
     } else {
-        string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
+        nickserv_addmask(NULL, hi, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
         if (irc_in_addr_is_valid(user->ip) && !irc_pton(&ip, NULL, user->hostname))
-            string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
+            nickserv_addmask(NULL, hi, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
     }
 
     /* If they're the first to register, give them level 1000. */
     if (dict_size(nickserv_handle_dict) == 1) {
         hi->opserv_level = 1000;
+        hi->staff_level = 1000;
         reply("NSMSG_ROOT_HANDLE", argv[1]);
     }
 
@@ -1256,46 +1415,67 @@ static NICKSERV_FUNC(cmd_oregister)
     char *mask;
     struct userNode *settee;
     struct handle_info *hi;
+    const char *pass, *email;
 
     NICKSERV_MIN_PARMS(3);
 
+    pass = argv[2];
+    argv[2] = "****";
+
     if (!is_valid_handle(argv[1])) {
         reply("NSMSG_BAD_HANDLE", argv[1]);
         return 0;
     }
 
-    if (argc < 4) {
+    if (argc < 5 || !nickserv_conf.email_enabled) {
+        email = NULL;
+    } else {
+        const char *str;
+        email = argv[4];
+        if (!is_valid_email_addr(email)) {
+            send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
+            return 0;
+        }
+        if ((str = mail_prohibited_address(email))) {
+            send_message(user, nickserv, "NSMSG_EMAIL_PROHIBITED", email, str);
+            return 0;
+        }
+    }
+
+    if (argc < 4 || !strcmp(argv[3], "*")) {
         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);
         free(mask);
         return 0;
     }
-    if (!(hi = nickserv_register(user, settee, argv[1], argv[2], 0))) {
+    if (!(hi = nickserv_register(user, settee, argv[1], pass, 0))) {
         free(mask);
         return 0;
     }
     if (mask)
         string_list_append(hi->masks, mask);
+    if (email)
+        nickserv_set_email_addr(hi, email);
     return 1;
 }
 
@@ -1306,6 +1486,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)) {
@@ -1318,19 +1499,23 @@ static NICKSERV_FUNC(cmd_handleinfo)
 
     nsmsg_none = handle_find_message(hi, "MSG_NONE");
     reply("NSMSG_HANDLEINFO_ON", hi->handle);
-#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));
+    if ((oper_has_access(user, cmd->parent->bot, 200, 1)) || IsNetworkHelper(user))
+        reply("NSMSG_HANDLEINFO_DEVNULL", (hi->devnull ? hi->devnull : nsmsg_none));
+    if (user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
+        reply("NSMSG_HANDLEINFO_WEBSITE", (hi->website ? hi->website : nsmsg_none));
+    if(hi->opserv_level > 0 && user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
+        reply("NSMSG_HANDLEINFO_ACCESS", hi->opserv_level);
     if (HANDLE_FLAGGED(hi, FROZEN))
         reply("NSMSG_HANDLEINFO_VACATION");
 
@@ -1361,35 +1546,40 @@ static NICKSERV_FUNC(cmd_handleinfo)
         reply(type);
     }
 
-    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 (oper_has_access(user, cmd->parent->bot, 601, 1))
+        reply("NSMSG_HANDLEINFO_ID", hi->id);
+
+    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)
@@ -1398,8 +1588,12 @@ static NICKSERV_FUNC(cmd_handleinfo)
         reply("NSMSG_HANDLEINFO_EPITHET", (hi->epithet ? hi->epithet : nsmsg_none));
     }
 
-    if (hi->fakehost)
-        reply("NSMSG_HANDLEINFO_FAKEHOST", (hi->fakehost ? hi->fakehost : handle_find_message(hi, "MSG_NONE")));
+    if (hi->fakeident && hi->fakehost)
+        reply("NSMSG_HANDLEINFO_FAKEIDENTHOST", hi->fakeident, hi->fakehost);
+    else if (hi->fakeident)
+        reply("NSMSG_HANDLEINFO_FAKEIDENT", hi->fakeident);
+    else if (hi->fakehost)
+        reply("NSMSG_HANDLEINFO_FAKEHOST", hi->fakehost);
 
     if (hi->last_quit_host[0])
         reply("NSMSG_HANDLEINFO_LAST_HOST", hi->last_quit_host);
@@ -1407,28 +1601,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) {
@@ -1452,50 +1646,50 @@ static NICKSERV_FUNC(cmd_handleinfo)
     }
 
     if (hi->channels) {
-       struct userData *channel, *next;
-       char *name;
-
-       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;
+        struct userData *chan, *next;
+        char *name;
+
+        for (chan = hi->channels; chan; chan = next) {
+            next = chan->u_next;
+            name = chan->channel->channel->name;
+            herelen = strlen(name);
+            if (pos + herelen + 7 > ArrayLength(buff)) {
+                next = chan;
                 goto print_chans_buff;
-           }
-            if (IsUserSuspended(channel))
+            }
+            if (IsUserSuspended(chan))
                 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;
-           }
-       }
+            pos += sprintf(buff+pos, "%d:%s ", chan->access, name);
+            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)
@@ -1504,13 +1698,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;
 }
 
@@ -1520,8 +1714,8 @@ 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;
@@ -1570,6 +1764,12 @@ static NICKSERV_FUNC(cmd_rename_handle)
         reply("NSMSG_HANDLE_EXISTS", argv[2]);
         return 0;
     }
+    if (hi->fakehost && hi->fakehost[0] == '.' &&
+        (strlen(argv[2]) + strlen(hi->fakehost+1) +
+         strlen(titlehost_suffix) + 2) > HOSTLEN) {
+        send_message(user, nickserv, "NSMSG_TITLE_TRUNCATED_RENAME");
+        return 0;
+    }
 
     dict_remove2(nickserv_handle_dict, old_handle = hi->handle, 1);
     hi->handle = strdup(argv[2]);
@@ -1580,6 +1780,7 @@ static NICKSERV_FUNC(cmd_rename_handle)
     reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
     global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
     free(old_handle);
+    apply_fakehost(hi, NULL);
     return 1;
 }
 
@@ -1601,6 +1802,50 @@ reg_failpw_func(failpw_func_t func)
     failpw_func_list[failpw_func_used++] = func;
 }
 
+static struct authlogEntry *authlog_add(struct handle_info *hi, struct userNode *user, const char *mask) {
+    if(!hi || (!user && !mask)) return NULL;
+    if(!mask)
+        mask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST);
+    struct authlogEntry *authlog, *next, *prev = NULL;
+    authlog = malloc(sizeof(*authlog));
+    authlog->login_time = now;
+    authlog->logout_time = 0;
+    authlog->hostmask = mask;
+    authlog->quit_reason = NULL;
+    authlog->user = user;
+    authlog->next = hi->authlog;
+    hi->authlog = authlog;
+    unsigned int i = 0;
+    for(authlog = hi->authlog; authlog; authlog = next) {
+        i++;
+        next = authlog->next;
+        if(i > nickserv_conf.max_authlog_len) {
+            struct pendingLOCUser *pending, *prev_pending = NULL;
+            for(pending = pendingLOCUsers; pending; pending = pending->next) {
+                if(pending->authlog == authlog) {
+                    if(prev_pending)
+                        prev_pending->next = pending->next;
+                    else
+                        pendingLOCUsers = pending->next;
+                    free(pending);
+                    break;
+                }
+                prev_pending = pending;
+            }
+            free((char *) authlog->hostmask);
+            if(authlog->quit_reason)
+                free((char *) authlog->quit_reason);
+            if(prev)
+                prev->next = authlog->next;
+            else
+                hi->authlog = authlog->next;
+            free(authlog);
+        } else
+            prev = authlog;
+    }
+    return hi->authlog;
+}
+
 static NICKSERV_FUNC(cmd_auth)
 {
     int pw_arg, used, maxlogins;
@@ -1700,6 +1945,10 @@ static NICKSERV_FUNC(cmd_auth)
             return 1;
         }
     }
+    if (HANDLE_FLAGGED(hi, AUTOHIDE)) {
+        //ok  we have a fakehost set... but we need to set mode +x
+        irc_svsmode(nickserv,user,"+x");
+    }
 
     set_user_handle_info(user, hi, 1);
     if (nickserv_conf.email_required && !hi->email_addr)
@@ -1710,15 +1959,61 @@ static NICKSERV_FUNC(cmd_auth)
         cryptpass(passwd, hi->passwd);
     if (!hi->masks->used) {
         irc_in_addr_t ip;
-        string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
+        nickserv_addmask(NULL, hi, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
         if (irc_in_addr_is_valid(user->ip) && irc_pton(&ip, NULL, user->hostname))
-            string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
+            nickserv_addmask(NULL, hi, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
     }
+    authlog_add(hi, user, NULL);
     argv[pw_arg] = "****";
     reply("NSMSG_AUTH_SUCCESS");
     return 1;
 }
 
+struct handle_info *checklogin(const char *user, const char *pass, const char *numeric, const char *hostmask, const char *ipmask)
+{
+    struct handle_info *hi;
+    unsigned int match = 0, ii = 0;
+    hi = dict_find(nickserv_handle_dict, user, NULL);
+    if(!hi)
+        return NULL;
+    /* If no hostmasks on the account, allow it. */
+    if (hi->masks->used) {
+        /* If any hostmask matches, allow it. */
+        for (ii=0; ii<hi->masks->used; ii++)
+          if (match_ircglob(hostmask, hi->masks->list[ii]) || match_ircglob(ipmask, hi->masks->list[ii])) {
+            match = 1;
+            break;
+          }
+        if(!match) 
+          return NULL;
+    }
+    if(!checkpass(pass, hi->passwd))
+        return NULL;
+    if (HANDLE_FLAGGED(hi, SUSPENDED))
+        return NULL;
+    char *ptr = malloc(strlen(hostmask)+1);
+    strcpy(ptr, hostmask);
+    struct authlogEntry *authlog = authlog_add(hi, NULL, ptr);
+    struct pendingLOCUser *pending;
+    if(authlog && (pending = malloc(sizeof(*pending)))) {
+        pending->handle_info = hi;
+        pending->time = now;
+        pending->authlog = authlog;
+        pending->next = pendingLOCUsers;
+        pendingLOCUsers = pending;
+    }
+    return hi;
+}
+
+char *getfakehost(const char *user)
+{
+    struct handle_info *hi;
+    hi = dict_find(nickserv_handle_dict, user, NULL);
+    if(!hi)
+        return 0;
+    return generate_fakehost(hi);
+}
+
 static allowauth_func_t *allowauth_func_list;
 static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
 
@@ -1737,6 +2032,89 @@ reg_allowauth_func(allowauth_func_t func)
     allowauth_func_list[allowauth_func_used++] = func;
 }
 
+static int cmd_authlog_func(struct userNode *user, struct svccmd *cmd, struct handle_info *hi);
+
+static MODCMD_FUNC(cmd_authlog)
+{
+    return cmd_authlog_func(user, cmd, user->handle_info);
+}
+
+static MODCMD_FUNC(cmd_oauthlog) {
+    struct handle_info *hi;
+    
+    NICKSERV_MIN_PARMS(2);
+    
+    if (!(hi = get_victim_oper(user, argv[1])))
+        return 0;
+    
+    return cmd_authlog_func(user, cmd, hi);
+}
+
+static int cmd_authlog_func(struct userNode *user, struct svccmd *cmd, struct handle_info *hi) {
+    struct helpfile_table tbl;
+    struct authlogEntry *authlog;
+    int i = 0;
+    
+    for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+        i++;
+    }
+    
+    tbl.length = i+1;
+    tbl.width = 4;
+    tbl.flags = 0;
+    tbl.flags = TABLE_NO_FREE;
+    tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
+    tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+    tbl.contents[0][0] = "Hostmask";
+    tbl.contents[0][1] = "Login";
+    tbl.contents[0][2] = "Logout";
+    tbl.contents[0][3] = "Quit Reason";
+    
+    if(!tbl.length) {
+        table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+        reply("MSG_NONE");
+        free(tbl.contents[0]);
+        free(tbl.contents);
+        return 0;
+    }
+    
+    char *str, *ptr;
+    char intervalBuf[INTERVALLEN];
+    i = 0;
+    for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+        tbl.contents[++i] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+        tbl.contents[i][0] = authlog->hostmask;
+        str = intervalString(intervalBuf, now - authlog->login_time, hi);
+        ptr = malloc(strlen(str)+1);
+        strcpy(ptr, str);
+        tbl.contents[i][1] = ptr;
+        if(authlog->logout_time)
+            str = intervalString(intervalBuf, now - authlog->logout_time, hi);
+        else if(!authlog->user)
+            str = "Unknown";
+        else {
+            sprintf(intervalBuf, "Never (%s)", authlog->user->nick);
+            str = intervalBuf;
+        }
+        ptr = malloc(strlen(str)+1);
+        strcpy(ptr, str);
+        tbl.contents[i][2] = ptr;
+        tbl.contents[i][3] = (authlog->quit_reason ? authlog->quit_reason : "-");
+    }
+    
+    table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+    for(i = 1; i < tbl.length; ++i)
+    {
+        free((char *) tbl.contents[i][1]);
+        free((char *) tbl.contents[i][2]);
+        free(tbl.contents[i]);
+    }
+    free(tbl.contents[0]);
+    free(tbl.contents);
+    
+    return 0;
+}
+
 static NICKSERV_FUNC(cmd_allowauth)
 {
     struct userNode *target;
@@ -1842,6 +2220,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;
@@ -1962,8 +2360,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);
@@ -1986,8 +2384,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);
@@ -2007,8 +2405,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] = "****";
@@ -2064,17 +2462,17 @@ nickserv_delmask(struct userNode *user, struct handle_info *hi, const char *del_
 {
     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 && !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;
-       }
+        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;
@@ -2101,26 +2499,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;
@@ -2145,14 +2543,10 @@ nickserv_apply_flags(struct userNode *user, struct handle_info *hi, const char *
         struct channelList *schannels;
         unsigned int ii;
         schannels = chanserv_support_channels();
-        for (uNode = hi->users; uNode; uNode = uNode->next_authed) {
-            for (ii = 0; ii < schannels->used; ++ii)
-                if (GetUserMode(schannels->list[ii], uNode))
-                    break;
-            if (ii < schannels->used)
+        for (ii = 0; ii < schannels->used; ++ii)
+            if (find_handle_in_channel(schannels->list[ii], hi, NULL))
                 break;
-        }
-        if (!uNode)
+        if (ii == schannels->used)
             HANDLE_CLEAR_FLAG(hi, HELPING);
     }
 
@@ -2176,15 +2570,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", "MAXLOGINS", "LANGUAGE"
+        "EMAIL", "MAXLOGINS", "LANGUAGE", "DEVNULL"
     };
 
     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)
@@ -2194,11 +2588,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);
@@ -2217,12 +2611,12 @@ 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;
     }
 
@@ -2238,12 +2632,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");
@@ -2251,10 +2645,86 @@ static OPTION_FUNC(opt_info)
     return 1;
 }
 
+static OPTION_FUNC(opt_devnull)
+{
+    const char *devnull;
+    
+    if (argc > 1) {
+        if (!override) {
+            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+            return 0;
+        }
+        if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
+            free(hi->devnull);
+            hi->devnull = NULL;
+        } else {
+            devnull = unsplit_string(argv+1, argc-1, NULL);
+            if(devnull_check(devnull) == 1) { 
+                if(hi->devnull)
+                    free(hi->devnull);
+                hi->devnull = strdup(devnull);
+            }
+        }
+    }
+
+    devnull = hi->devnull ? hi->devnull : user_find_message(user, "MSG_NONE");
+    send_message(user, nickserv, "NSMSG_SET_DEVNULL", devnull);
+    return 1;
+}
+
+void nickserv_devnull_delete(char *name) {
+    dict_iterator_t it;
+    struct handle_info *hi;
+
+    for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
+        hi = iter_data(it);
+        if (hi->devnull && !irccasecmp(name, hi->devnull)) {
+            free(hi->devnull);
+            hi->devnull = NULL;
+        }
+    }
+}
+
+void nickserv_devnull_rename(char *oldname, char *newname) {
+    dict_iterator_t it;
+    struct handle_info *hi;
+
+    for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
+        hi = iter_data(it);
+        if (hi->devnull && !irccasecmp(oldname, hi->devnull)) {
+            hi->devnull = strdup(newname);
+        }
+    }
+}
+
+static OPTION_FUNC(opt_website)
+{
+    const char *website;
+    
+    if (argc > 1) {
+        if (!HANDLE_FLAGGED(user->handle_info, BOT)) {
+            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+            return 0;
+        }
+        if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
+            free(hi->website);
+            hi->website = NULL;
+        } else {
+            website = unsplit_string(argv+1, argc-1, NULL);
+            hi->website = strdup(website);
+        }
+    }
+    if (HANDLE_FLAGGED(user->handle_info, BOT)) {
+        website = hi->website ? hi->website : user_find_message(user, "MSG_NONE");
+        send_message(user, nickserv, "NSMSG_SET_WEBSITE", website);
+    }
+    return 1;
+}
+
 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;
@@ -2268,7 +2738,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;
@@ -2282,14 +2752,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"));
@@ -2299,38 +2769,55 @@ 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"));
     return 1;
 }
 
+static OPTION_FUNC(opt_autohide)
+{
+    if (argc > 1) {
+        if (enabled_string(argv[1]))
+            HANDLE_SET_FLAG(hi, AUTOHIDE);
+        else if (disabled_string(argv[1]))
+            HANDLE_CLEAR_FLAG(hi, AUTOHIDE);
+        else {
+            send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
+            return 0;
+        }
+    }
+
+    send_message(user, nickserv, "NSMSG_SET_AUTOHIDE", user_find_message(user, HANDLE_FLAGGED(hi, AUTOHIDE) ? "MSG_ON" : "MSG_OFF"));
+    return 1;
+}
+
 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);
@@ -2340,14 +2827,16 @@ static OPTION_FUNC(opt_style)
 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", "***");
+    argv[1] = "****";
+    
     return 1;
 }
 
@@ -2357,12 +2846,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))
@@ -2480,13 +2969,31 @@ oper_try_set_access(struct userNode *user, struct userNode *bot, struct handle_i
     return 1;
 }
 
+int
+oper_try_set_staff_access(struct userNode *user, struct userNode *bot, struct handle_info *target, unsigned int new_level) {
+    if (!oper_has_access(user, bot, nickserv_conf.modstaff_level, 0))
+        return 0;
+    if ((user->handle_info->opserv_level < target->opserv_level)
+        || ((user->handle_info->opserv_level == target->opserv_level)
+            && (user->handle_info->opserv_level < 1000))) {
+        send_message(user, bot, "MSG_USER_OUTRANKED", target->handle);
+        return 0;
+    }
+    if (target->staff_level == new_level)
+        return 0;
+    log_module(NS_LOG, LOG_INFO, "Account %s setting staff level for account %s to %d (from %d).",
+        user->handle_info->handle, target->handle, new_level, target->staff_level);
+    target->staff_level = new_level;
+    return 1;
+}
+
 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;
@@ -2494,6 +3001,20 @@ static OPTION_FUNC(opt_level)
     return res;
 }
 
+static OPTION_FUNC(opt_staff_level)
+{
+    int res;
+
+    if (!override) {
+        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+        return 0;
+    }
+
+    res = (argc > 1) ? oper_try_set_staff_access(user, nickserv, hi, strtoul(argv[1], NULL, 0)) : 0;
+    send_message(user, nickserv, "NSMSG_SET_STAFFLEVEL", hi->staff_level);
+    return res;
+}
+
 static OPTION_FUNC(opt_epithet)
 {
     if (!override) {
@@ -2534,7 +3055,7 @@ static OPTION_FUNC(opt_title)
             return 0;
         }
         if ((strlen(user->handle_info->handle) + strlen(title) +
-             strlen(nickserv_conf.titlehost_suffix) + 2) > HOSTLEN) {
+             strlen(titlehost_suffix) + 2) > HOSTLEN) {
             send_message(user, nickserv, "NSMSG_TITLE_TRUNCATED");
             return 0;
         }
@@ -2547,7 +3068,7 @@ static OPTION_FUNC(opt_title)
             hi->fakehost[0] = '.';
             strcpy(hi->fakehost+1, title);
         }
-        apply_fakehost(hi);
+        apply_fakehost(hi, NULL);
     } else if (hi->fakehost && (hi->fakehost[0] == '.'))
         title = hi->fakehost + 1;
     else
@@ -2560,7 +3081,8 @@ static OPTION_FUNC(opt_title)
 
 static OPTION_FUNC(opt_fakehost)
 {
-    const char *fake;
+    char mask[USERLEN + HOSTLEN + 2];
+    char *host, *ident;
 
     if (!override) {
         send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
@@ -2568,34 +3090,109 @@ static OPTION_FUNC(opt_fakehost)
     }
 
     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakehost_level, 0)) {
-        fake = argv[1];
-        if ((strlen(fake) > HOSTLEN) || (fake[0] == '.')) {
+        if(strlen(argv[1]) >= sizeof(mask)) {
+            send_message(user, nickserv, "NSMSG_FAKEMASK_INVALID", USERLEN + HOSTLEN + 1);
+            return 0;
+        }
+
+        safestrncpy(mask, argv[1], sizeof(mask));
+
+        if ((host = strrchr(mask, '@')) && host != mask) {
+            /* If ident@host was used and the user doesn't have access to set idents, do not change anything. */
+            if (!oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
+                host = NULL;
+                ident = NULL;
+            } else {
+                ident = mask;
+                *host++ = '\0';
+            }
+        } else {
+            ident = NULL;
+            host = mask;
+        }
+
+        if (ident && strlen(ident) > USERLEN) {
+            send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
+            return 0;
+        }
+
+        if (host && ((strlen(host) > HOSTLEN) || (host[0] == '.'))) {
             send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
             return 0;
         }
-        free(hi->fakehost);
-        if (!strcmp(fake, "*"))
-            hi->fakehost = NULL;
+
+        if (host && host[0]) {
+            free(hi->fakehost);
+            if (!strcmp(host, "*"))
+                hi->fakehost = NULL;
+            else
+                hi->fakehost = strdup(host);
+            host = hi->fakehost;
+        }
+        else
+            host = generate_fakehost(hi);
+
+        if (ident) {
+            free(hi->fakeident);
+            if (!strcmp(ident, "*"))
+                hi->fakeident = NULL;
+            else
+                hi->fakeident = strdup(ident);
+            ident = hi->fakeident;
+        }
+        else
+            ident = generate_fakeident(hi, NULL);
+
+        apply_fakehost(hi, NULL);
+    } else {
+        host = generate_fakehost(hi);
+        ident = generate_fakeident(hi, NULL);
+    }
+    if (!host)
+        host = (char *) user_find_message(user, "MSG_NONE");
+    if(ident)
+        send_message(user, nickserv, "NSMSG_SET_FAKEIDENTHOST", ident, host);
+    else
+        send_message(user, nickserv, "NSMSG_SET_FAKEHOST", host);
+    return 1;
+}
+
+static OPTION_FUNC(opt_fakeident)
+{
+    const char *ident;
+
+    if (!override) {
+        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+        return 0;
+    }
+
+    if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakeident_level, 0)) {
+        ident = argv[1];
+        if (strlen(ident) > USERLEN) {
+            send_message(user, nickserv, "NSMSG_FAKEIDENT_INVALID", USERLEN);
+            return 0;
+        }
+        free(hi->fakeident);
+        if (!strcmp(ident, "*"))
+            hi->fakeident = NULL;
         else
-            hi->fakehost = strdup(fake);
-        fake = hi->fakehost;
-        apply_fakehost(hi);
+            hi->fakeident = strdup(ident);
+        ident = hi->fakeident;
+        apply_fakehost(hi, NULL);
     } else
-        fake = generate_fakehost(hi);
-    if (!fake)
-        fake = user_find_message(user, "MSG_NONE");
-    send_message(user, nickserv, "NSMSG_SET_FAKEHOST", fake);
+        ident = generate_fakeident(hi, NULL); /* NULL if no fake ident set */
+    if (!ident)
+        ident = user_find_message(user, "MSG_NONE");
+    send_message(user, nickserv, "NSMSG_SET_FAKEIDENT", ident);
     return 1;
 }
 
 static NICKSERV_FUNC(cmd_reclaim)
 {
-    struct handle_info *hi;
     struct nick_info *ni;
     struct userNode *victim;
 
     NICKSERV_MIN_PARMS(2);
-    hi = user->handle_info;
     ni = dict_find(nickserv_nick_dict, argv[1], 0);
     if (!ni) {
         reply("NSMSG_UNKNOWN_NICK", argv[1]);
@@ -2634,12 +3231,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);
@@ -2652,8 +3249,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;
@@ -2675,8 +3272,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;
     }
 }
@@ -2685,10 +3282,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);
@@ -2761,7 +3374,13 @@ static NICKSERV_FUNC(cmd_addnote)
     hi = get_victim_oper(user, argv[1]);
     if (!hi)
         return 0;
-    duration = ParseInterval(argv[2]);
+    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;
@@ -2821,9 +3440,7 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
 
     for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
         hi = iter_data(it);
-#ifdef WITH_PROTOCOL_BAHAMUT
-        assert(hi->id);
-#endif
+        assert(hi->id != 0);
         saxdb_start_record(ctx, iter_key(it), 0);
         if (hi->cookie) {
             struct handle_cookie *cookie = hi->cookie;
@@ -2867,6 +3484,8 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
         if (hi->fakehost)
             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
+        if (hi->fakeident)
+            saxdb_write_string(ctx, KEY_FAKEIDENT, hi->fakeident);
         if (hi->flags) {
             int ii, flen;
 
@@ -2876,11 +3495,13 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
             flags[flen] = 0;
             saxdb_write_string(ctx, KEY_FLAGS, flags);
         }
-#ifdef WITH_PROTOCOL_BAHAMUT
         saxdb_write_int(ctx, KEY_ID, hi->id);
-#endif
         if (hi->infoline)
             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
+        if (hi->devnull)
+            saxdb_write_string(ctx, KEY_DEVNULL, hi->devnull);
+        if (hi->website)
+            saxdb_write_string(ctx, KEY_WEBSITE, hi->website);
         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);
@@ -2902,6 +3523,8 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
         }
         if (hi->opserv_level)
             saxdb_write_int(ctx, KEY_OPSERV_LEVEL, hi->opserv_level);
+        if (hi->staff_level)
+            saxdb_write_int(ctx, KEY_STAFF_LEVEL, hi->staff_level);
         if (hi->language != lang_C)
             saxdb_write_string(ctx, KEY_LANGUAGE, hi->language->name);
         saxdb_write_string(ctx, KEY_PASSWD, hi->passwd);
@@ -2913,6 +3536,21 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
         flags[0] = hi->userlist_style;
         flags[1] = 0;
         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
+        if(hi->authlog) {
+            saxdb_start_record(ctx, KEY_AUTHLOG, 0);
+            struct authlogEntry *authlog;
+            int i = 0;
+            for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+                saxdb_start_record(ctx, strtab(++i), 0);
+                saxdb_write_int(ctx, KEY_AUTHLOG_LOGIN_TIME, authlog->login_time);
+                saxdb_write_int(ctx, KEY_AUTHLOG_LOGOUT_TIME, authlog->logout_time);
+                saxdb_write_string(ctx, KEY_AUTHLOG_HOSTMASK, authlog->hostmask);
+                if(authlog->quit_reason)
+                    saxdb_write_string(ctx, KEY_AUTHLOG_QUIT_REASON, authlog->quit_reason);
+                saxdb_end_record(ctx);
+            }
+            saxdb_end_record(ctx); //END KEY_AUTHLOG
+        }
         saxdb_end_record(ctx);
     }
     return 0;
@@ -3031,6 +3669,10 @@ static NICKSERV_FUNC(cmd_merge)
     if (hi_from->opserv_level > hi_to->opserv_level)
         hi_to->opserv_level = hi_from->opserv_level;
 
+    /* Do they get a staff level promotion? */
+    if (hi_from->staff_level > hi_to->staff_level)
+        hi_to->staff_level = hi_from->staff_level;
+
     /* What about last seen time? */
     if (hi_from->lastseen > hi_to->lastseen)
         hi_to->lastseen = hi_from->lastseen;
@@ -3044,6 +3686,8 @@ static NICKSERV_FUNC(cmd_merge)
      */
     if (hi_from->fakehost && !hi_to->fakehost)
         hi_to->fakehost = strdup(hi_from->fakehost);
+    if (hi_from->fakeident && !hi_to->fakeident)
+        hi_to->fakeident = strdup(hi_from->fakeident);
 
     /* Notify of success. */
     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
@@ -3056,10 +3700,25 @@ static NICKSERV_FUNC(cmd_merge)
     return 1;
 }
 
+#define NICKSERV_DISCRIM_FIELDS_AUTH     0x01
+#define NICKSERV_DISCRIM_FIELDS_EMAIL    0x02
+#define NICKSERV_DISCRIM_FIELDS_SEEN     0x04
+#define NICKSERV_DISCRIM_FIELDS_ACCESS   0x08
+#define NICKSERV_DISCRIM_FIELDS_FAKEHOST 0x10
+#define NICKSERV_DISCRIM_FIELDS_WEBSITE  0x20
+#define NICKSERV_DISCRIM_FIELDS_DEVNULL  0x40
+
+#define NICKSERV_DISCRIM_FIELD_COUNT     7
+
 struct nickserv_discrim {
+    unsigned int show_fields;
+    struct helpfile_table *output_table;
+    int output_table_pos;
+    unsigned int output_table_free_fields;
+    
     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;
@@ -3067,11 +3726,14 @@ struct nickserv_discrim {
     const char *nickmask;
     const char *hostmask;
     const char *fakehostmask;
+    const char *fakeidentmask;
+    const char *website;
+    const char *devnullclass;
     const char *handlemask;
     const char *emailmask;
 };
 
-typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
+typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi, struct nickserv_discrim *discrim);
 
 struct discrim_apply_info {
     struct nickserv_discrim *discrim;
@@ -3092,8 +3754,8 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
     discrim->max_level = INT_MAX;
     discrim->limit = 50;
     discrim->min_registered = 0;
-    discrim->max_registered = INT_MAX;
-    discrim->lastseen = LONG_MAX;
+    discrim->max_registered = ULONG_MAX;
+    discrim->lastseen = ULONG_MAX;
     discrim->min_karma = INT_MIN;
     discrim->max_karma = INT_MAX;
 
@@ -3106,6 +3768,40 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
             discrim->limit = strtoul(argv[++i], NULL, 0);
         } else if (!irccasecmp(argv[i], "flags")) {
             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
+        } else if (!irccasecmp(argv[i], "fields")) {
+            char *fields = argv[++i];
+            char *delimiter = strstr(fields, ",");
+            while(1) {
+                if(delimiter)
+                    *delimiter = '\0';
+                if(!irccasecmp(fields, "auth"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_AUTH;
+                else if(!irccasecmp(fields, "email"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_EMAIL;
+                else if(!irccasecmp(fields, "seen"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_SEEN;
+                else if(!irccasecmp(fields, "access"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_ACCESS;
+                else if(!irccasecmp(fields, "fakehost"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_FAKEHOST;
+                else if(!irccasecmp(fields, "website") && IsBot(user))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_WEBSITE;
+                else if(!irccasecmp(fields, "devnull"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_DEVNULL;
+                else {
+                    send_message(user, nickserv, "MSG_INVALID_FIELD", fields);
+                    goto fail;
+                }
+                if(delimiter) {
+                    *delimiter = ',';
+                    fields = delimiter+1;
+                    if(*fields) {
+                        delimiter = strstr(fields, ",");
+                        continue;
+                    }
+                }
+                break;
+            }
         } else if (!irccasecmp(argv[i], "registered")) {
             const char *cmp = argv[++i];
             if (cmp[0] == '<') {
@@ -3149,12 +3845,12 @@ 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;
@@ -3166,6 +3862,24 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
             } else {
                 discrim->fakehostmask = argv[i];
             }
+        } else if (!irccasecmp(argv[i], "fakeident")) {
+            if (!irccasecmp(argv[++i], "*")) {
+                discrim->fakeidentmask = 0;
+            } else {
+                discrim->fakeidentmask = argv[i];
+            }
+        } else if (!irccasecmp(argv[i], "website")) {
+            if (!irccasecmp(argv[++i], "*")) {
+                discrim->website = 0;
+            } else {
+                discrim->website = argv[i];
+            }
+        } else if (!irccasecmp(argv[i], "devnull")) {
+            if (!irccasecmp(argv[++i], "*")) {
+                discrim->devnullclass = 0;
+            } else {
+                discrim->devnullclass = argv[i];
+            }
         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
             if (!irccasecmp(argv[++i], "*")) {
                 discrim->handlemask = 0;
@@ -3241,6 +3955,9 @@ nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
         || (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->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask)))
+        || (discrim->website && (!hi->website || !match_ircglob(hi->website, discrim->website)))
+        || (discrim->devnullclass && (!hi->devnull || !match_ircglob(hi->devnull, discrim->devnullclass)))
         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
         || (discrim->min_level > hi->opserv_level)
         || (discrim->max_level < hi->opserv_level)
@@ -3259,8 +3976,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;
     }
@@ -3286,7 +4003,7 @@ nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func ds
          it = next) {
         next = iter_next(it);
         if (nickserv_discrim_match(discrim, iter_data(it))) {
-            dsf(source, iter_data(it));
+            dsf(source, iter_data(it), discrim);
             matched++;
         }
     }
@@ -3294,18 +4011,51 @@ nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func ds
 }
 
 static void
-search_print_func(struct userNode *source, struct handle_info *match)
+search_print_func(struct userNode *source, struct handle_info *match, struct nickserv_discrim *discrim)
 {
-    send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
+    if(discrim->show_fields) {
+        //custom fields
+        if(discrim->output_table) {
+            discrim->output_table->contents[++discrim->output_table_pos] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0]));
+            int i = 0;
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = match->handle;
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = (match->email_addr ? match->email_addr : "*");
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN) {
+                char *seen;
+                char seenBuf[INTERVALLEN];
+                if(match->users) {
+                    seen = "Here";
+                } else if(match->lastseen == 0) {
+                    seen = "Never";
+                } else {
+                    seen = intervalString(seenBuf, now - match->lastseen, source->handle_info);
+                }
+                discrim->output_table_free_fields |= 1 << i;
+                discrim->output_table->contents[discrim->output_table_pos][i++] = strdup(seen);
+            }
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = strtab(match->opserv_level);
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = (match->fakehost ? match->fakehost : "*");
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = (match->website ? match->website : "*");
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = (match->devnull ? match->devnull : "*");
+            
+        }
+    } else
+        send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
 }
 
 static void
-search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
+search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match), UNUSED_ARG(struct nickserv_discrim *discrim))
 {
 }
 
 static void
-search_unregister_func (struct userNode *source, struct handle_info *match)
+search_unregister_func (struct userNode *source, struct handle_info *match, UNUSED_ARG(struct nickserv_discrim *discrim))
 {
     if (oper_has_access(source, nickserv, match->opserv_level, 0))
         nickserv_unregister_handle(match, source);
@@ -3393,12 +4143,58 @@ static NICKSERV_FUNC(cmd_search)
         discrim->limit = INT_MAX;
 
     matches = nickserv_discrim_search(discrim, action, user);
-
+    
+    if(discrim->show_fields) {
+        int width = 0;
+        int ii;
+        for(ii = 0; ii < NICKSERV_DISCRIM_FIELD_COUNT; ii++) {
+            if(discrim->show_fields & (1 << ii)) width++;
+        }
+        discrim->output_table = malloc(sizeof(discrim->output_table[0]));
+        discrim->output_table->length = matches+1;
+        discrim->output_table->width = width;
+        discrim->output_table->flags = TABLE_NO_FREE;
+        discrim->output_table->contents = malloc(discrim->output_table->length * sizeof(discrim->output_table->contents[0]));
+        discrim->output_table->contents[0] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0]));
+        
+        ii = 0;
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH)
+            discrim->output_table->contents[0][ii++] = "Auth";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL)
+            discrim->output_table->contents[0][ii++] = "EMail";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN)
+            discrim->output_table->contents[0][ii++] = "Seen";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS)
+            discrim->output_table->contents[0][ii++] = "Access";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST)
+            discrim->output_table->contents[0][ii++] = "Fakehost";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE)
+            discrim->output_table->contents[0][ii++] = "Website";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL)
+            discrim->output_table->contents[0][ii++] = "DevNull";
+        
+        nickserv_discrim_search(discrim, action, user);
+        
+        table_send(nickserv, user->nick, 0, NULL, *discrim->output_table);
+        
+        for(ii = 1; ii < discrim->output_table->length; ++ii) {
+            int ij;
+            for(ij = 0; ij < NICKSERV_DISCRIM_FIELD_COUNT; ij++) {
+                if(discrim->output_table_free_fields & (1 << ij))
+                    free((char*)discrim->output_table->contents[ii][ij]);
+            }
+            free(discrim->output_table->contents[ii]);
+        }
+        free(discrim->output_table->contents[0]);
+        free(discrim->output_table->contents);
+        free(discrim->output_table);
+    }
     if (matches)
         reply("MSG_MATCH_COUNT", matches);
     else
         reply("MSG_NO_MATCHES");
 
+    
     free(discrim);
     return 0;
 }
@@ -3420,6 +4216,63 @@ 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]))) {
+        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 int
+nickserv_db_read_authlog(UNUSED_ARG(const char *key), void *data, void *extra)
+{
+    struct record_data *rd = data;
+    struct handle_info *hi = extra;
+    const char *str;
+    struct authlogEntry *authlog;
+    authlog = malloc(sizeof(*authlog));
+    
+    str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGIN_TIME, RECDB_QSTRING);
+    authlog->login_time = str ? strtoul(str, NULL, 0) : 0;
+    
+    str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGOUT_TIME, RECDB_QSTRING);
+    authlog->logout_time = str ? strtoul(str, NULL, 0) : 0;
+    
+    str = database_get_data(rd->d.object, KEY_AUTHLOG_HOSTMASK, RECDB_QSTRING);
+    authlog->hostmask = str ? strdup(str) : NULL;
+    
+    str = database_get_data(rd->d.object, KEY_AUTHLOG_QUIT_REASON, RECDB_QSTRING);
+    authlog->quit_reason = str ? strdup(str) : NULL;
+    
+    authlog->user = NULL;
+    
+    authlog->next = NULL;
+    
+    //append it to the end of the list...
+    struct authlogEntry *authlog_entry;
+    if(!hi->authlog) {
+        hi->authlog = authlog;
+    } else {
+        for(authlog_entry = hi->authlog; authlog_entry; authlog_entry = authlog_entry->next) {
+            if(!authlog_entry->next) {
+                authlog_entry->next = authlog;
+                break;
+            }
+        }
+    }
+    return 0;
+}
+
 static void
 nickserv_db_read_handle(const char *handle, dict_t obj)
 {
@@ -3427,8 +4280,8 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     struct string_list *masks, *slist;
     struct handle_info *hi;
     struct userNode *authed_users;
-    struct userData *channels;
-    unsigned long int id;
+    struct userData *channel_list;
+    unsigned long id;
     unsigned int ii;
     dict_t subdb;
 
@@ -3441,13 +4294,13 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     }
     if ((hi = get_handle_info(handle))) {
         authed_users = hi->users;
-        channels = hi->channels;
+        channel_list = hi->channels;
         hi->users = NULL;
         hi->channels = NULL;
         dict_remove(nickserv_handle_dict, hi->handle);
     } else {
         authed_users = NULL;
-        channels = NULL;
+        channel_list = NULL;
     }
     hi = register_handle(handle, str, id);
     if (authed_users) {
@@ -3457,7 +4310,7 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
             authed_users = authed_users->next_authed;
         }
     }
-    hi->channels = channels;
+    hi->channels = channel_list;
     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
@@ -3466,13 +4319,21 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     hi->language = language_find(str ? str : "C");
     str = database_get_data(obj, KEY_OPSERV_LEVEL, RECDB_QSTRING);
     hi->opserv_level = str ? strtoul(str, NULL, 0) : 0;
+    str = database_get_data(obj, KEY_STAFF_LEVEL, RECDB_QSTRING);
+    hi->staff_level = str ? strtoul(str, NULL, 0) : 0;
     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
     if (str)
         hi->infoline = strdup(str);
+    str = database_get_data(obj, KEY_WEBSITE, RECDB_QSTRING);
+    if (str)
+        hi->website = strdup(str);
+    str = database_get_data(obj, KEY_DEVNULL, RECDB_QSTRING);
+    if (str)
+        hi->devnull = 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
@@ -3507,6 +4368,9 @@ 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);
+    str = database_get_data(obj, KEY_FAKEIDENT, RECDB_QSTRING);
+    if (str)
+        hi->fakeident = strdup(str);
     /* Read the "cookie" sub-database (if it exists). */
     subdb = database_get_data(obj, KEY_COOKIE, RECDB_OBJECT);
     if (subdb) {
@@ -3560,13 +4424,13 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
             const char *setter;
             const char *text;
             const char *set;
-            const char *id;
+            const char *note_id;
             dict_t notedb;
 
-            id = iter_key(it);
+            note_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);
+                log_module(NS_LOG, LOG_ERROR, "Malformed note %s for account %s; ignoring note.", note_id, hi->handle);
                 continue;
             }
             expires = database_get_data(notedb, KEY_NOTE_EXPIRES, RECDB_QSTRING);
@@ -3574,14 +4438,14 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
             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);
+                log_module(NS_LOG, LOG_ERROR, "Missing field(s) from note %s for account %s; ignoring note.", 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);
+            note->id = strtoul(note_id, NULL, 10);
             safestrncpy(note->setter, setter, sizeof(note->setter));
             strcpy(note->note, text);
             if (last_note)
@@ -3591,6 +4455,8 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
             last_note = note;
         }
     }
+    if ((subdb = database_get_data(obj, KEY_AUTHLOG, RECDB_OBJECT)))
+        dict_foreach(subdb, nickserv_db_read_authlog, hi);
 }
 
 static int
@@ -3622,10 +4488,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;
 }
 
@@ -3633,7 +4499,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) {
@@ -3665,8 +4531,7 @@ nickserv_load_dict(const char *fname)
         log_module(NS_LOG, LOG_ERROR, "Unable to open dictionary file %s: %s", fname, strerror(errno));
         return;
     }
-    while (!feof(file)) {
-        fgets(line, sizeof(line), file);
+    while (fgets(line, sizeof(line), file)) {
         if (!line[0])
             continue;
         if (line[strlen(line)-1] == '\n')
@@ -3699,8 +4564,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)
@@ -3744,12 +4609,16 @@ nickserv_conf_read(void)
     nickserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
     str = database_get_data(conf_node, KEY_MODOPER_LEVEL, RECDB_QSTRING);
     nickserv_conf.modoper_level = str ? strtoul(str, NULL, 0) : 900;
+    str = database_get_data(conf_node, KEY_MODSTAFF_LEVEL, RECDB_QSTRING);
+    nickserv_conf.modstaff_level = str ? strtoul(str, NULL, 0) : 800;
     str = database_get_data(conf_node, KEY_SET_EPITHET_LEVEL, RECDB_QSTRING);
     nickserv_conf.set_epithet_level = str ? strtoul(str, NULL, 0) : 1;
     str = database_get_data(conf_node, KEY_SET_TITLE_LEVEL, RECDB_QSTRING);
     nickserv_conf.set_title_level = str ? strtoul(str, NULL, 0) : 900;
     str = database_get_data(conf_node, KEY_SET_FAKEHOST_LEVEL, RECDB_QSTRING);
     nickserv_conf.set_fakehost_level = str ? strtoul(str, NULL, 0) : 1000;
+    str = database_get_data(conf_node, KEY_SET_FAKEIDENT_LEVEL, RECDB_QSTRING);
+    nickserv_conf.set_fakeident_level = str ? strtoul(str, NULL, 0) : 1000;
     str = database_get_data(conf_node, KEY_HANDLE_EXPIRE_FREQ, RECDB_QSTRING);
     if (!str)
         str = database_get_data(conf_node, KEY_ACCOUNT_EXPIRE_FREQ, RECDB_QSTRING);
@@ -3768,6 +4637,27 @@ 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_MAX_AUTHLOG_LEN, RECDB_QSTRING);
+    nickserv_conf.max_authlog_len = str ? strtoul(str, NULL, 0) : 30;
+    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);
+    }
+    str = database_get_data(conf_node, KEY_HANDLE_TS_MODE, RECDB_QSTRING);
+    if (!str)
+        nickserv_conf.handle_ts_mode = TS_IGNORE;
+    else if (!irccasecmp(str, "ircu"))
+        nickserv_conf.handle_ts_mode = TS_IRCU;
+    else
+        nickserv_conf.handle_ts_mode = TS_IGNORE;
     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;
@@ -3825,7 +4715,7 @@ nickserv_conf_read(void)
     str = database_get_data(conf_node, KEY_EMAIL_SEARCH_LEVEL, RECDB_QSTRING);
     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
-    nickserv_conf.titlehost_suffix = str ? str : "example.net";
+    titlehost_suffix = str ? str : "example.net";
     str = conf_get_data("server/network", RECDB_QSTRING);
     nickserv_conf.network_name = str ? str : "some IRC network";
     if (!nickserv_conf.auth_policer_params) {
@@ -3873,52 +4763,74 @@ nickserv_reclaim_p(void *data) {
         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
 }
 
-static int
+static void
 check_user_nick(struct userNode *user) {
+    //check if this user is a pending LOC user
+    if(pendingLOCUsers) {
+        struct pendingLOCUser *pending, *next, *prev = NULL;
+        int remove;
+        for(pending = pendingLOCUsers; pending; pending = next) {
+            next = pending->next;
+            remove = 0;
+            if(user->handle_info == pending->handle_info) {
+                pending->authlog->user = user;
+                free((char*) pending->authlog->hostmask);
+                pending->authlog->hostmask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST);
+                remove = 1;
+            } else if(now - pending->time > 10)
+                remove = 1;
+            if(remove) {
+                if(prev)
+                    prev->next = next;
+                else
+                    pendingLOCUsers = next;
+                free(pending);
+            }
+        }
+    }
     struct nick_info *ni;
     user->modes &= ~FLAGS_REGNICK;
     if (!(ni = get_nick_info(user->nick)))
-        return 0;
+        return;
     if (user->handle_info == ni->owner) {
         user->modes |= FLAGS_REGNICK;
         irc_regnick(user);
-        return 0;
+        return;
     }
     if (nickserv_conf.warn_nick_owned)
         send_message(user, nickserv, "NSMSG_RECLAIM_WARN", ni->nick, ni->owner->handle);
     if (nickserv_conf.auto_reclaim_action == RECLAIM_NONE)
-        return 0;
+        return;
     if (nickserv_conf.auto_reclaim_delay)
         timeq_add(now + nickserv_conf.auto_reclaim_delay, nickserv_reclaim_p, user);
     else
         nickserv_reclaim(user, ni, nickserv_conf.auto_reclaim_action);
-    return 0;
-}
-
-int
-handle_new_user(struct userNode *user)
-{
-    return check_user_nick(user);
 }
 
 void
-handle_account(struct userNode *user, const char *stamp)
+handle_account(struct userNode *user, const char *stamp, unsigned long timestamp, unsigned long serial)
 {
-    struct handle_info *hi;
+    struct handle_info *hi = NULL;
 
-#ifdef WITH_PROTOCOL_P10
-    hi = dict_find(nickserv_handle_dict, stamp, NULL);
-#else
-    hi = dict_find(nickserv_id_dict, stamp, NULL);
-#endif
+    if (stamp != NULL)
+        hi = dict_find(nickserv_handle_dict, stamp, NULL);
+    if ((hi == NULL) && (serial != 0)) {
+        char id[IDLEN + 1];
+        inttobase64(id, serial, IDLEN);
+        hi = dict_find(nickserv_id_dict, id, NULL);
+    }
 
     if (hi) {
+        if ((nickserv_conf.handle_ts_mode == TS_IRCU)
+            && (timestamp != hi->registered)) {
+            return;
+        }
         if (HANDLE_FLAGGED(hi, SUSPENDED)) {
             return;
         }
         set_user_handle_info(user, hi, 0);
     } else {
-        log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s.", user->nick, stamp);
+        log_module(MAIN_LOG, LOG_WARNING, "%s had unknown account stamp %s:%lu:%lu.", user->nick, stamp, timestamp, serial);
     }
 }
 
@@ -3936,8 +4848,20 @@ handle_nick_change(struct userNode *user, const char *old_nick)
 }
 
 void
-nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
+nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), const char *why)
 {
+    if(user->handle_info) {
+        //check if theres an open authlog entry
+        struct authlogEntry *authlog;
+        for(authlog = user->handle_info->authlog; authlog; authlog = authlog->next) {
+            if(authlog->user == user) {
+                authlog->user = NULL;
+                authlog->logout_time = now;
+                authlog->quit_reason = strdup(why);
+                break;
+            }
+        }
+    }
     dict_remove(nickserv_allow_auth_dict, user->nick);
     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
     set_user_handle_info(user, NULL, 0);
@@ -3992,6 +4916,12 @@ nickserv_db_cleanup(void)
         regfree(&nickserv_conf.valid_handle_regex);
     if (nickserv_conf.valid_nick_regex_set)
         regfree(&nickserv_conf.valid_nick_regex);
+    struct pendingLOCUser *pending, *next;
+    for(pending = pendingLOCUsers; pending; pending = next) {
+        next = pending->next;
+        free(pending);
+    }
+    pendingLOCUsers = NULL;
 }
 
 void
@@ -3999,7 +4929,7 @@ init_nickserv(const char *nick)
 {
     unsigned int i;
     NS_LOG = log_register_type("NickServ", "file:nickserv.log");
-    reg_new_user_func(handle_new_user);
+    reg_new_user_func(check_user_nick);
     reg_nick_change_func(handle_nick_change);
     reg_del_user_func(nickserv_remove_user);
     reg_account_func(handle_account);
@@ -4040,12 +4970,12 @@ init_nickserv(const char *nick)
     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) {
@@ -4053,6 +4983,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);
@@ -4062,6 +4993,9 @@ 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);
+    nickserv_define_func("AUTHLOG", cmd_authlog, -1, 1, 0);
+    nickserv_define_func("OAUTHLOG", cmd_oauthlog, 0, 1, 0);
     /* other options */
     dict_insert(nickserv_opt_dict, "INFO", opt_info);
     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
@@ -4069,15 +5003,21 @@ init_nickserv(const char *nick)
     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
+    dict_insert(nickserv_opt_dict, "AUTOHIDE", opt_autohide);
     dict_insert(nickserv_opt_dict, "PASS", opt_password);
     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
+    dict_insert(nickserv_opt_dict, "WEBSITE", opt_website);
+    dict_insert(nickserv_opt_dict, "DEVNULL", opt_devnull);
     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
+    dict_insert(nickserv_opt_dict, "STAFF", opt_staff_level);
+    dict_insert(nickserv_opt_dict, "STAFF_LEVEL", opt_staff_level);
     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
-    if (nickserv_conf.titlehost_suffix) {
+    if (titlehost_suffix) {
         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
+        dict_insert(nickserv_opt_dict, "FAKEIDENT", opt_fakeident);
     }
     dict_insert(nickserv_opt_dict, "MAXLOGINS", opt_maxlogins);
     dict_insert(nickserv_opt_dict, "LANGUAGE", opt_language);