mux2.4/game/data/
mux2.4/src/tools/
// netcommon.cpp
//
// $Id: netcommon.cpp,v 1.51 2005/10/11 05:27:40 rmg Exp $
//
// This file contains routines used by the networking code that do not
// depend on the implementation of the networking code.  The network-specific
// portions of the descriptor data structure are not used.
//

#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

#include <time.h>

#include "ansi.h"
#include "attrs.h"
#include "command.h"
#include "comsys.h"
#include "file_c.h"
#include "functions.h"
#include "mguests.h"
#include "powers.h"
#include "svdreport.h"
#ifdef REALITY_LVLS
#include "levels.h"
#endif /* REALITY_LVLS */

extern void handle_prog(DESC *, char *);

#ifdef WIN32
extern HANDLE CompletionPort;    // IOs are queued up on this port
extern OVERLAPPED lpo_aborted; // special to indicate a player has finished TCP IOs
extern OVERLAPPED lpo_aborted_final; // Actually free the descriptor.
extern OVERLAPPED lpo_shutdown; // special to indicate a player should do a shutdown
#endif

/* ---------------------------------------------------------------------------
 * make_portlist: Make a list of ports for PORTS().
 */

void make_portlist(dbref player, dbref target, char *buff, char **bufc)
{
    ITL itl;
    ItemToList_Init(&itl, buff, bufc);

    DESC *d;
    DESC_ITER_CONN(d)
    {
        if (  d->player == target
           && !ItemToList_AddInteger(&itl, d->descriptor))
        {
            break;
        }
    }
    ItemToList_Final(&itl);
}

// ---------------------------------------------------------------------------
// make_port_ulist: Make a list of connected user numbers for the LPORTS function.
// ---------------------------------------------------------------------------

void make_port_ulist(dbref player, char *buff, char **bufc)
{
    DESC *d;
    ITL itl;
    char *tmp = alloc_sbuf("make_port_ulist");
    ItemToList_Init(&itl, buff, bufc, '#');
    DESC_ITER_CONN(d)
    {
        if (  !See_Hidden(player)
           && Hidden(d->player))
        {
            continue;
        }

        // printf format: printf("%d:%d", d->player, d->descriptor);
        //
        char *p = tmp;
        p += mux_ltoa(d->player, p);
        *p++ = ':';
        p += mux_ltoa(d->descriptor, p);

        size_t n = p - tmp;
        if (!ItemToList_AddStringLEN(&itl, n, tmp))
        {
            break;
        }
    }
    ItemToList_Final(&itl);
    free_sbuf(tmp);
}

/* ---------------------------------------------------------------------------
 * update_quotas: Update timeslice quotas
 */

void update_quotas(CLinearTimeAbsolute& ltaLast, const CLinearTimeAbsolute& ltaCurrent)
{
    if (ltaCurrent < ltaLast)
    {
        ltaLast = ltaCurrent;
        return;
    }

    CLinearTimeDelta ltdDiff = ltaCurrent - ltaLast;
    if (ltdDiff < mudconf.timeslice)
    {
        return;
    }

    int nSlices = ltdDiff / mudconf.timeslice;
    int nExtraQuota = mudconf.cmd_quota_incr * nSlices;

    if (nExtraQuota > 0)
    {
        DESC *d;
        DESC_ITER_ALL(d)
        {
            d->quota += nExtraQuota;
            if (d->quota > mudconf.cmd_quota_max)
            {
                d->quota = mudconf.cmd_quota_max;
            }
        }
    }
    ltaLast += mudconf.timeslice * nSlices;
}

/* raw_notify_html() -- raw_notify() without the newline */
void raw_notify_html(dbref player, const char *msg)
{
    if (!msg || !*msg)
    {
        return;
    }

    if (  mudstate.inpipe
       && player == mudstate.poutobj)
    {
        safe_str(msg, mudstate.poutnew, &mudstate.poutbufc);
        return;
    }
    if (  !Connected(player)
       || !Html(player))
    {
        return;
    }

    DESC *d;
    DESC_ITER_PLAYER(player, d)
    {
        queue_string(d, msg);
    }
}

/* ---------------------------------------------------------------------------
 * raw_notify: write a message to a player
 */

void raw_notify(dbref player, const char *msg)
{
    DESC *d;

    if (!msg || !*msg)
    {
        return;
    }

    if (  mudstate.inpipe
       && player == mudstate.poutobj)
    {
        safe_str(msg, mudstate.poutnew, &mudstate.poutbufc);
        safe_str("\r\n", mudstate.poutnew, &mudstate.poutbufc);
        return;
    }

    if (!Connected(player))
    {
        return;
    }

    DESC_ITER_PLAYER(player, d)
    {
        queue_string(d, msg);
        queue_write_LEN(d, "\r\n", 2);
    }
}

void raw_notify_newline(dbref player)
{
    if (  mudstate.inpipe
       && player == mudstate.poutobj)
    {
        safe_str("\r\n", mudstate.poutnew, &mudstate.poutbufc);
        return;
    }
    if (!Connected(player))
    {
        return;
    }

    DESC *d;
    DESC_ITER_PLAYER(player, d)
    {
        queue_write_LEN(d, "\r\n", 2);
    }
}

/* ---------------------------------------------------------------------------
 * raw_broadcast: Send message to players who have indicated flags
 */

void DCL_CDECL raw_broadcast(int inflags, char *fmt, ...)
{
    if (!fmt || !*fmt)
    {
        return;
    }

    char buff[LBUF_SIZE];

    va_list ap;
    va_start(ap, fmt);
    mux_vsnprintf(buff, LBUF_SIZE, fmt, ap);
    va_end(ap);

    DESC *d;
    DESC_ITER_CONN(d)
    {
        if ((Flags(d->player) & inflags) == inflags)
        {
            queue_string(d, buff);
            queue_write_LEN(d, "\r\n", 2);
            process_output(d, false);
        }
    }
}

/* ---------------------------------------------------------------------------
 * clearstrings: clear out prefix and suffix strings
 */

void clearstrings(DESC *d)
{
    if (d->output_prefix)
    {
        free_lbuf(d->output_prefix);
        d->output_prefix = NULL;
    }
    if (d->output_suffix)
    {
        free_lbuf(d->output_suffix);
        d->output_suffix = NULL;
    }
}

void add_to_output_queue(DESC *d, const char *b, int n)
{
    TBLOCK *tp;
    int left;

    // Allocate an output buffer if needed.
    //
    if (d->output_head == NULL)
    {
        tp = (TBLOCK *)MEMALLOC(OUTPUT_BLOCK_SIZE);
        ISOUTOFMEMORY(tp);
        tp->hdr.nxt = NULL;
        tp->hdr.start = tp->data;
        tp->hdr.end = tp->data;
        tp->hdr.nchars = 0;
        d->output_head = tp;
        d->output_tail = tp;
    }
    else
    {
        tp = d->output_tail;
    }

    // Now tp points to the last buffer in the chain.
    //
    do
    {
        // See if there is enough space in the buffer to hold the
        // string.  If so, copy it and update the pointers..
        //
        left = OUTPUT_BLOCK_SIZE - (tp->hdr.end - (char *)tp + 1);
        if (n <= left)
        {
            memcpy(tp->hdr.end, b, n);
            tp->hdr.end += n;
            tp->hdr.nchars += n;
            n = 0;
        }
        else
        {
            // It didn't fit.  Copy what will fit and then allocate
            // another buffer and retry.
            //
            if (left > 0)
            {
                memcpy(tp->hdr.end, b, left);
                tp->hdr.end += left;
                tp->hdr.nchars += left;
                b += left;
                n -= left;
            }
            tp = (TBLOCK *)MEMALLOC(OUTPUT_BLOCK_SIZE);
            ISOUTOFMEMORY(tp);
            tp->hdr.nxt = NULL;
            tp->hdr.start = tp->data;
            tp->hdr.end = tp->data;
            tp->hdr.nchars = 0;
            d->output_tail->hdr.nxt = tp;
            d->output_tail = tp;
        }
    } while (n > 0);
}

/* ---------------------------------------------------------------------------
 * queue_write: Add text to the output queue for the indicated descriptor.
 */

void queue_write_LEN(DESC *d, const char *b, int n)
{
    if (n <= 0)
    {
        return;
    }

    if (d->output_size + n > mudconf.output_limit)
    {
        process_output(d, false);
    }

    int left = mudconf.output_limit - d->output_size - n;
    if (left < 0)
    {
        TBLOCK *tp = d->output_head;
        if (tp == NULL)
        {
            STARTLOG(LOG_PROBLEMS, "QUE", "WRITE");
            log_text("Flushing when output_head is null!");
            ENDLOG;
        }
        else
        {
            STARTLOG(LOG_NET, "NET", "WRITE");
            char *buf = alloc_lbuf("queue_write.LOG");
            sprintf(buf, "[%d/%s] Output buffer overflow, %d chars discarded by ", d->descriptor, d->addr, tp->hdr.nchars);
            log_text(buf);
            free_lbuf(buf);
            if (d->flags & DS_CONNECTED)
            {
                log_name(d->player);
            }
            ENDLOG;
            d->output_size -= tp->hdr.nchars;
            d->output_head = tp->hdr.nxt;
            d->output_lost += tp->hdr.nchars;
            if (d->output_head == NULL)
            {
                d->output_tail = NULL;
            }
            MEMFREE(tp);
            tp = NULL;
        }
    }

    add_to_output_queue(d, b, n);
    d->output_size += n;
    d->output_tot += n;

#ifdef WIN32
    if (  platform == VER_PLATFORM_WIN32_NT
       && !d->bWritePending
       && !d->bConnectionDropped)
    {
        d->bCallProcessOutputLater = true;
    }
#endif
}

