Use nickserv_addmask when adding a new mask to an account (on auth/register). This...
[srvx.git] / src / nickserv.c
index 4ba42507876a34630242cff2f97f1ff6a6734a12..5da64a780a3291a57df60ac1684492d401cdaee7 100644 (file)
@@ -48,6 +48,7 @@
 #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_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_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-_"
 
@@ -212,6 +222,9 @@ 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" },
@@ -305,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" },
@@ -316,6 +332,7 @@ 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" },
@@ -380,6 +397,7 @@ 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;
@@ -399,9 +417,18 @@ static struct {
     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;
@@ -463,6 +490,8 @@ register_handle(const char *handle, const char *passwd, unsigned long id)
     dict_insert(nickserv_handle_dict, hi->handle, hi);
 
     hi->id = id;
+    hi->website = NULL;
+    hi->devnull = NULL;
     dict_insert(nickserv_id_dict, strdup(id_base64), hi);
 
     return hi;
@@ -549,6 +578,8 @@ 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);
@@ -565,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);
 }
 
@@ -643,6 +694,23 @@ oper_has_access(struct userNode *user, struct userNode *bot, unsigned int min_le
     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;
+}
+
 static int
 is_valid_handle(const char *handle)
 {
@@ -697,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 *
@@ -785,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++)
@@ -891,6 +1001,10 @@ generate_fakehost(struct handle_info *handle)
         /* A leading dot indicates the stored value is actually a title. */
         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;
 }
@@ -1268,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]);
     }
 
@@ -1395,6 +1510,12 @@ static NICKSERV_FUNC(cmd_handleinfo)
     }
 
     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");
 
@@ -1659,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;
 }
 
@@ -1680,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;
@@ -1779,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)
@@ -1789,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;
 
@@ -1816,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;
@@ -2271,7 +2570,7 @@ 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");
@@ -2346,6 +2645,82 @@ 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)
@@ -2408,6 +2783,23 @@ static OPTION_FUNC(opt_privmsg)
     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;
@@ -2443,6 +2835,8 @@ static OPTION_FUNC(opt_password)
         cryptpass(argv[1], hi->passwd);
 
     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
+    argv[1] = "****";
+    
     return 1;
 }
 
@@ -2575,6 +2969,24 @@ 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;
@@ -2589,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) {
@@ -2763,12 +3189,10 @@ static OPTION_FUNC(opt_fakeident)
 
 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]);
@@ -3074,6 +3498,10 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
         saxdb_write_int(ctx, KEY_ID, hi->id);
         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);
@@ -3095,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);
@@ -3106,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;
@@ -3224,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;
@@ -3251,7 +3700,22 @@ 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;
     unsigned long min_registered, max_registered;
     unsigned long lastseen;
@@ -3263,11 +3727,13 @@ struct nickserv_discrim {
     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;
@@ -3302,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] == '<') {
@@ -3368,6 +3868,18 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
             } 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;
@@ -3444,6 +3956,8 @@ nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
         || (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)
@@ -3489,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++;
         }
     }
@@ -3497,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);
@@ -3596,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;
 }
@@ -3640,6 +4233,45 @@ static MODCMD_FUNC(cmd_checkemail)
     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)
@@ -3687,9 +4319,17 @@ 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 ? strtoul(str, NULL, 0) : now;
     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
@@ -3815,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
@@ -3967,6 +4609,8 @@ 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);
@@ -3993,6 +4637,8 @@ 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);
@@ -4119,6 +4765,29 @@ nickserv_reclaim_p(void *data) {
 
 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)))
@@ -4179,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);
@@ -4235,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
@@ -4307,6 +4994,8 @@ init_nickserv(const char *nick)
     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);
@@ -4314,11 +5003,16 @@ 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 (titlehost_suffix) {
         dict_insert(nickserv_opt_dict, "TITLE", opt_title);