void queue_write(DESC *d, const char *b)
{
    queue_write_LEN(d, b, strlen(b));
}

void queue_string(DESC *d, const char *s)
{
    const char *p = s;

    if (d->flags & DS_CONNECTED)
    {
        if (  !Ansi(d->player)
           && strchr(s, ESC_CHAR))
        {
            p = strip_ansi(p);
        }
        else if (NoBleed(d->player))
        {
            p = normal_to_white(p);
        }

        if (NoAccents(d->player))
        {
            p = strip_accents(p);
        }
    }
    else
    {
        if (strchr(s, ESC_CHAR))
        {
            p = strip_ansi(p);
        }
        p = strip_accents(p);
    }
    queue_write(d, p);
}

void freeqs(DESC *d)
{
    TBLOCK *tb, *tnext;
    CBLK *cb, *cnext;

    tb = d->output_head;
    while (tb)
    {
        tnext = tb->hdr.nxt;
        MEMFREE(tb);
        tb = tnext;
    }
    d->output_head = NULL;
    d->output_tail = NULL;

    cb = d->input_head;
    while (cb)
    {
        cnext = (CBLK *) cb->hdr.nxt;
        free_lbuf(cb);
        cb = cnext;
    }

    d->input_head = NULL;
    d->input_tail = NULL;

    if (d->raw_input)
        free_lbuf(d->raw_input);
    d->raw_input = NULL;
    d->raw_input_at = NULL;
}

/* ---------------------------------------------------------------------------
 * desc_addhash: Add a net descriptor to its player hash list.
 */

void desc_addhash(DESC *d)
{
    dbref player = d->player;
    DESC *hdesc = (DESC *)hashfindLEN(&player, sizeof(player), &mudstate.desc_htab);
    if (hdesc == NULL)
    {
        d->hashnext = NULL;
        hashaddLEN(&player, sizeof(player), d, &mudstate.desc_htab);
    }
    else
    {
        d->hashnext = hdesc;
        hashreplLEN(&player, sizeof(player), d, &mudstate.desc_htab);
    }
}

/* ---------------------------------------------------------------------------
 * desc_delhash: Remove a net descriptor from its player hash list.
 */

static void desc_delhash(DESC *d)
{
    dbref player = d->player;
    DESC *last = NULL;
    DESC *hdesc = (DESC *)hashfindLEN(&player, sizeof(player), &mudstate.desc_htab);
    while (hdesc != NULL)
    {
        if (d == hdesc)
        {
            if (last == NULL)
            {
                if (d->hashnext == NULL)
                {
                    hashdeleteLEN(&player, sizeof(player), &mudstate.desc_htab);
                }
                else
                {
                    hashreplLEN(&player, sizeof(player), d->hashnext, &mudstate.desc_htab);
                }
            }
            else
            {
                last->hashnext = d->hashnext;
            }
            break;
        }
        last = hdesc;
        hdesc = hdesc->hashnext;
    }
    d->hashnext = NULL;
}

void welcome_user(DESC *d)
{
    if (d->host_info & H_REGISTRATION)
    {
        fcache_dump(d, FC_CONN_REG);
    }
    else
    {
        fcache_dump(d, FC_CONN);
    }
}

void save_command(DESC *d, CBLK *command)
{
    command->hdr.nxt = NULL;
    if (d->input_tail == NULL)
    {
        d->input_head = command;

        // We have added our first command to an empty list. Go process it later.
        //
        scheduler.DeferImmediateTask(PRIORITY_SYSTEM, Task_ProcessCommand, d, 0);
    }
    else
    {
        d->input_tail->hdr.nxt = command;
    }
    d->input_tail = command;
}

static void set_userstring(char **userstring, const char *command)
{
    while (mux_isspace(*command))
    {
        command++;
    }

    if (!*command)
    {
        if (*userstring != NULL)
        {
            free_lbuf(*userstring);
            *userstring = NULL;
        }
    }
    else
    {
        if (*userstring == NULL)
        {
            *userstring = alloc_lbuf("set_userstring");
        }
        strcpy(*userstring, command);
    }
}

static void parse_connect(const char *msg, char *command, char *user, char *pass)
{
    if (strlen(msg) > MBUF_SIZE)
    {
        *command = '\0';
        *user = '\0';
        *pass = '\0';
        return;
    }
    while (mux_isspace(*msg))
    {
        msg++;
    }
    char *p = command;
    while (  *msg
          && !mux_isspace(*msg))
    {
        *p++ = *msg++;
    }
    *p = '\0';
    while (mux_isspace(*msg))
    {
        msg++;
    }
    p = user;
    if (  mudconf.name_spaces
       && *msg == '\"')
    {
        for (; *msg && (*msg == '\"' || mux_isspace(*msg)); msg++)
        {
            // Nothing.
        }
        while (  *msg
              && *msg != '\"')
        {
            while (  *msg
                  && !mux_isspace(*msg)
                  && *msg != '\"')
            {
                *p++ = *msg++;
            }

            if (*msg == '\"')
            {
                break;
            }

            while (mux_isspace(*msg))
            {
                msg++;
            }

            if (  *msg
               && *msg != '\"')
            {
                *p++ = ' ';
            }
        }
        while (  *msg
              && *msg == '\"')
        {
             msg++;
        }
    }
    else
    {
        while (  *msg
              && !mux_isspace(*msg))
        {
            *p++ = *msg++;
        }
    }
    *p = '\0';
    while (mux_isspace(*msg))
    {
        msg++;
    }
    p = pass;
    while (  *msg
          && !mux_isspace(*msg))
    {
        *p++ = *msg++;
    }
    *p = '\0';
}

static void announce_connect(dbref player, DESC *d)
{
    desc_addhash(d);

    DESC *dtemp;
    int count = 0;
    DESC_ITER_CONN(dtemp)
    {
        count++;
    }

    if (mudstate.record_players < count)
    {
        mudstate.record_players = count;
    }

    char *buf = alloc_lbuf("announce_connect");
    dbref aowner;
    int aflags;
    size_t nLen;
    atr_pget_str_LEN(buf, player, A_TIMEOUT, &aowner, &aflags, &nLen);
    if (nLen)
    {
        d->timeout = mux_atol(buf);
        if (d->timeout <= 0)
        {
            d->timeout = mudconf.idle_timeout;
        }
    }

    dbref loc = Location(player);
    s_Connected(player);

    if (d->flags & DS_PUEBLOCLIENT)
    {
        s_Html(player);
    }

    raw_notify( player, tprintf("\n%sMOTD:%s %s\n", ANSI_HILITE,
                ANSI_NORMAL, mudconf.motd_msg));

    if (Wizard(player))
    {
        raw_notify(player, tprintf("%sWIZMOTD:%s %s\n", ANSI_HILITE,
            ANSI_NORMAL, mudconf.wizmotd_msg));

        if (!(mudconf.control_flags & CF_LOGIN))
        {
            raw_notify(player, "*** Logins are disabled.");
        }
    }
    atr_get_str_LEN(buf, player, A_LPAGE, &aowner, &aflags, &nLen);
    if (nLen)
    {
        raw_notify(player, "Your PAGE LOCK is set.  You may be unable to receive some pages.");
    }
    int num = 0;
    DESC_ITER_PLAYER(player, dtemp)
    {
        num++;
    }

    // Reset vacation flag.
    //
    s_Flags(player, FLAG_WORD2, Flags2(player) & ~VACATION);

    char *pRoomAnnounceFmt;
    char *pMonitorAnnounceFmt;
    if (num < 2)
    {
        pRoomAnnounceFmt = "%s has connected.";
        if (mudconf.have_comsys)
        {
            do_comconnect(player);
        }
        if (  Hidden(player)
           && Can_Hide(player))
        {
            pMonitorAnnounceFmt = "GAME: %s has DARK-connected.";
        }
        else
        {
            pMonitorAnnounceFmt = "GAME: %s has connected.";
        }
        if (  Suspect(player)
           || (d->host_info & H_SUSPECT))
        {
            raw_broadcast(WIZARD, "[Suspect] %s has connected.", Moniker(player));
        }
    }
    else
    {
        pRoomAnnounceFmt = "%s has reconnected.";
        pMonitorAnnounceFmt = "GAME: %s has reconnected.";
        if (  Suspect(player)
           || (d->host_info & H_SUSPECT))
        {
            raw_broadcast(WIZARD, "[Suspect] %s has reconnected.", Moniker(player));
        }
    }
    sprintf(buf, pRoomAnnounceFmt, Moniker(player));
    raw_broadcast(MONITOR, pMonitorAnnounceFmt, Moniker(player));

    int key = MSG_INV;
    if (  loc != NOTHING
       && !(  Hidden(player)
           && Can_Hide(player)))
    {
        key |= (MSG_NBR | MSG_NBR_EXITS | MSG_LOC | MSG_FWDLIST);
    }

    dbref temp = mudstate.curr_enactor;
    mudstate.curr_enactor = player;
#ifdef REALITY_LVLS
    if(loc == NOTHING)
        notify_check(player, player, buf, key);
    else
        notify_except_rlevel(loc, player, player, buf, 0);
#else
    notify_check(player, player, buf, key);
#endif /* REALITY_LVLS */
    atr_pget_str_LEN(buf, player, A_ACONNECT, &aowner, &aflags, &nLen);
    CLinearTimeAbsolute lta;
    dbref zone, obj;
    if (nLen)
    {
        wait_que(player, player, player, false, lta, NOTHING, 0, buf,
            (char **)NULL, 0, NULL);
    }
    if (mudconf.master_room != NOTHING)
    {
        atr_pget_str_LEN(buf, mudconf.master_room, A_ACONNECT, &aowner,
            &aflags, &nLen);
        if (nLen)
        {
            wait_que(mudconf.master_room, player, player, false, lta,
                NOTHING, 0, buf, (char **)NULL, 0, NULL);
        }
        DOLIST(obj, Contents(mudconf.master_room))
        {
            atr_pget_str_LEN(buf, obj, A_ACONNECT, &aowner, &aflags, &nLen);
            if (nLen)
            {
                wait_que(obj, player, player, false, lta, NOTHING, 0, buf,
                    (char **)NULL, 0, NULL);
            }
        }
    }

    // Do the zone of the player's location's possible aconnect.
    //
    if (  mudconf.have_zones
       && Good_obj(zone = Zone(loc)))
    {
        switch (Typeof(zone))
        {
        case TYPE_THING:

            atr_pget_str_LEN(buf, zone, A_ACONNECT, &aowner, &aflags, &nLen);
            if (nLen)
            {
                wait_que(zone, player, player, false, lta, NOTHING, 0, buf,
                    (char **)NULL, 0, NULL);
            }
            break;

        case TYPE_ROOM:

            // check every object in the room for a connect action.
            //
            DOLIST(obj, Contents(zone))
            {
                atr_pget_str_LEN(buf, obj, A_ACONNECT, &aowner, &aflags,
                    &nLen);
                if (nLen)
                {
                    wait_que(obj, player, player, false, lta, NOTHING, 0,
                        buf, (char **)NULL, 0, NULL);
                }
            }
            break;

        default:

            log_text(tprintf("Invalid zone #%d for %s(#%d) has bad type %d",
                zone, Name(player), player, Typeof(zone)));
        }
    }
    free_lbuf(buf);
    CLinearTimeAbsolute ltaNow;
    ltaNow.GetLocal();
    char *time_str = ltaNow.ReturnDateString(7);

    record_login(player, true, time_str, d->addr, d->username,
        inet_ntoa((d->address).sin_addr));
    if (mudconf.have_mailer)
    {
        check_mail(player, 0, false);
    }
    look_in(player, Location(player), (LK_SHOWEXIT|LK_OBEYTERSE|LK_SHOWVRML));
    mudstate.curr_enactor = temp;
    if (Guest(player))
    {
        db[player].fs.word[FLAG_WORD1] &= ~DARK;
    }
}

void announce_disconnect(dbref player, DESC *d, const char *reason)
{
    int num = 0, key;
    DESC *dtemp;
    DESC_ITER_PLAYER(player, dtemp)
    {
        num++;
    }

    dbref temp = mudstate.curr_enactor;
    mudstate.curr_enactor = player;
    dbref loc = Location(player);

    if (num < 2)
    {
        if (  Suspect(player)
           || (d->host_info & H_SUSPECT))
        {
            raw_broadcast(WIZARD, "[Suspect] %s has disconnected.", Moniker(player));
        }
        char *buf = alloc_lbuf("announce_disconnect.only");

        sprintf(buf, "%s has disconnected.", Moniker(player));
        key = MSG_INV;
        if (  loc != NOTHING
           && !(  Hidden(player)
               && Can_Hide(player)))
        {
            key |= (MSG_NBR | MSG_NBR_EXITS | MSG_LOC | MSG_FWDLIST);
        }
#ifdef REALITY_LVLS
        if(loc == NOTHING)
            notify_check(player, player, buf, key);
        else
            notify_except_rlevel(loc, player, player, buf, 0);
#else
        notify_check(player, player, buf, key);
#endif /* REALITY_LVLS */

        if (mudconf.have_mailer)
        {
            do_mail_purge(player);
        }

        raw_broadcast(MONITOR, "GAME: %s has disconnected. <%s>", Moniker(player), reason);

        c_Connected(player);

        if (mudconf.have_comsys)
        {
            do_comdisconnect(player);
        }

        dbref aowner, zone, obj;
        int aflags;
        size_t nLen;
        char *argv[1];
        argv[0] = (char *)reason;
        CLinearTimeAbsolute lta;
        atr_pget_str_LEN(buf, player, A_ADISCONNECT, &aowner, &aflags, &nLen);
        if (nLen)
        {
            wait_que(player, player, player, false, lta, NOTHING, 0, buf,
                argv, 1, NULL);
        }
        if (mudconf.master_room != NOTHING)
        {
            atr_pget_str_LEN(buf, mudconf.master_room, A_ADISCONNECT, &aowner,
                &aflags, &nLen);
            if (nLen)
            {
                wait_que(mudconf.master_room, player, player, false, lta,
                    NOTHING, 0, buf, (char **)NULL, 0, NULL);
            }
            DOLIST(obj, Contents(mudconf.master_room))
            {
                atr_pget_str_LEN(buf, obj, A_ADISCONNECT, &aowner, &aflags,
                    &nLen);
                if (nLen)
                {
                    wait_que(obj, player, player, false, lta, NOTHING, 0,
                        buf, (char **)NULL, 0, NULL);
                }
            }
        }

        // Do the zone of the player's location's possible adisconnect.
        //
        if (mudconf.have_zones && Good_obj(zone = Zone(loc)))
        {
            switch (Typeof(zone))
            {
            case TYPE_THING:

                atr_pget_str_LEN(buf, zone, A_ADISCONNECT, &aowner, &aflags,
                    &nLen);
                if (nLen)
                {
                    wait_que(zone, player, player, false, lta, NOTHING, 0,
                        buf, (char **)NULL, 0, NULL);
                }
                break;

            case TYPE_ROOM:

                // check every object in the room for a connect action.
                //
                DOLIST(obj, Contents(zone))
                {
                    atr_pget_str_LEN(buf, obj, A_ADISCONNECT, &aowner, &aflags,
                        &nLen);
                    if (nLen)
                    {
                        wait_que(obj, player, player, false, lta, NOTHING,
                            0, buf, (char **)NULL, 0, NULL);
                    }
                }
                break;

            default:
                log_text(tprintf("Invalid zone #%d for %s(#%d) has bad type %d",
                    zone, Name(player), player, Typeof(zone)));
            }
        }
        free_lbuf(buf);
        if (d->flags & DS_AUTODARK)
        {
            d->flags &= ~DS_AUTODARK;

            // Don't clear the DARK flag on the player unless there are
            // no other sessions.
            //
            DESC *d1;
            int num = 0;
            DESC_ITER_PLAYER(player, d1)
            {
                num++;
            }
            if (num <= 1)
            {
                db[player].fs.word[FLAG_WORD1] &= ~DARK;
            }
        }

        if (Guest(player))
        {
            db[player].fs.word[FLAG_WORD1] |= DARK;
            halt_que(NOTHING, player);
        }
    }
    else
    {
        if (  Suspect(player)
           || (d->host_info & H_SUSPECT))
        {
            raw_broadcast(WIZARD, "[Suspect] %s has partially disconnected.", Moniker(player));
        }
        char *mbuf = alloc_mbuf("announce_disconnect.partial");
        sprintf(mbuf, "%s has partially disconnected.", Moniker(player));
        key = MSG_INV;
        if (  loc != NOTHING
           && !(  Hidden(player)
               && Can_Hide(player)))
        {
            key |= (MSG_NBR | MSG_NBR_EXITS | MSG_LOC | MSG_FWDLIST);
        }
#ifdef REALITY_LVLS
        if(loc == NOTHING)
            notify_check(player, player, mbuf, key);
        else
            notify_except_rlevel(loc, player, player, mbuf, 0);
#else
        notify_check(player, player, mbuf, key);
#endif /* REALITY_LVLS */
        raw_broadcast(MONITOR, "GAME: %s has partially disconnected.",
            Moniker(player));
        free_mbuf(mbuf);
    }

    mudstate.curr_enactor = temp;
    desc_delhash(d);

    local_disconnect(player, num);
}

int boot_off(dbref player, const char *message)
{
    DESC *d, *dnext;
    int count = 0;
    DESC_SAFEITER_PLAYER(player, d, dnext)
    {
        if (message && *message)
        {
            queue_string(d, message);
            queue_write_LEN(d, "\r\n", 2);
        }
        shutdownsock(d, R_BOOT);
        count++;
    }
    return count;
}

int boot_by_port(SOCKET port, bool bGod, const char *message)
{
    DESC *d, *dnext;
    int count = 0;
    DESC_SAFEITER_ALL(d, dnext)
    {
        if (  d->descriptor == port
           && (  bGod
              || !(d->flags & DS_CONNECTED)
              || !God(d->player)))
        {
            if (  message
               && *message)
            {
                queue_string(d, message);
                queue_write_LEN(d, "\r\n", 2);
            }
            shutdownsock(d, R_BOOT);
            count++;
        }
    }
    return count;
}

/* ---------------------------------------------------------------------------
 * desc_reload: Reload parts of net descriptor that are based on db info.
 */

void desc_reload(dbref player)
{
    DESC *d;
    char *buf;
    dbref aowner;
    FLAG aflags;

    DESC_ITER_PLAYER(player, d)
    {
        buf = atr_pget(player, A_TIMEOUT, &aowner, &aflags);
        if (buf)
        {
            d->timeout = mux_atol(buf);
            if (d->timeout <= 0)
            {
                d->timeout = mudconf.idle_timeout;
            }
        }
        free_lbuf(buf);
    }
}

// ---------------------------------------------------------------------------
// fetch_session: Return number of sessions (or 0 if not logged in).
//
int fetch_session(dbref target)
{
    DESC *d;
    int nCount = 0;
    DESC_ITER_PLAYER(target, d)
    {
        nCount++;
    }
    return nCount;
}

// ---------------------------------------------------------------------------
// fetch_idle: Return smallest idle time for a player (or -1 if not logged in).
//
int fetch_idle(dbref target)
{
    CLinearTimeAbsolute ltaNow;
    CLinearTimeAbsolute ltaNewestLastTime;
    ltaNow.GetUTC();

    DESC *d;
    bool bFound = false;
    DESC_ITER_PLAYER(target, d)
    {
        if (  !bFound
           || ltaNewestLastTime < d->last_time)
        {
            bFound = true;
            ltaNewestLastTime = d->last_time;
        }
    }
    if (bFound)
    {
        CLinearTimeDelta ltdResult;
        ltdResult = ltaNow - ltaNewestLastTime;
        return ltdResult.ReturnSeconds();
    }
    else
    {
        return -1;
    }
}

// ---------------------------------------------------------------------------
// find_oldest: Return descriptor with the oldeset connected_at (or NULL if
// not logged in).
//
void find_oldest(dbref target, DESC *dOldest[2])
{
    dOldest[0] = NULL;
    dOldest[1] = NULL;

    DESC *d;
    bool bFound = false;
    DESC_ITER_PLAYER(target, d)
    {
        if (  !bFound
           || d->connected_at < dOldest[0]->connected_at)
        {
            bFound = true;
            dOldest[1] = dOldest[0];
            dOldest[0] = d;
        }
    }
}

// ---------------------------------------------------------------------------
// fetch_connect: Return largest connect time for a player (or -1 if not
// logged in).
//
int fetch_connect(dbref target)
{
    DESC *dOldest[2];
    find_oldest(target, dOldest);
    if (dOldest[0])
    {
        CLinearTimeAbsolute ltaNow;
        CLinearTimeDelta ltdOldest;

        ltaNow.GetUTC();
        ltdOldest = ltaNow - dOldest[0]->connected_at;
        return ltdOldest.ReturnSeconds();
    }
    else
    {
        return -1;
    }
}

// A NOTE about AUTODARK: It only works for wizard players. Wizard players
// are automatically set DARK if they are not already set DARK and they have
// no session which is unidle.
//
// The AUTODARK state is cleared when at least one session becomes unidle.
// The AUTODARK state is also cleared when the last idle session is
// disconnected from the server (session shutdown or @shutdown).
//
void check_idle(void)
{
    DESC *d, *dnext;

    CLinearTimeAbsolute ltaNow;
    ltaNow.GetUTC();

    DESC_SAFEITER_ALL(d, dnext)
    {
        if (  (d->flags & DS_CONNECTED)
           && KeepAlive(d->player))
        {
            // Send a Telnet NOP code - creates traffic to keep NAT routers
            // happy.  Hopefully this only runs once a minute.
            //
            queue_write_LEN(d, "\377\361", 2);
        }
        if (d->flags & DS_AUTODARK)
        {
            continue;
        }
        if (d->flags & DS_CONNECTED)
        {
            if (mudconf.idle_timeout <= 0)
            {
                // Idle timeout checking on connected players is effectively disabled.
                // PennMUSH uses idle_timeout == 0. Rhost uses idel_timeout == -1.
                // We will be disabled for either setting.
                //
                continue;
            }

            CLinearTimeDelta ltdIdle = ltaNow - d->last_time;
            if (Can_Idle(d->player))
            {
                if (  mudconf.idle_wiz_dark
                   && (Flags(d->player) & (WIZARD|DARK)) == WIZARD
                   && ltdIdle.ReturnSeconds() > mudconf.idle_timeout)
                {
                    // Make sure this Wizard player does not have some other
                    // active session.
                    //
                    DESC *d1;
                    bool bFound = false;
                    DESC_ITER_PLAYER(d->player, d1)
                    {
                        if (d1 != d)
                        {
                            CLinearTimeDelta ltd = ltaNow - d1->last_time;
                            if (ltd.ReturnSeconds() <= mudconf.idle_timeout)
                            {
                                 bFound = true;
                                 break;
                            }
                        }
                    }
                    if (!bFound)
                    {
                        db[d->player].fs.word[FLAG_WORD1] |= DARK;
                        DESC_ITER_PLAYER(d->player, d1)
                        {
                            d1->flags |= DS_AUTODARK;
                        }
                    }
                }
            }
            else if (ltdIdle.ReturnSeconds() > d->timeout)
            {
                queue_write(d, "*** Inactivity Timeout ***\r\n");
                shutdownsock(d, R_TIMEOUT);
            }
        }
        else if (0 < mudconf.conn_timeout)
        {
            CLinearTimeDelta ltdIdle = ltaNow - d->connected_at;
            if (ltdIdle.ReturnSeconds() > mudconf.conn_timeout)
            {
                queue_write(d, "*** Login Timeout ***\r\n");
                shutdownsock(d, R_TIMEOUT);
            }
        }
    }
}

void check_events(void)
{
    dbref thing, parent;
    int lev;

    CLinearTimeAbsolute ltaNow;
    ltaNow.GetLocal();

    FIELDEDTIME ft;
    if (!ltaNow.ReturnFields(&ft))
    {
        return;
    }

    // Resetting every midnight.
    //
    static int iLastHourChecked = 25;
    if (  iLastHourChecked == 23
       && ft.iHour < iLastHourChecked)
    {
        mudstate.events_flag &= ~ET_DAILY;
    }
    iLastHourChecked = ft.iHour;

    if (  ft.iHour == mudconf.events_daily_hour
       && !(mudstate.events_flag & ET_DAILY))
    {
        mudstate.events_flag |= ET_DAILY;
        DO_WHOLE_DB(thing)
        {
            if (Going(thing))
            {
                continue;
            }

            ITER_PARENTS(thing, parent, lev)
            {
                if (Flags2(thing) & HAS_DAILY)
                {
                    did_it(Owner(thing), thing, 0, NULL, 0, NULL, A_DAILY, (char **)NULL, 0);
                    break;
                }
            }
        }
    }

}

#define MAX_TRIMMED_NAME_LENGTH 16
static const char *trimmed_name(dbref player, int *pvw)
{
    static char cbuff[MBUF_SIZE];

    ANSI_TruncateToField(
        Moniker(player),
        sizeof(cbuff),
        cbuff,
        MAX_TRIMMED_NAME_LENGTH,
        pvw,
        ANSI_ENDGOAL_NORMAL
    );
    return cbuff;
}

static char *trimmed_site(char *szName)
{
    static char buff[MBUF_SIZE];

    unsigned int nLen = strlen(szName);
    if (  mudconf.site_chars <= 0
       || nLen <= mudconf.site_chars)
    {
        return szName;
    }
    nLen = mudconf.site_chars;
    if (nLen > sizeof(buff)-1)
    {
        nLen = sizeof(buff)-1;
    }
    memcpy(buff, szName, nLen);
    buff[nLen] = '\0';
    return buff;
}

static void dump_users(DESC *e, char *match, int key)
{
    DESC *d;
    int count;
    char *buf, *fp, *sp, flist[4], slist[4];
    dbref room_it;

    if (match)
    {
        while (mux_isspace(*match))
        {
            match++;
        }

        if (!*match)
        {
            match = NULL;
        }
    }

    if (  (e->flags & (DS_PUEBLOCLIENT|DS_CONNECTED))
       && Html(e->player))
    {
        queue_write(e, "<pre>");
    }

    buf = alloc_mbuf("dump_users");
    if (key == CMD_SESSION)
    {
        queue_write(e, "                               ");
        queue_write(e, "     Characters Input----  Characters Output---\r\n");
    }
    queue_write(e, "Player Name        On For Idle ");
    if (key == CMD_SESSION)
    {
        queue_write(e, "Port Pend  Lost     Total  Pend  Lost     Total\r\n");
    }
    else if (  (e->flags & DS_CONNECTED)
            && Wizard_Who(e->player)
            && key == CMD_WHO)
    {
        queue_write(e, "  Room    Cmds   Host\r\n");
    }
    else
    {
        if (  Wizard_Who(e->player)
           || See_Hidden(e->player))
        {
            queue_write(e, "  ");
        }
        else
        {
            queue_write(e, " ");
        }
        queue_string(e, mudstate.doing_hdr);
        queue_write_LEN(e, "\r\n", 2);
    }
    count = 0;

    CLinearTimeAbsolute ltaNow;
    ltaNow.GetUTC();

    DESC_ITER_ALL(d)
    {
        if (!(  (  (e->flags & DS_CONNECTED)
                && SiteMon(e->player))
             || (d->flags & DS_CONNECTED)))
        {
            continue;
        }
        if (  !(d->flags & DS_CONNECTED)
           || !Hidden(d->player)
           || (  (e->flags & DS_CONNECTED)
              && (  Wizard_Who(e->player)
                 || See_Hidden(e->player))))
        {
            count++;
            if (  match
               && (  !(d->flags & DS_CONNECTED)
                  || string_prefix(Name(d->player), match) == 0))
            {
                continue;
            }
            if (  key == CMD_SESSION
               && (  !(e->flags & DS_CONNECTED)
                  || !Wizard_Who(e->player))
               && (  !(e->flags & DS_CONNECTED)
                  || !(d->flags & DS_CONNECTED)
                  || d->player != e->player))
            {
                continue;
            }

            // Get choice flags for wizards.
            //
            fp = flist;
            sp = slist;
            if (  (e->flags & DS_CONNECTED)
               && Wizard_Who(e->player))
            {
                if (  (d->flags & DS_CONNECTED)
                   && Hidden(d->player))
                {
                    if (d->flags & DS_AUTODARK)
                    {
                        *fp++ = 'd';
                    }
                    else
                    {
                        *fp++ = 'D';
                    }
                }
                if (d->flags & DS_CONNECTED)
                {
                    if (Hideout(d->player))
                    {
                        *fp++ = 'U';
                    }
                    else
                    {
                        room_it = where_room(d->player);
                        if (Good_obj(room_it))
                        {
                            if (Hideout(room_it))
                            {
                                *fp++ = 'u';
                            }
                        }
                        else
                        {
                            *fp++ = 'u';
                        }
                    }

                    if (Suspect(d->player))
                    {
                        *fp++ = '+';
                    }
                }
                if (d->host_info & H_FORBIDDEN)
                {
                    *sp++ = 'F';
                }
                if (d->host_info & H_REGISTRATION)
                {
                    *sp++ = 'R';
                }
                if (d->host_info & H_SUSPECT)
                {
                    *sp++ = '+';
                }
                if (d->host_info & H_GUEST)
                {
                    *sp++ = 'G';
                }
            }
            else if (  (e->flags & DS_CONNECTED)
                    && (d->flags & DS_CONNECTED)
                    && See_Hidden(e->player)
                    && Hidden(d->player))
            {
                if (d->flags & DS_AUTODARK)
                {
                    *fp++ = 'd';
                }
                else
                {
                    *fp++ = 'D';
                }
            }
            *fp = '\0';
            *sp = '\0';

            CLinearTimeDelta ltdConnected = ltaNow - d->connected_at;
            CLinearTimeDelta ltdLastTime  = ltaNow - d->last_time;

            const char *pNameField = "<Unconnected>";
            int vwNameField = strlen(pNameField);
            if (d->flags & DS_CONNECTED)
            {
                pNameField = trimmed_name(d->player, &vwNameField);
            }

            // How many spaces between the name field and the 'On For' field.
            //
            size_t nFill;
            if (13 <= vwNameField)
            {
                nFill = 1;
            }
            else
            {
                nFill = 14-vwNameField;
            }
            char aFill[15];
            memset(aFill, ' ', nFill);
            aFill[nFill] = '\0';

            // The width size allocated to the 'On For' field.
            //
            size_t nOnFor = 25 - nFill - vwNameField;

            if (  (e->flags & DS_CONNECTED)
               && Wizard_Who(e->player)
               && key == CMD_WHO)
            {
                sprintf(buf, "%s%s%s %4s%-3s#%-6d%5d%3s%s\r\n",
                    pNameField, aFill,
                    time_format_1(ltdConnected.ReturnSeconds(), nOnFor),
                    time_format_2(ltdLastTime.ReturnSeconds()),
                    flist,
                    ((d->flags & DS_CONNECTED) ? Location(d->player) : -1),
                    d->command_count,
                    slist,
                    trimmed_site(((d->username[0] != '\0') ? tprintf("%s@%s", d->username, d->addr) : d->addr)));
            }
            else if (key == CMD_SESSION)
            {
                sprintf(buf, "%s%s%s %4s%5d%5d%6d%10d%6d%6d%10d\r\n",
                    pNameField, aFill,
                    time_format_1(ltdConnected.ReturnSeconds(), nOnFor),
                    time_format_2(ltdLastTime.ReturnSeconds()),
                    d->descriptor,
                    d->input_size, d->input_lost,
                    d->input_tot,
                    d->output_size, d->output_lost,
                    d->output_tot);
            }
            else if (  Wizard_Who(e->player)
                    || See_Hidden(e->player))
            {
                sprintf(buf, "%s%s%s %4s%-3s%s\r\n",
                    pNameField, aFill,
                    time_format_1(ltdConnected.ReturnSeconds(), nOnFor),
                    time_format_2(ltdLastTime.ReturnSeconds()),
                    flist,
                    d->doing);
            }
            else
            {
                sprintf(buf, "%s%s%s %4s  %s\r\n",
                    pNameField, aFill,
                    time_format_1(ltdConnected.ReturnSeconds(), nOnFor),
                    time_format_2(ltdLastTime.ReturnSeconds()),
                    d->doing);
            }
            queue_string(e, buf);
        }
    }

    // Sometimes I like the ternary operator.
    //
    sprintf(buf, "%d Player%slogged in, %d record, %s maximum.\r\n", count,
        (count == 1) ? " " : "s ", mudstate.record_players,
        (mudconf.max_players == -1) ? "no" : mux_ltoa_t(mudconf.max_players));
    queue_write(e, buf);

    if (  (e->flags & (DS_PUEBLOCLIENT|DS_CONNECTED))
       && Html(e->player))
    {
        queue_write(e, "</pre>");
    }
    free_mbuf(buf);
}

#ifdef WOD_REALMS
#define INFO_VERSION "1.1"
#else // WOD_REALMS
#define INFO_VERSION "1"
#endif // WOD_REALMS

static void dump_info(DESC *arg_desc)
{
    queue_write(arg_desc, "### Begin INFO " INFO_VERSION "\r\n");

    queue_string(arg_desc, tprintf("Name: %s\r\n", mudconf.mud_name));

    char *temp = mudstate.start_time.ReturnDateString();
    queue_write(arg_desc, tprintf("Uptime: %s\r\n", temp));

    DESC *d;
    int count = 0;
    DESC_ITER_CONN(d)
    {
        if (!Good_obj(d->player))
        {
            continue;
        }
        if (  !Hidden(d->player)
           || (  (arg_desc->flags & DS_CONNECTED)
              && See_Hidden(arg_desc->player)))
        {
            count++;
        }
    }
    queue_write(arg_desc, tprintf("Connected: %d\r\n", count));
    queue_write(arg_desc, tprintf("Size: %d\r\n", mudstate.db_top));
    queue_write(arg_desc, tprintf("Version: %s\r\n", mudstate.short_ver));
#ifdef WOD_REALMS
    queue_write(arg_desc, tprintf("Patches: WOD_REALMS\r\n"));
#endif // WOD_REALMS
    queue_write(arg_desc, "### End INFO\r\n");
}

char *MakeCanonicalDoing(char *pDoing, int *pnValidDoing, bool *pbValidDoing)
{
    *pnValidDoing = 0;
    *pbValidDoing = false;

    if (!pDoing)
    {
        return NULL;
    }

    // First, remove all '\r\n\t' from the string.
    //
    char *Buffer = RemoveSetOfCharacters(pDoing, "\r\n\t");

    // Optimize/terminate any ANSI in the string.
    //
    int nVisualWidth;
    static char szFittedDoing[SIZEOF_DOING_STRING];
    *pnValidDoing = ANSI_TruncateToField
                    ( Buffer,
                      SIZEOF_DOING_STRING,
                      szFittedDoing,
                      WIDTHOF_DOING_STRING,
                      &nVisualWidth,
                      ANSI_ENDGOAL_NORMAL
                    );
    *pbValidDoing = true;
    return szFittedDoing;
}

// ---------------------------------------------------------------------------
// do_doing: Set the doing string that appears in the WHO report.
// Idea from R'nice@TinyTIM.
//
void do_doing(dbref executor, dbref caller, dbref enactor, int key, char *arg)
{
    // Make sure there can be no embedded newlines from %r
    //
    static char *Empty = "";
    char *szValidDoing = Empty;
    bool bValidDoing;
    int nValidDoing = 0;
    if (arg)
    {
        szValidDoing = MakeCanonicalDoing(arg, &nValidDoing, &bValidDoing);
        if (!bValidDoing)
        {
            szValidDoing = Empty;
            nValidDoing = 0;
        }
    }

    bool bQuiet = ((key & DOING_QUIET) == DOING_QUIET);
    key &= DOING_MASK;
    if (key == DOING_MESSAGE)
    {
        DESC *d;
        bool bFound = false;
        DESC_ITER_PLAYER(executor, d)
        {
            memcpy(d->doing, szValidDoing, nValidDoing+1);
            bFound = true;
        }
        if (bFound)
        {
            if (  !bQuiet
               && !Quiet(executor))
            {
                notify(executor, "Set.");
            }
        }
        else
        {
            notify(executor, "Not connected.");
        }
    }
    else if (key == DOING_UNIQUE)
    {
        DESC *d;
        DESC *dMax = NULL;
        CLinearTimeAbsolute ltaMax;
        DESC_ITER_PLAYER(executor, d)
        {
            if (  !dMax
               && ltaMax < d->last_time)
            {
                ltaMax = d->last_time;
                dMax = d;
            }
        }
        if (dMax)
        {
            memcpy(dMax->doing, szValidDoing, nValidDoing+1);
            if (  !bQuiet
               && !Quiet(executor))
            {
                notify(executor, "Set.");
            }
        }
        else
        {
            notify(executor, "Not connected.");
        }
    }
    else if (key == DOING_HEADER)
    {
        if (!Can_Poll(executor))
        {
            notify(executor, NOPERM_MESSAGE);
            return;
        }
        if (nValidDoing == 0)
        {
            strcpy(mudstate.doing_hdr, "Doing");
        }
        else
        {
            memcpy(mudstate.doing_hdr, szValidDoing, nValidDoing+1);
        }
        if (  !bQuiet
           && !Quiet(executor))
        {
            notify(executor, "Set.");
        }
    }
    else // if (key == DOING_POLL)
    {
        notify(executor, tprintf("Poll: %s", mudstate.doing_hdr));
    }
}

NAMETAB logout_cmdtable[] =
{
    {(char *)"DOING",         5,  CA_PUBLIC,  CMD_DOING},
    {(char *)"LOGOUT",        6,  CA_PUBLIC,  CMD_LOGOUT},
    {(char *)"OUTPUTPREFIX", 12,  CA_PUBLIC,  CMD_PREFIX|CMD_NOxFIX},
    {(char *)"OUTPUTSUFFIX", 12,  CA_PUBLIC,  CMD_SUFFIX|CMD_NOxFIX},
    {(char *)"QUIT",          4,  CA_PUBLIC,  CMD_QUIT},
    {(char *)"SESSION",       7,  CA_PUBLIC,  CMD_SESSION},
    {(char *)"WHO",           3,  CA_PUBLIC,  CMD_WHO},
    {(char *)"PUEBLOCLIENT", 12,  CA_PUBLIC,  CMD_PUEBLOCLIENT},
    {(char *)"INFO",          4,  CA_PUBLIC,  CMD_INFO},
    {NULL,                    0,          0,         0}
};

void init_logout_cmdtab(void)
{
    NAMETAB *cp;

    // Make the htab bigger than the number of entries so that we find things
    // on the first check.  Remember that the admin can add aliases.
    //
    for (cp = logout_cmdtable; cp->flag; cp++)
    {
        hashaddLEN(cp->name, strlen(cp->name), cp, &mudstate.logout_cmd_htab);
    }
}

static void failconn(const char *logcode, const char *logtype, const char *logreason,
                     DESC *d, int disconnect_reason,
                     dbref player, int filecache, char *motd_msg, char *command,
                     char *user, char *password, char *cmdsave)
{
    STARTLOG(LOG_LOGIN | LOG_SECURITY, logcode, "RJCT");
    char *buff = alloc_mbuf("failconn.LOG");
    sprintf(buff, "[%d/%s] %s rejected to ", d->descriptor, d->addr, logtype);
    log_text(buff);
    free_mbuf(buff);
    if (player != NOTHING)
    {
        log_name(player);
    }
    else
    {
        log_text(user);
    }
    log_text(" (");
    log_text(logreason);
    log_text(")");
    ENDLOG;
    fcache_dump(d, filecache);
    if (*motd_msg)
    {
        queue_string(d, motd_msg);
        queue_write_LEN(d, "\r\n", 2);
    }
    free_lbuf(command);
    free_lbuf(user);
    free_lbuf(password);
    shutdownsock(d, disconnect_reason);
    mudstate.debug_cmd = cmdsave;
    return;
}

static const char *connect_fail = "Either that player does not exist, or has a different password.\r\n";

static bool check_connect(DESC *d, char *msg)
{
    char *buff;
    dbref player, aowner;
    int aflags, nplayers;
    DESC *d2;
    const char *p;
    bool isGuest = false;

    char *cmdsave = mudstate.debug_cmd;
    mudstate.debug_cmd = (char *)"< check_connect >";

    // Hide the password length from SESSION.
    //
    d->input_tot -= (strlen(msg) + 1);

    // Crack the command apart.
    //
    char *command = alloc_lbuf("check_conn.cmd");
    char *user = alloc_lbuf("check_conn.user");
    char *password = alloc_lbuf("check_conn.pass");
    parse_connect(msg, command, user, password);

    // At this point, command, user, and password are all less than
    // MBUF_SIZE.
    //
    if (  strncmp(command, "co", 2) == 0
       || strncmp(command, "cd", 2) == 0)
    {
        if (string_prefix(user, mudconf.guest_prefix))
        {
            if (  (d->host_info & H_GUEST)
               || (   !mudconf.allow_guest_from_registered_site
                  && (d->host_info & H_REGISTRATION)))
            {
                // Someone from an IP with guest restrictions is
                // trying to use a guest account. Give them the blurb
                // most likely to have instructions about requesting a
                // character by other means and then fail this
                // connection.
                //
                // The guest 'power' is handled separately further
                // down.
                //
                failconn("CONN", "Connect", "Guest Site Forbidden", d,
                    R_GAMEDOWN, NOTHING, FC_CONN_REG, mudconf.downmotd_msg,
                    command, user, password, cmdsave);
                return false;
            }
            if (  mudconf.guest_char != NOTHING
               && (mudconf.control_flags & CF_LOGIN))
            {
                if (!(mudconf.control_flags & CF_GUEST))
                {
                    queue_write(d, "Guest logins are disabled.\r\n");
                    free_lbuf(command);
                    free_lbuf(user);
                    free_lbuf(password);
                    return false;
                }

                if ((p = Guest.Create(d)) == NULL)
                {
                    queue_write(d, "All guests are tied up, please try again later.\r\n");
                    free_lbuf(command);
                    free_lbuf(user);
                    free_lbuf(password);
                    return false;
                }
                strcpy(user, p);
                strcpy(password, GUEST_PASSWORD);
                isGuest = true;
            }
        }

        // See if this connection would exceed the max #players.
        //
        if (mudconf.max_players < 0)
        {
            nplayers = mudconf.max_players - 1;
        }
        else
        {
            nplayers = 0;
            DESC_ITER_CONN(d2)
            {
                nplayers++;
            }
        }

        player = connect_player(user, password, d->addr, d->username, inet_ntoa((d->address).sin_addr));
        if (  player == NOTHING
           || (!isGuest && Guest.CheckGuest(player)))
        {
            // Not a player, or wrong password.
            //
            queue_write(d, connect_fail);
            STARTLOG(LOG_LOGIN | LOG_SECURITY, "CON", "BAD");
            buff = alloc_lbuf("check_conn.LOG.bad");
            sprintf(buff, "[%d/%s] Failed connect to '%s'", d->descriptor, d->addr, user);
            log_text(buff);
            free_lbuf(buff);
            ENDLOG;
            if (--(d->retries_left) <= 0)
            {
                free_lbuf(command);
                free_lbuf(user);
                free_lbuf(password);
                shutdownsock(d, R_BADLOGIN);
                mudstate.debug_cmd = cmdsave;
                return false;
            }
        }
        else if (  (  (mudconf.control_flags & CF_LOGIN)
                   && (nplayers < mudconf.max_players))
                || WizRoy(player)
                || God(player))
        {
            if (  strncmp(command, "cd", 2) == 0
               && (  Wizard(player)
                  || God(player)))
            {
                db[player].fs.word[FLAG_WORD1] |= DARK;
            }

            // Make sure we don't have a guest from an unwanted host.
            // The majority of these are handled above.
            //
            // The following code handles the case where a staffer
            // (#1-only by default) has specifically given the guest 'power'
            // to an existing player.
            //
            // In this case, the player -already- has an account complete
            // with password. We still fail the connection to -this- player
            // but if the site isn't register_sited, this player can simply
            // auto-create another player. So, the procedure is not much
            // different from @newpassword'ing them. Oh well. We are just
            // following orders. ;)
            //
            if (  Guest(player)
               && (  (d->host_info & H_GUEST)
                  || (   !mudconf.allow_guest_from_registered_site
                     && (d->host_info & H_REGISTRATION))))
            {
                failconn("CON", "Connect", "Guest Site Forbidden", d,
                    R_GAMEDOWN, player, FC_CONN_SITE,
                    mudconf.downmotd_msg, command, user, password,
                    cmdsave);
                return false;
            }

            // Logins are enabled, or wiz or god.
            //
            STARTLOG(LOG_LOGIN, "CON", "LOGIN");
            buff = alloc_mbuf("check_conn.LOG.login");
            sprintf(buff, "[%d/%s] Connected to ", d->descriptor, d->addr);
            log_text(buff);
            log_name_and_loc(player);
            free_mbuf(buff);
            ENDLOG;
            d->flags |= DS_CONNECTED;
            d->connected_at.GetUTC();
            d->player = player;

            // Check to see if the player is currently running an
            // @program. If so, drop the new descriptor into it.
            //
            DESC_ITER_PLAYER(player, d2)
            {
                if (d2->program_data != NULL)
                {
                    d->program_data = d2->program_data;
                    break;
                }
            }

            // Give the player the MOTD file and the settable MOTD
            // message(s). Use raw notifies so the player doesn't try
            // to match on the text.
            //
            if (Guest(player))
            {
                fcache_dump(d, FC_CONN_GUEST);
            }
            else
            {
                buff = atr_get(player, A_LAST, &aowner, &aflags);
                if (*buff == '\0')
                    fcache_dump(d, FC_CREA_NEW);
                else
                    fcache_dump(d, FC_MOTD);
                if (Wizard(player))
                    fcache_dump(d, FC_WIZMOTD);
                free_lbuf(buff);
            }
            announce_connect(player, d);

            DESC* dtemp;
            int num_con = 0;
            DESC_ITER_PLAYER(player, dtemp)
            {
                num_con++;
            }
            local_connect(player, 0, num_con);

            // If stuck in an @prog, show the prompt.
            //
            if (d->program_data != NULL)
            {
                queue_write_LEN(d, ">\377\371", 3);
            }

        }
        else if (!(mudconf.control_flags & CF_LOGIN))
        {
            failconn("CON", "Connect", "Logins Disabled", d, R_GAMEDOWN, player, FC_CONN_DOWN,
                mudconf.downmotd_msg, command, user, password, cmdsave);
            return false;
        }
        else
        {
            failconn("CON", "Connect", "Game Full", d, R_GAMEFULL, player, FC_CONN_FULL,
                mudconf.fullmotd_msg, command, user, password, cmdsave);
            return false;
        }
    }
    else if (strncmp(command, "cr", 2) == 0)
    {
        // Enforce game down.
        //
        if (!(mudconf.control_flags & CF_LOGIN))
        {
            failconn("CRE", "Create", "Logins Disabled", d, R_GAMEDOWN, NOTHING, FC_CONN_DOWN,
                mudconf.downmotd_msg, command, user, password, cmdsave);
            return false;
        }

        // Enforce max #players.
        //
        if (mudconf.max_players < 0)
        {
            nplayers = mudconf.max_players;
        }
        else
        {
            nplayers = 0;
            DESC_ITER_CONN(d2)
            {
                nplayers++;
            }
        }
        if (nplayers > mudconf.max_players)
        {
            // Too many players on, reject the attempt.
            //
            failconn("CRE", "Create", "Game Full", d,
                R_GAMEFULL, NOTHING, FC_CONN_FULL,
                mudconf.fullmotd_msg, command, user, password,
                cmdsave);
            return false;
        }
        if (d->host_info & H_REGISTRATION)
        {
            fcache_dump(d, FC_CREA_REG);
        }
        else
        {
            const char *pmsg;
            player = create_player(user, password, NOTHING, false, &pmsg);
            if (player == NOTHING)
            {
                queue_write(d, pmsg);
                queue_write(d, "\r\n");
                STARTLOG(LOG_SECURITY | LOG_PCREATES, "CON", "BAD");
                buff = alloc_lbuf("check_conn.LOG.badcrea");
                sprintf(buff, "[%d/%s] Create of '%s' failed", d->descriptor, d->addr, user);
                log_text(buff);
                free_lbuf(buff);
                ENDLOG;
            }
            else
            {
                AddToPublicChannel(player);
                STARTLOG(LOG_LOGIN | LOG_PCREATES, "CON", "CREA");
                buff = alloc_mbuf("check_conn.LOG.create");
                sprintf(buff, "[%d/%s] Created ", d->descriptor, d->addr);
                log_text(buff);
                log_name(player);
                free_mbuf(buff);
                ENDLOG;
                move_object(player, mudconf.start_room);
                d->flags |= DS_CONNECTED;
                d->connected_at.GetUTC();
                d->player = player;
                fcache_dump(d, FC_CREA_NEW);
                announce_connect(player, d);

                // Since it is on the create call, assume connection count
                // is 0 and indicate the connect is a new character.
                //
                local_connect(player, 1, 0);
            }
        }
    }
    else
    {
        welcome_user(d);
        STARTLOG(LOG_LOGIN | LOG_SECURITY, "CON", "BAD");
        buff = alloc_mbuf("check_conn.LOG.bad");
        msg[150] = '\0';
        sprintf(buff, "[%d/%s] Failed connect: '%s'", d->descriptor, d->addr, msg);
        log_text(buff);
        free_mbuf(buff);
        ENDLOG;
    }
    free_lbuf(command);
    free_lbuf(user);
    free_lbuf(password);

    mudstate.debug_cmd = cmdsave;
    return true;
}

static void do_logged_out_internal(DESC *d, int key, char *arg)
{
    switch (key)
    {
    case CMD_QUIT:

        shutdownsock(d, R_QUIT);
        break;

    case CMD_LOGOUT:

        shutdownsock(d, R_LOGOUT);
        break;

    case CMD_WHO:
    case CMD_DOING:
    case CMD_SESSION:

        dump_users(d, arg, key);
        break;

    case CMD_PREFIX:

        set_userstring(&d->output_prefix, arg);
        break;

    case CMD_SUFFIX:

        set_userstring(&d->output_suffix, arg);
        break;

    case CMD_INFO:

        dump_info(d);
        break;

    case CMD_PUEBLOCLIENT:

        // Set the descriptor's flag.
        //
        d->flags |= DS_PUEBLOCLIENT;

        queue_string(d, mudconf.pueblo_msg);
        queue_write_LEN(d, "\r\n", 2);
        break;

    default:

        {
            char buf[LBUF_SIZE * 2];
            STARTLOG(LOG_BUGS, "BUG", "PARSE");
            sprintf(buf, "Logged-out command with no handler: '%s'", mudstate.debug_cmd);
            log_text(buf);
            ENDLOG;
        }
    }
}

void do_command(DESC *d, char *command)
{
    char *cmdsave = mudstate.debug_cmd;
    mudstate.debug_cmd = (char *)"< do_command >";

    if (d->flags & DS_CONNECTED)
    {
        // Normal logged-in command processing.
        //
        d->command_count++;
        if (d->output_prefix)
        {
            queue_string(d, d->output_prefix);
            queue_write_LEN(d, "\r\n", 2);
        }
        mudstate.curr_executor = d->player;
        mudstate.curr_enactor = d->player;
        for (int i = 0; i < MAX_GLOBAL_REGS; i++)
        {
            mudstate.global_regs[i][0] = '\0';
            mudstate.glob_reg_len[i] = 0;
        }

        CLinearTimeAbsolute ltaBegin;
        ltaBegin.GetUTC();
        MuxAlarm.Set(mudconf.max_cmdsecs);

        char *log_cmdbuf = process_command(d->player, d->player, d->player,
            true, command, (char **)NULL, 0);

        CLinearTimeAbsolute ltaEnd;
        ltaEnd.GetUTC();
        if (MuxAlarm.bAlarmed)
        {
            notify(d->player, "GAME: Expensive activity abbreviated.");
            halt_que(d->player, NOTHING);
            s_Flags(d->player, FLAG_WORD1, Flags(d->player) | HALT);
        }
        MuxAlarm.Clear();

        CLinearTimeDelta ltd = ltaEnd - ltaBegin;
        if (ltd > mudconf.rpt_cmdsecs)
        {
            STARTLOG(LOG_PROBLEMS, "CMD", "CPU");
            log_name_and_loc(d->player);
            char *logbuf = alloc_lbuf("do_command.LOG.cpu");
            sprintf(logbuf, " queued command taking %s secs: ",
                ltd.ReturnSecondsString(4));
            log_text(logbuf);
            free_lbuf(logbuf);
            log_text(log_cmdbuf);
            ENDLOG;
        }

        mudstate.curr_cmd = (char *) "";
        if (d->output_suffix)
        {
            queue_string(d, d->output_suffix);
            queue_write_LEN(d, "\r\n", 2);
        }
        mudstate.debug_cmd = cmdsave;
        return;
    }

    // Login screen (logged-out) command processing.
    //

    // Split off the command from the arguments.
    //
    char *arg = command;
    while (*arg && !mux_isspace(*arg))
    {
        arg++;
    }

    if (*arg)
    {
        *arg++ = '\0';
    }

    // Look up the command in the logged-out command table.
    //
    NAMETAB *cp = (NAMETAB *)hashfindLEN(command, strlen(command), &mudstate.logout_cmd_htab);
    if (cp == NULL)
    {
        // Not in the logged-out command table, so maybe a connect attempt.
        //
        if (*arg)
        {
            // Restore nullified space
            //
            *--arg = ' ';
        }
        mudstate.curr_executor = NOTHING;
        mudstate.curr_enactor = NOTHING;
        mudstate.debug_cmd = cmdsave;
        check_connect(d, command);
        return;
    }

    // The command was in the logged-out command table. Perform
    // prefix and suffix processing, and invoke the command
    // handler.
    //
    d->command_count++;
    if (!(cp->flag & CMD_NOxFIX))
    {
        if (d->output_prefix)
        {
            queue_string(d, d->output_prefix);
            queue_write_LEN(d, "\r\n", 2);
        }
    }
    if (cp->perm != CA_PUBLIC)
    {
        queue_write(d, "Permission denied.\r\n");
    }
    else
    {
        mudstate.debug_cmd = cp->name;
        do_logged_out_internal(d, cp->flag & CMD_MASK, arg);
    }
    // QUIT or LOGOUT will close the connection and cause the
    // descriptor to be freed!
    //
    if (  ((cp->flag & CMD_MASK) != CMD_QUIT)
       && ((cp->flag & CMD_MASK) != CMD_LOGOUT)
       && !(cp->flag & CMD_NOxFIX))
    {
        if (d->output_suffix)
        {
            queue_string(d, d->output_suffix);
            queue_write_LEN(d, "\r\n", 2);
        }
    }
    mudstate.debug_cmd = cmdsave;
}

void logged_out1(dbref executor, dbref caller, dbref enactor, int key, char *arg)
{
    // PUEBLOCLIENT affects all the player's connections.
    //
    if (key == CMD_PUEBLOCLIENT)
    {
        DESC *d;
        DESC_ITER_PLAYER(executor, d)
        {
            do_logged_out_internal(d, key, arg);
        }
        // Set the player's flag.
        //
        s_Html(executor);
        return;
    }

    // Other logged-out commands affect only the player's most recently
    // used connection.
    //
    DESC *d;
    DESC *dLatest = NULL;
    DESC_ITER_PLAYER(executor, d)
    {
        if (  dLatest == NULL
           || dLatest->last_time < d->last_time)
        {
            dLatest = d;
        }
    }
    if (dLatest != NULL)
    {
        do_logged_out_internal(dLatest, key, arg);
    }
}

void logged_out0(dbref executor, dbref caller, dbref enactor, int key)
{
    logged_out1(executor, caller, enactor, key, "");
}

void Task_ProcessCommand(void *arg_voidptr, int arg_iInteger)
{
    DESC *d = (DESC *)arg_voidptr;
    if (d)
    {
        CBLK *t = d->input_head;
        if (t)
        {
            if (d->quota > 0)
            {
                d->quota--;
                d->input_head = (CBLK *) t->hdr.nxt;
                if (d->input_head)
                {
                    // There are still commands to process, so schedule another looksee.
                    //
                    scheduler.DeferImmediateTask(PRIORITY_SYSTEM, Task_ProcessCommand, d, 0);
                }
                else
                {
                    d->input_tail = NULL;
                }
                d->input_size -= (strlen(t->cmd) + 1);
                d->last_time.GetUTC();
                if (d->program_data != NULL)
                {
                    handle_prog(d, t->cmd);
                }
                else
                {
                    do_command(d, t->cmd);
                }
                free_lbuf(t);
            }
            else
            {
                // Don't bother looking for more quota until at least this much time has past.
                //
                CLinearTimeAbsolute lsaWhen;
                lsaWhen.GetUTC();

                scheduler.DeferTask(lsaWhen + mudconf.timeslice, PRIORITY_SYSTEM, Task_ProcessCommand, d, 0);
            }
        }
    }
}

/* ---------------------------------------------------------------------------
 * site_check: Check for site flags in a site list.
 */

int site_check(struct in_addr host, SITE *site_list)
{
    SITE *this0;

    for (this0 = site_list; this0; this0 = this0->next)
    {
        if ((host.s_addr & this0->mask.s_addr) == this0->address.s_addr)
        {
            return this0->flag;
        }
    }
    return 0;
}

/* --------------------------------------------------------------------------
 * list_sites: Display information in a site list
 */

#define S_SUSPECT   1
#define S_ACCESS    2

static const char *stat_string(int strtype, int flag)
{
    const char *str;

    switch (strtype)
    {
    case S_SUSPECT:
        if (flag)
        {
            str = "Suspected";
        }
        else
        {
            str = "Trusted";
        }
        break;

    case S_ACCESS:
        switch (flag)
        {
        case H_FORBIDDEN:
            str = "Forbidden";
            break;

        case H_REGISTRATION:
            str = "Registration";
            break;

        case H_GUEST:
            str = "NoGuest";
            break;

        case H_NOSITEMON:
            str = "NoSiteMon";
            break;

        case 0:
            str = "Unrestricted";
            break;

        default:
            str = "Strange";
            break;
        }
        break;

    default:
        str = "Strange";
        break;
    }
    return str;
}

static void list_sites(dbref player, SITE *site_list, const char *header_txt, int stat_type)
{
    char *buff, *buff1, *str;
    SITE *this0;

    buff = alloc_mbuf("list_sites.buff");
    buff1 = alloc_sbuf("list_sites.addr");
    sprintf(buff, "----- %s -----", header_txt);
    notify(player, buff);
    notify(player, "Address              Mask                 Status");
    for (this0 = site_list; this0; this0 = this0->next)
    {
        str = (char *)stat_string(stat_type, this0->flag);
        strcpy(buff1, inet_ntoa(this0->mask));
        sprintf(buff, "%-20s %-20s %s", inet_ntoa(this0->address), buff1,
            str);
        notify(player, buff);
    }
    free_mbuf(buff);
    free_sbuf(buff1);
}

/* ---------------------------------------------------------------------------
 * list_siteinfo: List information about specially-marked sites.
 */

void list_siteinfo(dbref player)
{
    list_sites(player, mudstate.access_list, "Site Access", S_ACCESS);
    list_sites(player, mudstate.suspect_list, "Suspected Sites", S_SUSPECT);
}

/* ---------------------------------------------------------------------------
 * make_ulist: Make a list of connected user numbers for the LWHO function.
 */

void make_ulist(dbref player, char *buff, char **bufc, bool bPorts)
{
    DESC *d;
    if (bPorts)
    {
        make_port_ulist(player, buff, bufc);
    }
    else
    {
        ITL pContext;
        ItemToList_Init(&pContext, buff, bufc, '#');
        DESC_ITER_CONN(d)
        {
            if (  !See_Hidden(player)
               && Hidden(d->player))
            {
                continue;
            }
            if (!ItemToList_AddInteger(&pContext, d->player))
            {
                break;
            }
        }
        ItemToList_Final(&pContext);
    }
}

/* ---------------------------------------------------------------------------
 * find_connected_name: Resolve a playername from the list of connected
 * players using prefix matching.  We only return a match if the prefix
 * was unique.
 */

dbref find_connected_name(dbref player, char *name)
{
    DESC *d;
    dbref found = NOTHING;
    DESC_ITER_CONN(d)
    {
        if (  Good_obj(player)
           && !See_Hidden(player)
           && Hidden(d->player))
        {
            continue;
        }
        if (!string_prefix(Name(d->player), name))
        {
            continue;
        }
        if (  found != NOTHING
           && found != d->player)
        {
            return NOTHING;
        }
        found = d->player;
    }
    return found;
}

FUNCTION(fun_doing)
{
    if (is_rational(fargs[0]))
    {
        SOCKET s = mux_atol(fargs[0]);
        bool bFound = false;
        DESC *d;
        DESC_ITER_CONN(d)
        {
            if (d->descriptor == s)
            {
                bFound = true;
                break;
            }
        }
        if (  bFound
           && (  d->player == executor
              || Wizard_Who(executor)))
        {
            safe_str(d->doing, buff, bufc);
        }
        else
        {
            safe_nothing(buff, bufc);
        }
    }
    else
    {
        dbref victim = lookup_player(executor, fargs[0], true);
        if (victim == NOTHING)
        {
            safe_str("#-1 PLAYER DOES NOT EXIST", buff, bufc);
            return;
        }

        if (  Wizard_Who(executor)
           || !Hidden(victim))
        {
            DESC *d;
            DESC_ITER_CONN(d)
            {
                if (d->player == victim)
                {
                    safe_str(d->doing, buff, bufc);
                    return;
                }
            }
        }
        safe_str("#-1 NOT A CONNECTED PLAYER", buff, bufc);
    }
}

// ---------------------------------------------------------------------------
// fun_host: Return hostname of player or port descriptor.
// ---------------------------------------------------------------------------
FUNCTION(fun_host)
{
    if (!Wizard_Who(executor))
    {
        safe_noperm(buff, bufc);
        return;
    }

    bool isPort = is_rational(fargs[0]);
    bool bFound = false;
    DESC *d;
    if (isPort)
    {
        SOCKET s = mux_atol(fargs[0]);
        DESC_ITER_CONN(d)
        {
            if (d->descriptor == s)
            {
                bFound = true;
                break;
            }
        }
    }
    else
    {
        dbref victim = lookup_player(executor, fargs[0], true);
        if (victim == NOTHING)
        {
            safe_str("#-1 PLAYER DOES NOT EXIST", buff, bufc);
            return;
        }
        DESC_ITER_CONN(d)
        {
            if (d->player == victim)
            {
                bFound = true;
                break;
            }
        }
    }
    if (bFound)
    {
        char *hostname = ((d->username[0] != '\0') ?
            tprintf("%s@%s", d->username, d->addr) : d->addr);
        safe_str(hostname, buff, bufc);
        return;
    }
    if (isPort)
    {
        safe_str("#-1 NOT AN ACTIVE PORT", buff, bufc);
    }
    else
    {
        safe_str("#-1 NOT A CONNECTED PLAYER", buff, bufc);
    }
}

FUNCTION(fun_poll)
{
    safe_str(mudstate.doing_hdr, buff, bufc);
}

FUNCTION(fun_motd)
{
    safe_str(mudconf.motd_msg, buff, bufc);
}

// fetch_cmds - Retrieve Player's number of commands entered.
//
int fetch_cmds(dbref target)
{
    int sum = 0;
    bool bFound = false;

    DESC *d;
    DESC_ITER_PLAYER(target, d)
    {
        sum += d->command_count;
        bFound = true;
    }

    if (bFound)
    {
        return sum;
    }
    else
    {
        return -1;
    }
}

void ParseConnectionInfoString(char *pConnInfo, char *pFields[5])
{
    MUX_STRTOK_STATE tts;
    mux_strtok_src(&tts, pConnInfo);
    mux_strtok_ctl(&tts, " ");
    for (int i = 0; i < 5; i++)
    {
        pFields[i] = mux_strtok_parse(&tts);
    }
}

void fetch_ConnectionInfoFields(dbref target, long anFields[4])
{
    dbref aowner;
    int   aflags;
    char *pConnInfo = atr_get(target, A_CONNINFO, &aowner, &aflags);
    char *aFields[5];
    ParseConnectionInfoString(pConnInfo, aFields);

    for (int i = 0; i < 4; i++)
    {
        long result;
        if (  !aFields[i]
           || (result = mux_atol(aFields[i])) < 0)
        {
            result = 0;
        }
        anFields[i] = result;
    }
    free_lbuf(pConnInfo);
}

void put_ConnectionInfoFields
(
    dbref target,
    long anFields[4],
    CLinearTimeAbsolute &ltaLogout
)
{
    char *pConnInfo = alloc_lbuf("put_CIF");
    char *p = pConnInfo;
    for (int i = 0; i < 4; i++)
    {
        p += mux_ltoa(anFields[i], p);
        *p++ = ' ';
    }
    p += mux_i64toa(ltaLogout.ReturnSeconds(), p);
    *p++ = 0;

    atr_add_raw_LEN(target, A_CONNINFO, pConnInfo, p - pConnInfo);
    free_lbuf(pConnInfo);
}

long fetch_ConnectionInfoField(dbref target, int iField)
{
    dbref aowner;
    int   aflags;
    char *pConnInfo = atr_get(target, A_CONNINFO, &aowner, &aflags);
    char *aFields[5];
    ParseConnectionInfoString(pConnInfo, aFields);

    long result;
    if (  !aFields[iField]
       || (result = mux_atol(aFields[iField])) < 0)
    {
        result = 0;
    }
    free_lbuf(pConnInfo);
    return result;
}

#define CIF_LOGOUTTIME     4

CLinearTimeAbsolute fetch_logouttime(dbref target)
{
    dbref aowner;
    int   aflags;
    char *pConnInfo = atr_get(target, A_CONNINFO, &aowner, &aflags);
    char *aFields[5];
    ParseConnectionInfoString(pConnInfo, aFields);

    CLinearTimeAbsolute lta;
    if (aFields[CIF_LOGOUTTIME])
    {
        lta.SetSecondsString(aFields[CIF_LOGOUTTIME]);
    }
    else
    {
        lta.SetSeconds(0);
    }
    free_lbuf(pConnInfo);
    return lta;
}