mux2.0/game/
mux2.0/game/data/
mux2.0/src/tools/
// mail.cpp 
//
// $Id: mail.cpp,v 1.11 2000/09/20 22:49:28 sdennis Exp $
//
// This code was taken from Kalkin's DarkZone code, which was
// originally taken from PennMUSH 1.50 p10, and has been heavily modified
// since being included in MUX.
//
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"

#include <sys/types.h>
#include "db.h"
#include "interface.h"
#include "attrs.h"
#include "mail.h"
#include "alloc.h"
#include "htab.h"
#include "match.h"
#include "functions.h"
#ifdef RADIX_COMPRESSION
#include "radix.h"

// Buffers used for RADIX_COMPRESSION.
//
char msgbuff[LBUF_SIZE + (LBUF_SIZE >> 1) + 1];
char subbuff[LBUF_SIZE + (LBUF_SIZE >> 1) + 1];
char timebuff[LBUF_SIZE + (LBUF_SIZE >> 1) + 1];
#endif // RADIX_COMPRESSION

static int FDECL(sign, (int));
static void FDECL(do_mail_flags, (dbref, char *, mail_flag, int));
static void FDECL(send_mail, (dbref, dbref, const char *, const char *, int, mail_flag, int));
static int FDECL(player_folder, (dbref));
static int FDECL(parse_msglist, (char *, struct mail_selector *, dbref));
static int FDECL(mail_match, (struct mail *, struct mail_selector, int));
static int FDECL(parse_folder, (dbref, char *));
static char *FDECL(status_chars, (struct mail *));
static char *FDECL(status_string, (struct mail *));
void FDECL(add_folder_name, (dbref, int, char *));
static int FDECL(get_folder_number, (dbref, char *));
static char *FDECL(get_folder_name, (dbref, int));
static char *FDECL(mail_list_time, (const char *));
static char *FDECL(make_numlist, (dbref, char *));
static char *FDECL(make_namelist, (dbref, char *));
static void FDECL(mail_to_list, (dbref, char *, char *, char *, int, int));
static void FDECL(do_edit_msg, (dbref, char *, char *));
static void FDECL(do_mail_proof, (dbref));
void FDECL(do_mail_cc, (dbref, char *));
void FDECL(do_expmail_abort, (dbref));

#define SIZEOF_MALIAS 13
#define WIDTHOF_MALIASDESC 40
#define SIZEOF_MALIASDESC (WIDTHOF_MALIASDESC*2)

#define MAX_MALIAS_MEMBERSHIP 100
struct malias {
    int owner;
    char *name;
    char *desc;
    int desc_width; // The visual width of the Mail Alias Description.
    int numrecep;
    dbref list[MAX_MALIAS_MEMBERSHIP];
};

int ma_size = 0;
int ma_top = 0;

struct malias **malias;


/*
 * Handling functions for the database of mail messages. 
 */

/*
 * mail_db_grow - We keep a database of mail text, so if we send a message to
 * * more than one player, we won't have to duplicate the text.
 */

static void mail_db_grow(int newtop)
{
    int newsize, i;
    MENT *newdb;

    if (newtop <= mudstate.mail_db_top)
    {
        return;
    }
    if (newtop <= mudstate.mail_db_size)
    {
        for (i = mudstate.mail_db_top; i < newtop; i++)
        {
            mudstate.mail_list[i].count = 0;
            mudstate.mail_list[i].message = NULL;
        }
        mudstate.mail_db_top = newtop;
        return;
    }
    if (newtop <= mudstate.mail_db_size + 100)
    {
        newsize = mudstate.mail_db_size + 100;
    }
    else
    {
        newsize = newtop;
    }

    newdb = (MENT *)MEMALLOC((newsize + 1) * sizeof(MENT));
    ISOUTOFMEMORY(newdb);
    if (mudstate.mail_list)
    {
        mudstate.mail_list -= 1;
        memcpy(newdb, mudstate.mail_list, (mudstate.mail_db_top + 1) * sizeof(MENT));
        MEMFREE(mudstate.mail_list);
    }
    mudstate.mail_list = newdb + 1;
    newdb = NULL;

    for (i = mudstate.mail_db_top; i < newtop; i++)
    {
        mudstate.mail_list[i].count = 0;
        mudstate.mail_list[i].message = NULL;
    }
    mudstate.mail_db_top = newtop;
    mudstate.mail_db_size = newsize;
}

/*
 * make_mail_freelist - move the freelist to the first empty slot in the mail
 * * database.
 */

static void make_mail_freelist()
{
    int i;
    for (i = 0; i < mudstate.mail_db_top; i++)
    {
        if (mudstate.mail_list[i].message == NULL)
        {
            mudstate.mail_freelist = i;
            return;
        }
    }
    mail_db_grow(i + 1);
    mudstate.mail_freelist = i;
}

/*
 * add_mail_message - adds a new text message to the mail database, and returns
 * * a unique number for that message.
 */

static int add_mail_message(dbref player, char *message)
{
    int number;
    char *atrstr, *execstr, *msg, *bp, *str;
    int aflags;
    dbref aowner;

    if (!_stricmp(message, "clear"))
    {
        notify(player, "MAIL: You probably don't wanna send mail saying 'clear'.");
        return -1;
    }
    if (!mudstate.mail_list)
    {
        mail_db_grow(1);
    }

    // Add an extra bit of protection here.
    //
    while (mudstate.mail_list[mudstate.mail_freelist].message != NULL)
    {
        make_mail_freelist();
    }
    number = mudstate.mail_freelist;

    atrstr = atr_get(player, A_SIGNATURE, &aowner, &aflags);
    execstr = bp = alloc_lbuf("add_mail_message");
    str = atrstr;
    TinyExec(execstr, &bp, 0, player, player, EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, (char **)NULL, 0);
    *bp = '\0';
    msg = bp = alloc_lbuf("add_mail_message.2");
    str = message;
    TinyExec(msg, &bp, 0, player, player, EV_EVAL | EV_FCHECK | EV_NO_COMPRESS, &str, (char **)NULL, 0);
    *bp = '\0';

    char *pMessageBody = tprintf("%s %s", msg, execstr);
#ifdef RADIX_COMPRESSION
    int len = string_compress(pMessageBody, msgbuff);
    mudstate.mail_list[number].message = BufferCloneLen(msgbuff, len);
#else
    mudstate.mail_list[number].message = StringClone(pMessageBody);
#endif // RADIX_COMPRESSION
    free_lbuf(atrstr);
    free_lbuf(execstr);
    free_lbuf(msg);
    make_mail_freelist();
    return number;
}

/*
 * add_mail_message_nosig - used for reading in old style messages from disk 
 */

static int add_mail_message_nosig(char *message)
{
    int number = mudstate.mail_freelist;
    if (!mudstate.mail_list)
    {
        mail_db_grow(1);
    }
    if (strlen(message) > LBUF_SIZE-1)
    {
        STARTLOG(LOG_BUGS, "BUG", "MAIL");
        log_text(tprintf("add_mail_message_nosig: Mail message %d truncated.", number));
        ENDLOG;
        message[LBUF_SIZE-1] = '\0';
    }
#ifdef RADIX_COMPRESSION
    int len = string_compress(message, msgbuff);
    mudstate.mail_list[number].message = BufferCloneLen(msgbuff, len);
#else
    mudstate.mail_list[number].message = StringClone(message);
#endif // RADIX_COMPRESSION
    make_mail_freelist();
    return number;
}

/*
 * new_mail_message - used for reading messages in from disk which already have
 * * a number assigned to them.
 */

#ifdef RADIX_COMPRESSION
static void new_mail_message(char *message, int number, int len)
#else
static void new_mail_message(char *message, int number)
#endif
{
    if (strlen(message) > LBUF_SIZE-1)
    {
        STARTLOG(LOG_BUGS, "BUG", "MAIL");
        log_text(tprintf("new_mail_message: Mail message %d truncated.", number));
        ENDLOG;
        message[LBUF_SIZE-1] = '\0';
    }
#ifdef RADIX_COMPRESSION
    mudstate.mail_list[number].message = BufferCloneLen(message, len);
#else
    mudstate.mail_list[number].message = StringClone(message);
#endif
}

/*
 * add_count - increments the reference count for any particular message 
 */

static DCL_INLINE void add_count(int number)
{
    mudstate.mail_list[number].count++;
}

/*
 * delete_mail_message - decrements the reference count for a message, and
 * * deletes the message if the counter reaches 0.
 */

static void delete_mail_message(int number)
{
    mudstate.mail_list[number].count--;

    if (mudstate.mail_list[number].count < 1)
    {
        MEMFREE(mudstate.mail_list[number].message);
        mudstate.mail_list[number].message = NULL;
        mudstate.mail_list[number].count = 0;
    }
}

/*
 * get_mail_message - returns the text for a particular number. This text
 * * should NOT be modified.
 */

char *get_mail_message(int number)
{
#ifdef RADIX_COMPRESSION
    static char buff[LBUF_SIZE];
#endif
    
    if (mudstate.mail_list[number].message != NULL)
    {
        return mudstate.mail_list[number].message;
    }
    else
    {
        delete_mail_message(number);
#ifdef RADIX_COMPRESSION
        string_compress("MAIL: This mail message does not exist in the database. Please alert your admin.", msgbuff);
        strcpy(buff, msgbuff);
        return buff;
#else       
        return "MAIL: This mail message does not exist in the database. Please alert your admin.";
#endif
    }
}

/*-------------------------------------------------------------------------*
 *   User mail functions (these are called from game.c)
 *
 * do_mail - cases 
 * do_mail_read - read messages
 * do_mail_list - list messages
 * do_mail_flags - tagging, untagging, clearing, unclearing of messages
 * do_mail_file - files messages into a new folder
 * do_mail_fwd - forward messages to another player(s)
 * do_mail_reply - reply to a message
 * do_mail_count - count messages
 * do_mail_purge - purge cleared messages
 * do_mail_change_folder - change current folder
 *-------------------------------------------------------------------------*/

/*
 * Change or rename a folder 
 */
void do_mail_change_folder(dbref player, char *fld, char *newname)
{
    int pfld;
    char *p;

    if (!fld || !*fld) {
        /*
         * Check mail in all folders 
         */
        for (pfld = MAX_FOLDERS; pfld >= 0; pfld--)
            check_mail(player, pfld, 1);

        pfld = player_folder(player);
        notify(player, tprintf("MAIL: Current folder is %d [%s].",
                       pfld, get_folder_name(player, pfld)));

        return;
    }
    pfld = parse_folder(player, fld);
    if (pfld < 0) {
        notify(player, "MAIL: What folder is that?");
        return;
    }
    if (newname && *newname)
    {
        /*
         * We're changing a folder name here 
         */
        if (strlen(newname) > FOLDER_NAME_LEN) {
            notify(player, "MAIL: Folder name too long");
            return;
        }
        for (p = newname; Tiny_IsAlphaNumeric[(unsigned char)*p]; p++) ;
        if (*p != '\0')
        {
            notify(player, "MAIL: Illegal folder name");
            return;
        }

        add_folder_name(player, pfld, newname);
        notify(player, tprintf("MAIL: Folder %d now named '%s'", pfld, newname));
    }
    else
    {
        /*
         * Set a new folder 
         */
        set_player_folder(player, pfld);
        notify(player, tprintf("MAIL: Current folder set to %d [%s].",
                       pfld, get_folder_name(player, pfld)));
    }
}

void do_mail_tag(dbref player, char *msglist)
{
    do_mail_flags(player, msglist, M_TAG, 0);
}

void do_mail_safe(dbref player, char *msglist)
{
    do_mail_flags(player, msglist, M_SAFE, 0);
}

void do_mail_clear(dbref player, char *msglist)
{
    do_mail_flags(player, msglist, M_CLEARED, 0);
}

void do_mail_untag(dbref player, char *msglist)
{
    do_mail_flags(player, msglist, M_TAG, 1);
}

void do_mail_unclear(dbref player, char *msglist)
{
    do_mail_flags(player, msglist, M_CLEARED, 1);
}

/*
 * adjust the flags of a set of messages 
 */

// if 1, clear the flag
static void do_mail_flags(dbref player, char *msglist, mail_flag flag, int negate)
{
    struct mail *mp;
    struct mail_selector ms;
    int i = 0, j = 0, folder;

    if (!parse_msglist(msglist, &ms, player))
    {
        return;
    }
    folder = player_folder(player);
    for (mp = (struct mail *)hashfindLEN(&player, sizeof(player), &mudstate.mail_htab); mp; mp = mp->next)
    {
        if (All(ms) || (Folder(mp) == folder))
        {
            i++;
            if (mail_match(mp, ms, i))
            {
                j++;
                if (negate)
                {
                    mp->read &= ~flag;
                }
                else
                {
                    mp->read |= flag;
                }

                switch (flag)
                {
                case M_TAG:
                    notify(player, tprintf("MAIL: Msg #%d %s.", i, negate ? "untagged" : "tagged"));
                    break;

                case M_CLEARED:
                    if (Unread(mp) && !negate)
                    {
                        notify(player, tprintf("MAIL: Unread Msg #%d cleared! Use @mail/unclear %d to recover.", i, i));
                    }
                    else
                    {
                        notify(player, tprintf("MAIL: Msg #%d %s.", i, negate ? "uncleared" : "cleared"));
                    }
                    break;

                case M_SAFE:
                    notify(player, tprintf("MAIL: Msg #%d marked safe.", i));
                    break;
                }
            }
        }
    }

    if (!j)
    {
        // Ran off the end of the list without finding anything.
        //
        notify(player, "MAIL: You don't have any matching messages!");
    }
}

/*
 * Change a message's folder 
 */

void do_mail_file(dbref player, char *msglist, char *folder)
{
    struct mail *mp;
    struct mail_selector ms;
    int foldernum, origfold;
    int i = 0, j = 0;

    if (!parse_msglist(msglist, &ms, player))
    {
        return;
    }
    if ((foldernum = parse_folder(player, folder)) == -1)
    {
        notify(player, "MAIL: Invalid folder specification");
        return;
    }
    origfold = player_folder(player);
    for (mp = (struct mail *)hashfindLEN(&player, sizeof(player), &mudstate.mail_htab); mp; mp = mp->next)
    {
        if (All(ms) || (Folder(mp) == origfold))
        {
            i++;
            if (mail_match(mp, ms, i))
            {
                j++;
                
                // Clear the folder.
                //
                mp->read &= M_FMASK;
                mp->read |= FolderBit(foldernum);
                notify(player, tprintf("MAIL: Msg %d filed in folder %d", i, foldernum));
            }
        }
    }

    if (!j)
    {
        // Ran off the end of the list without finding anything.
        //
        notify(player, "MAIL: You don't have any matching messages!");
    }
}

/*
 * print a mail message(s) 
 */

void do_mail_read(dbref player, char *msglist)
{
    struct mail *mp;
    char *buff, *status, *names;
    struct mail_selector ms;
    int i = 0, j = 0, folder;

    if (!parse_msglist(msglist, &ms, player))
    {
        return;
    }
    buff = alloc_lbuf("do_mail_read.1");
    folder = player_folder(player);
    mp = (struct mail *)hashfindLEN( &player,
                                     sizeof(player),
                                     &mudstate.mail_htab);
    for ( ; mp; mp = mp->next)
    {
        if (Folder(mp) == folder)
        {
            i++;
            if (mail_match(mp, ms, i))
            {
                // Read it.
                //
                j++;
#ifdef RADIX_COMPRESSION
                string_decompress(get_mail_message(mp->number), buff);
#else
                buff[LBUF_SIZE-1] = '\0';
                strncpy(buff, get_mail_message(mp->number), LBUF_SIZE);
                if (buff[LBUF_SIZE-1] != '\0')
                {
                    STARTLOG(LOG_BUGS, "BUG", "MAIL");
                    log_text(tprintf("do_mail_read: %s: Mail message %d truncated.", Name(player), mp->number));
                    ENDLOG;
                    buff[LBUF_SIZE-1] = '\0';
                }
#endif // RADIX_COMPRESSION
                notify(player, DASH_LINE);
                status = status_string(mp);
                names = make_namelist(player, (char *)mp->tolist);
#ifdef RADIX_COMPRESSION
                string_decompress(mp->subject, subbuff);
                string_decompress(mp->time, timebuff);
                notify(player, tprintf("%-3d         From:  %-*s  At: %-25s  %s\r\nFldr   : %-2d Status: %s\r\nTo     : %-65s\r\nSubject: %-65s",
                   i, PLAYER_NAME_LIMIT - 6, Name(mp->from),
                               timebuff,
                             (Connected(mp->from) &&
                  (!Hidden(mp->from) || Hasprivs(player))) ?
                           " (Conn)" : "      ", folder,
                               status,
                               names,
                               subbuff));
#else
                notify(player, tprintf("%-3d         From:  %-*s  At: %-25s  %s\r\nFldr   : %-2d Status: %s\r\nTo     : %-65s\r\nSubject: %-65s",
                   i, PLAYER_NAME_LIMIT - 6, Name(mp->from),
                               mp->time,
                             (Connected(mp->from) &&
                  (!Hidden(mp->from) || Hasprivs(player))) ?
                           " (Conn)" : "      ", folder,
                               status,
                               names,
                               mp->subject));
#endif // RADIX_COMPRESSION
                free_lbuf(names);
                free_lbuf(status);
                notify(player, DASH_LINE);
                notify(player, buff);
                notify(player, DASH_LINE);
                if (Unread(mp))
                {
                    // Mark message as read.
                    //
                    mp->read |= M_ISREAD;
                }
            }
        }
    }
    free_lbuf(buff);

    if (!j)
    {
        // Ran off the end of the list without finding anything.
        //
        notify(player, "MAIL: You don't have that many matching messages!");
    }
}

void do_mail_retract(dbref player, char *name, char *msglist)
{
    dbref target;
    struct mail *mp, *nextp;
    struct mail_selector ms;
    int i = 0, j = 0;

    target = lookup_player(player, name, 1);
    if (target == NOTHING)
    {
        notify(player, "MAIL: No such player.");
        return;
    }
    if (!parse_msglist(msglist, &ms, target))
    {
        return;
    }
    for (mp = (struct mail *)hashfindLEN(&target, sizeof(target), &mudstate.mail_htab); mp; mp = nextp)
    {
        if (mp->from == player)
        {
            i++;
            if (mail_match(mp, ms, i))
            {
                j++;
                if (Unread(mp))
                {
                    if (mp->prev == NULL)
                    {
                        if (mp->next == NULL)
                        {
                            hashdeleteLEN(&target, sizeof(target), &mudstate.mail_htab);
                        }
                        else
                        {
                            hashreplLEN(&target, sizeof(target), (int *)(mp->next), &mudstate.mail_htab);
                        }
                    }
                    else if (mp->next == NULL)
                        mp->prev->next = NULL;

                    if (mp->prev != NULL)
                        mp->prev->next = mp->next;
                    if (mp->next != NULL)
                        mp->next->prev = mp->prev;

                    nextp = mp->next;
                    MEMFREE((char *)mp->subject);
                    delete_mail_message(mp->number);
                    MEMFREE((char *)mp->time);
                    MEMFREE((char *)mp->tolist);
                    MEMFREE(mp);
                    notify(player, "MAIL: Mail retracted.");
                }
                else
                {
                    notify(player, "MAIL: That message has been read.");
                    nextp = mp->next;
                }
            }
            else
            {
                nextp = mp->next;
            }
        }
        else 
        {
            nextp = mp->next;
        }
    }

    if (!j)
    {
        /*
         * ran off the end of the list without finding anything 
         */
        notify(player, "MAIL: No matching messages.");
    }
}


void do_mail_review(dbref player, char *name, char *msglist)
{
    struct mail *mp;
    struct mail_selector ms;
    int i = 0, j = 0;
    dbref target;
    char *tbuf1, *msg, *status, *bp, *str;

    target = lookup_player(player, name, 1);
    if (target == NOTHING)
    {
        notify(player, "MAIL: No such player.");
        return;
    }
    if (!msglist || !*msglist)
    {
        notify(player, tprintf("--------------------   MAIL: %-25s   ------------------", Name(target)));
        for (mp = (struct mail *)hashfindLEN(&target, sizeof(target), &mudstate.mail_htab); mp; mp = mp->next)
        {
            if (mp->from == player)
            {
                i++;
                /*
                 * list it 
                 */
#ifdef RADIX_COMPRESSION
                string_decompress(get_mail_message(mp->number), msgbuff);
                string_decompress(mp->time, timebuff);
                string_decompress(mp->subject, subbuff);
                notify(player, tprintf("[%s] %-3d (%4d) From: %-*s Sub: %.25s",
                               status_chars(mp),
                               i, strlen(msgbuff),
                      PLAYER_NAME_LIMIT - 6, Name(mp->from),
                               subbuff));
#else
                notify(player, tprintf("[%s] %-3d (%4d) From: %-*s Sub: %.25s",
                               status_chars(mp),
                    i, strlen(get_mail_message(mp->number)),
                      PLAYER_NAME_LIMIT - 6, Name(mp->from),
                               mp->subject));
#endif /*
        * RADIX_COMPRESSION 
        */
            }
        }
        notify(player, DASH_LINE);
    }
    else
    {
        tbuf1 = alloc_lbuf("do_mail_read");

        if (!parse_msglist(msglist, &ms, target))
        {
            free_lbuf(tbuf1);
            return;
        }
        for (mp = (struct mail *)hashfindLEN(&target, sizeof(target), &mudstate.mail_htab); mp; mp = mp->next)
        {
            if (mp->from == player)
            {
                i++;
                if (mail_match(mp, ms, i))
                {
                    /*
                     * Read it 
                     */
                    j++;
                    status = status_string(mp);
#ifdef RADIX_COMPRESSION
                    string_decompress(get_mail_message(mp->number), msgbuff);
                    string_decompress(mp->time, timebuff);
                    string_decompress(mp->subject, subbuff);

                    msg = bp = alloc_lbuf("do_mail_review");
                    str = msgbuff;
                    TinyExec(msg, &bp, 0, player, player, EV_EVAL | EV_FCHECK | EV_NO_COMPRESS, &str, (char **)NULL, 0);
                    *bp = '\0';
                    notify(player, DASH_LINE);
                    notify(player, tprintf("%-3d         From:  %-*s  At: %-25s  %s\r\nFldr   : %-2d Status: %s\r\nSubject: %-65s",
                                   i, PLAYER_NAME_LIMIT - 6, Name(mp->from),
                                   timebuff,
                             (Connected(mp->from) &&
                              (!Hidden(mp->from) || Hasprivs(player))) ?
                            " (Conn)" : "      ", 0,
                              status, subbuff));
#else
                    msg = bp = alloc_lbuf("do_mail_review");
                    str = get_mail_message(mp->number);
                    TinyExec(msg, &bp, 0, player, player, EV_EVAL | EV_FCHECK | EV_NO_COMPRESS, &str, (char **)NULL, 0);
                    *bp = '\0';
                    notify(player, DASH_LINE);
                    notify(player, tprintf("%-3d         From:  %-*s  At: %-25s  %s\r\nFldr   : %-2d Status: %s\r\nSubject: %-65s",
                                   i, PLAYER_NAME_LIMIT - 6, Name(mp->from),
                                   mp->time,
                             (Connected(mp->from) &&
                              (!Hidden(mp->from) || Hasprivs(player))) ?
                            " (Conn)" : "      ", 0,
                              status, mp->subject));
#endif // RADIX_COMPRESSION 
                    free_lbuf(status);
                    notify(player, DASH_LINE);
                    strcpy(tbuf1, msg);
                    notify(player, tbuf1);
                    notify(player, DASH_LINE);
                    free_lbuf(msg);
                }
            }
        }

        if (!j)
        {
            /*
             * ran off the end of the list without finding * * *
             * anything 
             */
            notify(player, "MAIL: You don't have that many matching messages!");
        }
        free_lbuf(tbuf1);
    }
}

void do_mail_list(dbref player, char *msglist, int sub)
{
    struct mail *mp;
    struct mail_selector ms;
    int i = 0, folder;
    char *time;

    if (!parse_msglist(msglist, &ms, player)) {
        return;
    }
    folder = player_folder(player);

    notify(player, tprintf(
                      "---------------------------   MAIL: Folder %d   ----------------------------", folder));

    for (mp = (struct mail *)hashfindLEN(&player, sizeof(player), &mudstate.mail_htab); mp; mp = mp->next)
    {
        if (Folder(mp) == folder)
        {
            i++;
            if (mail_match(mp, ms, i))
            {
                /*
                 * list it 
                 */

#ifdef RADIX_COMPRESSION
                string_decompress(get_mail_message(mp->number), msgbuff);
                string_decompress(mp->subject, subbuff);
                string_decompress(mp->time, timebuff);
                time = mail_list_time(timebuff);
                if (sub)
                    notify(player, tprintf("[%s] %-3d (%4d) From: %-*s Sub: %.25s",
                               status_chars(mp),
                             i, strlen(msgbuff),
                                   PLAYER_NAME_LIMIT - 6, Name(mp->from),
                                   subbuff));
                else
                    notify(player, tprintf("[%s] %-3d (%4d) From: %-*s At: %s %s",
                               status_chars(mp),
                             i, strlen(msgbuff),
                                   PLAYER_NAME_LIMIT - 6, Name(mp->from),
                                   time,
                            ((Connected(mp->from) &&
                              (!Hidden(mp->from) || Hasprivs(player)))
                             ? "Conn" : " ")));
#else
                time = mail_list_time(mp->time);
                if (sub)
                    notify(player, tprintf("[%s] %-3d (%4d) From: %-*s Sub: %.25s",
                               status_chars(mp),
                                   i, strlen(get_mail_message(mp->number)),
                                   PLAYER_NAME_LIMIT - 6, Name(mp->from),
                                   mp->subject));
                else
                    notify(player, tprintf("[%s] %-3d (%4d) From: %-*s At: %s %s",
                               status_chars(mp),
                                   i, strlen(get_mail_message(mp->number)),
                                   PLAYER_NAME_LIMIT - 6, Name(mp->from),
                                   time,
                            ((Connected(mp->from) &&
                              (!Hidden(mp->from) || Hasprivs(player)))
                             ? "Conn" : " ")));
#endif /*
        * RADIX_COMPRESSION 
        */
                free_lbuf(time);
            }
        }
    }
    notify(player, DASH_LINE);
}

static char *mail_list_time(const char *the_time)
{
    char *new0;
    char *p, *q;
    int i;

    p = (char *)the_time;
    q = new0 = alloc_lbuf("mail_list_time");
    if (!p || !*p) {
        *new0 = '\0';
        return new0;
    }
    /*
     * Format of the_time is: day mon dd hh:mm:ss yyyy 
     */
    /*
     * Chop out :ss 
     */
    for (i = 0; i < 16; i++) {
        if (*p)
            *q++ = *p++;
    }

    for (i = 0; i < 3; i++) {
        if (*p)
            p++;
    }

    for (i = 0; i < 5; i++) {
        if (*p)
            *q++ = *p++;
    }

    *q = '\0';
    return new0;
}

void do_mail_purge(dbref player)
{
    struct mail *mp, *nextp;

    /*
     * Go through player's mail, and remove anything marked cleared 
     */
    for (mp = (struct mail *)hashfindLEN(&player, sizeof(player), &mudstate.mail_htab); mp; mp = nextp)
    {
        if (Cleared(mp)) 
        {
            /*
             * Delete this one 
             */
            /*
             * head and tail of the list are special 
             */
            if (mp->prev == NULL)
            {
                if (mp->next == NULL)
                {
                    hashdeleteLEN(&player, sizeof(player), &mudstate.mail_htab);
                }
                else
                {
                    hashreplLEN(&player, sizeof(player), (int *)(mp->next), &mudstate.mail_htab);
                }
            }
            else if (mp->next == NULL)
            {
                mp->prev->next = NULL;
            }

            /*
             * relink the list 
             */
            if (mp->prev != NULL)
                mp->prev->next = mp->next;
            if (mp->next != NULL)
                mp->next->prev = mp->prev;

            /*
             * save the pointer 
             */
            nextp = mp->next;

            /*
             * then wipe 
             */
            MEMFREE((char *)mp->subject);
            delete_mail_message(mp->number);
            MEMFREE((char *)mp->time);
            MEMFREE((char *)mp->tolist);
            MEMFREE(mp);
        }
        else
        {
            nextp = mp->next;
        }
    }
    notify(player, "MAIL: Mailbox purged.");
}

void do_mail_fwd(dbref player, char *msg, char *tolist)
{
    struct mail *mp;
    int num;

    if (Flags2(player) & PLAYER_MAILS)
    {
        notify(player, "MAIL: Mail message already in progress.");
        return;
    }
    if (!msg || !*msg)
    {
        notify(player, "MAIL: No message list.");
        return;
    }
    if (!tolist || !*tolist)
    {
        notify(player, "MAIL: To whom should I forward?");
        return;
    }
    num = Tiny_atol(msg);
    if (!num)
    {
        notify(player, "MAIL: I don't understand that message number.");
        return;
    }
    mp = mail_fetch(player, num);
    if (!mp)
    {
        notify(player, "MAIL: You can't forward non-existent messages.");
        return;
    }
#ifdef RADIX_COMPRESSION
    string_decompress(mp->subject, subbuff);
    string_decompress(get_mail_message(mp->number), msgbuff);
    do_expmail_start(player, tolist, tprintf("%s (fwd from %s)", subbuff, Name(mp->from)));
    atr_add_raw(player, A_MAILMSG, msgbuff);
#else
    do_expmail_start(player, tolist, tprintf("%s (fwd from %s)", mp->subject, Name(mp->from)));
    atr_add_raw(player, A_MAILMSG, get_mail_message(mp->number));
#endif
    char *pValue = atr_get_raw(player, A_MAILFLAGS);
    int iFlag = M_FORWARD;
    if (pValue)
    {
        iFlag |= Tiny_atol(pValue);
    }
    atr_add_raw(player, A_MAILFLAGS, Tiny_ltoa_t(iFlag));
}

void do_mail_reply(dbref player, char *msg, int all, int key)
{
    char *tolist, *bp, *p, *names, *oldlist;

    if (Flags2(player) & PLAYER_MAILS)
    {
        notify(player, "MAIL: Mail message already in progress.");
        return;
    }
    if (!msg || !*msg)
    {
        notify(player, "MAIL: No message list.");
        return;
    }
    int num = Tiny_atol(msg);
    if (!num)
    {
        notify(player, "MAIL: I don't understand that message number.");
        return;
    }
    struct mail *mp = mail_fetch(player, num);
    if (!mp)
    {
        notify(player, "MAIL: You can't reply to non-existent messages.");
        return;
    }
    if (all)
    {
        names = alloc_lbuf("do_mail_reply.names");
        oldlist = alloc_lbuf("do_mail_reply.oldlist");
        bp = names;
        *bp = '\0';

        strcpy(oldlist, (char *)mp->tolist);

        TINY_STRTOK_STATE tts;
        Tiny_StrTokString(&tts, oldlist);
        Tiny_StrTokControl(&tts, " ");
        for (p = Tiny_StrTokParse(&tts); p; p = Tiny_StrTokParse(&tts))
        {
            if (*p == '*')
            {
                safe_str(p, names, &bp);
                safe_chr(' ', names, &bp);
            }
            else if (Tiny_atol(p) != mp->from)
            {
                safe_chr('"', names, &bp);
                safe_str(Name(Tiny_atol(p)), names, &bp);
                safe_chr('"', names, &bp);
                safe_chr(' ', names, &bp);
            }
        }
        free_lbuf(oldlist);
        safe_chr(' ', names, &bp);
        safe_chr('"', names, &bp);
        safe_str(Name(mp->from), names, &bp);
        safe_chr('"', names, &bp);
        *bp = '\0';
        tolist = names;
    }
    else
    {
        tolist = msg;
    }
    
    const char *pSubject;
    char *pMessage;
    const char *pTime;
#ifdef RADIX_COMPRESSION
    string_decompress(mp->subject, subbuff);
    pSubject = subbuff;
    string_decompress(get_mail_message(mp->number), msgbuff);
    pMessage = msgbuff;
    string_decompress(mp->time, timebuff);
    pTime = timebuff;
#else
    pSubject = mp->subject;
    pMessage = get_mail_message(mp->number);
    pTime = mp->time;
#endif
    if (strncmp(pSubject, "Re:", 3))
    {
        do_expmail_start(player, tolist, tprintf("Re: %s", pSubject));
    }
    else
    {
        do_expmail_start(player, tolist, tprintf("%s", pSubject));
    }
    if (key & MAIL_QUOTE)
    {
        char *pMessageBody =
            tprintf("On %s, %s wrote:\r\n\r\n%s\r\n\r\n********** End of included message from %s\r\n",
                pTime, Name(mp->from), pMessage, Name(mp->from));
        atr_add_raw(player, A_MAILMSG, pMessageBody);
    }

    char *pValue = atr_get_raw(player, A_MAILFLAGS);
    int iFlag = M_REPLY;
    if (pValue)
    {
        iFlag |= Tiny_atol(pValue);
    }
    atr_add_raw(player, A_MAILFLAGS, Tiny_ltoa_t(iFlag));
    if (all)
    {
        free_lbuf(names);
    }
}

/*-------------------------------------------------------------------------*
 *   Admin mail functions
 *
 * do_mail_nuke - clear & purge mail for a player, or all mail in db.
 * do_mail_stat - stats on mail for a player, or for all db.
 * do_mail_debug - fix mail with a sledgehammer
 *-------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------*
 *   Basic mail functions
 *-------------------------------------------------------------------------*/
struct mail *mail_fetch(dbref player, int num)
{
    struct mail *mp;
    int i = 0;

    for (mp = (struct mail *)hashfindLEN(&player, sizeof(player), &mudstate.mail_htab); mp; mp = mp->next)
    {
        if (Folder(mp) == player_folder(player))
        {
            i++;
            if (i == num)
                return mp;
        }
    }
    return NULL;
}

/*
 * returns count of read, unread, & cleared messages as rcount, ucount,
 * * ccount 
 */

void count_mail(dbref player, int folder, int *rcount, int *ucount, int *ccount)
{
    struct mail *mp;
    int rc, uc, cc;
    
    cc = rc = uc = 0;
    for (mp = (struct mail *)hashfindLEN(&player, sizeof(player), &mudstate.mail_htab); mp; mp = mp->next)
    {
        if (Folder(mp) == folder)
        {
            if (Read(mp))
                rc++;
            else
                uc++;
            
            if (Cleared(mp))
                cc++;
        }
    }
    *rcount = rc;
    *ucount = uc;
    *ccount = cc;
}

void urgent_mail(dbref player, int folder, int *ucount)
{
    struct mail *mp;
    int uc;

    uc = 0;

    for (mp = (struct mail *)hashfindLEN(&player, sizeof(player), &mudstate.mail_htab); mp; mp = mp->next)
    {
        if (Folder(mp) == folder)
        {
            if (!(Read(mp)) && (Urgent(mp)))
                uc++;
        }
    }
    *ucount = uc;
}

static void send_mail(dbref player, dbref target, const char *tolist, const char *subject, int number, mail_flag flags, int silent)
{
    char tbuf1[30];

    if (Typeof(target) != TYPE_PLAYER)
    {
        notify(player, "MAIL: You cannot send mail to non-existent people.");
        delete_mail_message(number);
        return;
    }
    CLinearTimeAbsolute ltaNow;
    ltaNow.GetLocal();
    char *temp = ltaNow.ReturnDateString();
    strcpy(tbuf1, temp);

    // Initialize the appropriate fields.
    //
    struct mail *newp = (struct mail *)MEMALLOC(sizeof(struct mail));
    ISOUTOFMEMORY(newp);
    newp->to = target;
    newp->from = player;
    newp->tolist = StringClone(tolist);

    newp->number = number;
    add_count(number);

#ifdef RADIX_COMPRESSION
    int len = string_compress(tbuf1, timebuff);
    newp->time = BufferCloneLen(timebuff, len);
    len = string_compress(subject, subbuff);
    newp->subject = BufferCloneLen(subbuff, len);
#else
    newp->time = StringClone(tbuf1);
    newp->subject = StringClone(subject);
#endif // RADIX_COMPRESSION 

    // Send to folder 0
    //
    newp->read = flags & M_FMASK;

    // If this is the first message, it is the head and the tail.
    //
    BOOL bFailed = FALSE;
    struct mail *mptr = (struct mail *)hashfindLEN(&target, sizeof(target), &mudstate.mail_htab);
    if (mptr)
    {
        while (mptr->next != NULL)
        {
            mptr = mptr->next;
        }
        mptr->next = newp;
        newp->next = NULL;
        newp->prev = mptr;
    }
    else
    {
        hashaddLEN(&target, sizeof(target), (int *)newp, &mudstate.mail_htab);
        newp->next = NULL;
        newp->prev = NULL;
    }

    if (!bFailed)
    {
        /*
         * notify people 
         */
        if (!silent)
        {
            notify(player, tprintf("MAIL: You sent your message to %s.", Name(target)));
        }

        notify(target, tprintf("MAIL: You have a new message from %s.", Name(player)));
        did_it(player, target, A_MAIL, NULL, 0, NULL, A_AMAIL, NULL, NOTHING);
    }
}

void do_mail_nuke(dbref player)
{
    struct mail *mp, *nextp;
    dbref thing;

    if (!God(player))
    {
        notify(player, "The postal service issues a warrant for your arrest.");
        return;
    }
    /*
     * walk the list 
     */
    MAIL_ITER_SAFE(mp, thing, nextp)
    {
        nextp = mp->next;
        delete_mail_message(mp->number);
        MEMFREE((char *)mp->subject);
        MEMFREE((char *)mp->tolist);
        MEMFREE((char *)mp->time);
        MEMFREE(mp);
    }

    log_text(tprintf("** MAIL PURGE ** done by %s(#%d).",
             Name(player), (int)player));

    notify(player, "You annihilate the post office. All messages cleared.");
}

void do_mail_debug(dbref player, char *action, char *victim)
{
    dbref target, thing;
    struct mail *mp, *nextp;

    if (!Wizard(player))
    {
        notify(player, "Go get some bugspray.");
        return;
    }
    if (string_prefix("clear", action))
    {
        target = lookup_player(player, victim, 1);
        if (target == NOTHING)
        {
            init_match(player, victim, NOTYPE);
            match_absolute();
            target = match_result();
        }
        if (target == NOTHING)
        {
            notify(player, tprintf("%s: no such player.", victim));
            return;
        }
        do_mail_clear(target, NULL);
        do_mail_purge(target);
        notify(player, tprintf("Mail cleared for %s(#%d).", Name(target), target));
        return;
    }
    else if (string_prefix("sanity", action))
    {
        MAIL_ITER_ALL(mp, thing)
        {
            if (!Good_obj(mp->to))
                notify(player, tprintf("Bad object #%d has mail.", mp->to));
            else if (Typeof(mp->to) != TYPE_PLAYER)
                notify(player, tprintf("%s(#%d) has mail but is not a player.",
                             Name(mp->to), mp->to));
        }
        notify(player, "Mail sanity check completed.");
    }
    else if (string_prefix("fix", action))
    {
        MAIL_ITER_SAFE(mp, thing, nextp)
        {
            if (!Good_obj(mp->to) || (Typeof(mp->to) != TYPE_PLAYER))
            {
                notify(player, tprintf("Fixing mail for #%d.", mp->to));
                /*
                 * Delete this one 
                 */
                /*
                 * head and tail of the list are * special 
                 */
                if (mp->prev == NULL)
                {
                    if (mp->next == NULL)
                    {
                        hashdeleteLEN(&player, sizeof(player), &mudstate.mail_htab);
                    }
                    else
                    {
                        hashreplLEN(&player, sizeof(player), (int *)(mp->next), &mudstate.mail_htab);
                    }
                }
                else if (mp->next == NULL)
                {
                    mp->prev->next = NULL;
                }

                /*
                 * relink the list 
                 */
                if (mp->prev != NULL)
                    mp->prev->next = mp->next;
                if (mp->next != NULL)
                    mp->next->prev = mp->prev;
                /*
                 * save the pointer 
                 */
                nextp = mp->next;
                /*
                 * then wipe 
                 */
                MEMFREE((char *)mp->subject);
                delete_mail_message(mp->number);
                MEMFREE((char *)mp->time);
                MEMFREE((char *)mp->tolist);
                MEMFREE(mp);
            } else
                nextp = mp->next;
        }
        notify(player, "Mail sanity fix completed.");
    } else {
        notify(player, "That is not a debugging option.");
        return;
    }
}

void do_mail_stats(dbref player, char *name, int full)
{
    dbref target, thing;
    int fc, fr, fu, tc, tr, tu, fchars, tchars, cchars, count;
    char last[50];
    struct mail *mp;
#ifdef RADIX_COMPRESSION
    int len;
#endif

    fc = fr = fu = tc = tr = tu = cchars = fchars = tchars = count = 0;

    /*
     * find player 
     */

    if ((*name == '\0') || !name) {
        if Wizard
            (player)
                target = AMBIGUOUS;
        else
            target = player;
    } else if (*name == NUMBER_TOKEN) {
        target = Tiny_atol(&name[1]);
        if (!Good_obj(target) || (Typeof(target) != TYPE_PLAYER))
            target = NOTHING;
    } else if (!_stricmp(name, "me")) {
        target = player;
    } else {
        target = lookup_player(player, name, 1);
    }

    if (target == NOTHING) {
        init_match(player, name, NOTYPE);
        match_absolute();
        target = match_result();
    }
    if (target == NOTHING) {
        notify(player, tprintf("%s: No such player.", name));
        return;
    }
    if (!Wizard(player) && (target != player)) {
        notify(player, "The post office protects privacy!");
        return;
    }
    /*
     * this comand is computationally expensive 
     */

    if (!payfor(player, mudconf.searchcost)) {
        notify(player, tprintf("Finding mail stats costs %d %s.",
                       mudconf.searchcost,
                       (mudconf.searchcost == 1) ? mudconf.one_coin : mudconf.many_coins));
        return;
    }
    if (target == AMBIGUOUS) {  /*
                     * stats for all 
                     */
        if (full == 0) {
            MAIL_ITER_ALL(mp, thing) {
                count++;
            }
            notify(player,
                   tprintf("There are %d messages in the mail spool.", count));
            return;
        } else if (full == 1) {
            MAIL_ITER_ALL(mp, thing) {
                if (Cleared(mp))
                    fc++;
                else if (Read(mp))
                    fr++;
                else
                    fu++;
            }
            notify(player,
                   tprintf("MAIL: There are %d msgs in the mail spool, %d unread, %d cleared.",
                       fc + fr + fu, fu, fc));
            return;
        } else {
            MAIL_ITER_ALL(mp, thing) {
#ifdef RADIX_COMPRESSION
                if (Cleared(mp)) {
                    fc++;
                    len = string_decompress(get_mail_message(mp->number), msgbuff);
                    cchars += len;
                } else if (Read(mp)) {
                    fr++;
                    len = string_decompress(get_mail_message(mp->number), msgbuff);
                    fchars += len;
                } else {
                    fu++;
                    len = string_decompress(get_mail_message(mp->number), msgbuff);
                    tchars += len;
                }
#else
                if (Cleared(mp)) {
                    fc++;
                    cchars += strlen(get_mail_message(mp->number));
                } else if (Read(mp)) {
                    fr++;
                    fchars += strlen(get_mail_message(mp->number));
                } else {
                    fu++;
                    tchars += strlen(get_mail_message(mp->number));
                }
#endif /*
        * RADIX_COMPRESSION 
        */
            }
            notify(player, tprintf("MAIL: There are %d old msgs in the mail spool, totalling %d characters.", fr, fchars));
            notify(player, tprintf("MAIL: There are %d new msgs in the mail spool, totalling %d characters.", fu, tchars));
            notify(player, tprintf("MAIL: There are %d cleared msgs in the mail spool, totalling %d characters.", fc, cchars));
            return;
        }
    }
    /*
     * individual stats 
     */

    if (full == 0) {
        /*
         * just count number of messages 
         */
        MAIL_ITER_ALL(mp, thing) {
            if (mp->from == target)
                fr++;
            if (mp->to == target)
                tr++;
        }
        notify(player, tprintf("%s sent %d messages.",
                       Name(target), fr));
        notify(player, tprintf("%s has %d messages.", Name(target), tr));
        return;
    }
    /*
     * more detailed message count 
     */
    MAIL_ITER_ALL(mp, thing) {
        if (mp->from == target) {
            if (Cleared(mp))
                fc++;
            else if (Read(mp))
                fr++;
            else
                fu++;
            if (full == 2)
#ifdef RADIX_COMPRESSION
                len = string_compress(get_mail_message(mp->number), msgbuff);
            fchars += len;
#else
                fchars += strlen(get_mail_message(mp->number));
#endif /*
        * RADIX_COMPRESSION 
        */
        }
        if (mp->to == target) {
            if (!tr && !tu) {
#ifdef RADIX_COMPRESSION
                string_decompress(mp->time, timebuff);
                strcpy(last, timebuff);
#else
                strcpy(last, mp->time);
#endif /*
        * RADIX_COMPRESSION 
        */
            }
            if (Cleared(mp))
                tc++;
            else if (Read(mp))
                tr++;
            else
                tu++;
            if (full == 2) {
#ifdef RADIX_COMPRESSION
                len = string_decompress(get_mail_message(mp->number), msgbuff);
                tchars += len;
#else
                tchars += strlen(get_mail_message(mp->number));
#endif /*
        * RADIX_COMPRESSION 
        */
            }
        }
    }

    notify(player, tprintf("Mail statistics for %s:", Name(target)));

    if (full == 1) {
        notify(player, tprintf("%d messages sent, %d unread, %d cleared.",
                       fc + fr + fu, fu, fc));
        notify(player, tprintf("%d messages received, %d unread, %d cleared.",
                       tc + tr + tu, tu, tc));
    } else {
        notify(player,
               tprintf("%d messages sent, %d unread, %d cleared, totalling %d characters.",
                   fc + fr + fu, fu, fc, fchars));
        notify(player,
               tprintf("%d messages received, %d unread, %d cleared, totalling %d characters.",
                   tc + tr + tu, tu, tc, tchars));
    }

    if (tc + tr + tu > 0)
        notify(player, tprintf("Last is dated %s", last));
}


/*-------------------------------------------------------------------------*
 *   Main mail routine for @mail w/o a switch
 *-------------------------------------------------------------------------*/

void do_mail_stub(dbref player, char *arg1, char *arg2)
{

    if (!arg1 || !*arg1)
    {
        if (arg2 && *arg2)
        {
            notify(player, "MAIL: Invalid mail command.");
            return;
        }
        /*
         * just the "@mail" command 
         */
        do_mail_list(player, arg1, 1);
        return;
    }
    /*
     * purge a player's mailbox 
     */
    if (!_stricmp(arg1, "purge")) {
        do_mail_purge(player);
        return;
    }
    /*
     * clear message 
     */
    if (!_stricmp(arg1, "clear")) {
        do_mail_clear(player, arg2);
        return;
    }
    if (!_stricmp(arg1, "unclear")) {
        do_mail_unclear(player, arg2);
        return;
    }
    if (arg2 && *arg2)
    {
        /*
         * Sending mail 
         */
        do_expmail_start(player, arg1, arg2);
        return;
    }
    else
    {
        /*
         * Must be reading or listing mail - no arg2 
         */
        if (Tiny_IsDigit[(unsigned char)*arg1] && !strchr(arg1, '-'))
            do_mail_read(player, arg1);
        else
            do_mail_list(player, arg1, 1);
        return;
    }
}


void do_mail(dbref player, dbref cause, int key, char *arg1, char *arg2)
{
    if (!mudconf.have_mailer) {
        notify(player, "Mailer is disabled.");
        return;
    }
    switch (key & ~MAIL_QUOTE) {
    case 0:
        do_mail_stub(player, arg1, arg2);
        break;
    case MAIL_STATS:
        do_mail_stats(player, arg1, 0);
        break;
    case MAIL_DSTATS:
        do_mail_stats(player, arg1, 1);
        break;
    case MAIL_FSTATS:
        do_mail_stats(player, arg1, 2);
        break;
    case MAIL_DEBUG:
        do_mail_debug(player, arg1, arg2);
        break;
    case MAIL_NUKE:
        do_mail_nuke(player);
        break;
    case MAIL_FOLDER:
        do_mail_change_folder(player, arg1, arg2);
        break;
    case MAIL_LIST:
        do_mail_list(player, arg1, 0);
        break;
    case MAIL_READ:
        do_mail_read(player, arg1);
        break;
    case MAIL_CLEAR:
        do_mail_clear(player, arg1);
        break;
    case MAIL_UNCLEAR:
        do_mail_unclear(player, arg1);
        break;
    case MAIL_PURGE:
        do_mail_purge(player);
        break;
    case MAIL_FILE:
        do_mail_file(player, arg1, arg2);
        break;
    case MAIL_TAG:
        do_mail_tag(player, arg1);
        break;
    case MAIL_UNTAG:
        do_mail_untag(player, arg1);
        break;
    case MAIL_FORWARD:
        do_mail_fwd(player, arg1, arg2);
        break;
    case MAIL_REPLY:
        do_mail_reply(player, arg1, 0, key);
        break;
    case MAIL_REPLYALL:
        do_mail_reply(player, arg1, 1, key);
        break;
    case MAIL_SEND:
        do_expmail_stop(player, 0);
        break;
    case MAIL_EDIT:
        do_edit_msg(player, arg1, arg2);
        break;
    case MAIL_URGENT:
        do_expmail_stop(player, M_URGENT);
        break;
    case MAIL_ALIAS:
        do_malias_create(player, arg1, arg2);
        break;
    case MAIL_ALIST:
        do_malias_list_all(player);
        break;
    case MAIL_PROOF:
        do_mail_proof(player);
        break;
    case MAIL_ABORT:
        do_expmail_abort(player);
        break;
    case MAIL_QUICK:
        do_mail_quick(player, arg1, arg2);
        break;
    case MAIL_REVIEW:
        do_mail_review(player, arg1, arg2);
        break;
    case MAIL_RETRACT:
        do_mail_retract(player, arg1, arg2);
        break;
    case MAIL_CC:
        do_mail_cc(player, arg1);
        break;
    case MAIL_SAFE:
        do_mail_safe(player, arg1);
        break;
    }
}

int dump_mail(FILE *fp)
{
    struct mail *mp, *mptr;
    dbref thing;
    int count = 0, i;

    /*
     * Write out version number 
     */
    fprintf(fp, "+V5\n");
    putref(fp, mudstate.mail_db_top);
    DO_WHOLE_DB(thing)
    {
        if (isPlayer(thing))
        {
            mptr = (struct mail *)hashfindLEN(&thing, sizeof(thing), &mudstate.mail_htab);
            if (mptr != NULL)
            {
                for (mp = mptr; mp; mp = mp->next)
                {
                    putref(fp, mp->to);
                    putref(fp, mp->from);
                    putref(fp, mp->number);
                    putstring(fp, mp->tolist);
#ifdef RADIX_COMPRESSION
                    string_decompress(mp->time, timebuff);
                    string_decompress(mp->subject, subbuff);
                    putstring(fp, timebuff);
                    putstring(fp, subbuff);
#else
                    putstring(fp, mp->time);
                    putstring(fp, mp->subject);
#endif /*
        * RADIX_COMPRESSION 
        */
                    putref(fp, mp->read);
                    count++;
                }
            }
        }
    }

    fprintf(fp, "*** END OF DUMP ***\n");

    /*
     * Add the db of mail messages 
     */
    for (i = 0; i < mudstate.mail_db_top; i++)
    {
        if (mudstate.mail_list[i].count > 0)
        {
            putref(fp, i);
#ifdef RADIX_COMPRESSION
            string_decompress(get_mail_message(i),msgbuff);
            putstring(fp, msgbuff);
#else
            putstring(fp, get_mail_message(i));
#endif
        }
    }
    fprintf(fp, "+++ END OF DUMP +++\n");

    save_malias(fp);

    return count;
}

int load_mail(FILE *fp)
{
    char nbuf1[8];
    int mail_top = 0;
    int new0 = 0;
    int pennsub = 0;
    int read_tolist = 0;
    int read_newdb = 0;
    int read_new_strings = 0;
#ifdef RADIX_COMPRESSION
    int len, number;
#endif

    // Read the version number.
    //
    fgets(nbuf1, sizeof(nbuf1), fp);

    if (!strncmp(nbuf1, "+V2", 3))
    {
        new0 = 1;
    }
    else if (!strncmp(nbuf1, "+V3", 3))
    {
        new0 = 1;
        read_tolist = 1;
    }
    else if (!strncmp(nbuf1, "+V4", 3))
    {
        new0 = 1;
        read_tolist = 1;
        read_newdb = 1;
    }
    else if (!strncmp(nbuf1, "+V5", 3))
    {
        new0 = 1;
        read_tolist = 1;
        read_newdb = 1;
        read_new_strings = 1;
    }
    else if (!strncmp(nbuf1, "+1", 2))
    {
        pennsub = 1;
    }
    if (pennsub)
    {
        // Toss away the number of messages.
        //
        fgets(nbuf1, sizeof(nbuf1), fp);
    }
    if (read_newdb)
    {
        mail_top = getref(fp);
        mail_db_grow(mail_top + 1);
    }
    else
    {
        mail_db_grow(1);
    }

    fgets(nbuf1, sizeof(nbuf1), fp);

    while (strncmp(nbuf1, "***", 3) != 0)
    {
        struct mail *mp = (struct mail *)MEMALLOC(sizeof(struct mail));
        ISOUTOFMEMORY(mp);

        dbref nTo = Tiny_atol(nbuf1);

        struct mail *mptr = (struct mail *)hashfindLEN(&nTo, sizeof(nTo), &mudstate.mail_htab);
        if (mptr)
        {
            // Find the end of the list the hard way.
            //
            while (mptr->next != NULL)
            {
                mptr = mptr->next;
            }
            mptr->next = mp;
            mp->prev = mptr;
        }
        else
        {
            mp->prev = NULL;
            hashaddLEN(&nTo, sizeof(nTo), (int *)mp, &mudstate.mail_htab);
        }
        mp->next = NULL;
        mp->to = nTo;
        mp->from = getref(fp);

        if (read_newdb)
        {
            mp->number = getref(fp);
            add_count(mp->number);
        }
        if (read_tolist)
        {
            mp->tolist = StringClone(getstring_noalloc(fp, read_new_strings));
        }
        else
        {
            mp->tolist = StringClone(Tiny_ltoa_t(mp->to));
        }

#ifdef RADIX_COMPRESSION
        len = string_compress(getstring_noalloc(fp, read_new_strings), timebuff);
        mp->time = BufferCloneLen(timebuff, len);
        if (pennsub)
        {
            len = string_compress(getstring_noalloc(fp, read_new_strings), subbuff);
            mp->subject = BufferCloneLen(subbuff, len);
        }
        else if (!new0)
        {
            len = string_compress("No subject", subbuff);
            mp->subject = BufferCloneLen(subbuff, len);
        }
        if (!read_newdb)
        {
            string_compress(getstring_noalloc(fp, read_new_strings), msgbuff);
            number = add_mail_message_nosig(msgbuff);
            add_count(number);
            mp->number = number;
        }
        if (new0)
        {
            len = string_compress(getstring_noalloc(fp, read_new_strings), subbuff);
            mp->subject = BufferCloneLen(subbuff, len);
        }
        else if (!pennsub)
        {
            len = string_compress("No subject", subbuff);
            mp->subject = BufferCloneLen(subbuff, len);
        }
#else
        mp->time = StringClone(getstring_noalloc(fp, read_new_strings));
        if (pennsub)
        {
            mp->subject = StringClone(getstring_noalloc(fp, read_new_strings));
        }
        else if (!new0)
        {
            mp->subject = StringClone("No subject");
        }

        if (!read_newdb)
        {
            int number = add_mail_message_nosig(getstring_noalloc(fp, read_new_strings));
            add_count(number);
            mp->number = number;
        }
        if (new0)
        {
            mp->subject = StringClone(getstring_noalloc(fp, read_new_strings));
        }
        else if (!pennsub)
        {
            mp->subject = StringClone("No subject");
        }
#endif // RADIX_COMPRESSION
        mp->read = getref(fp);
        fgets(nbuf1, sizeof(nbuf1), fp);
    }

    if (read_newdb)
    {
        fgets(nbuf1, sizeof(nbuf1), fp);

        while (strncmp(nbuf1, "+++", 3))
        {
            int number = Tiny_atol(nbuf1);
#ifdef RADIX_COMPRESSION
            len = string_compress(getstring_noalloc(fp, read_new_strings), msgbuff);
            new_mail_message(msgbuff, number, len);
#else
            new_mail_message(getstring_noalloc(fp, read_new_strings), number);
#endif
            fgets(nbuf1, sizeof(nbuf1), fp);
        }
    }
    load_malias(fp);
    return 1;
}

static int get_folder_number(dbref player, char *name)
{
    int aflags;
    dbref aowner;
    char *atrstr;
    char *str, *pat, *res, *p, *bp;

    /* Look up a folder name and return the appopriate number */
    
    atrstr = atr_get(player, A_MAILFOLDERS, &aowner, &aflags);
    if (!*atrstr)
    {
        free_lbuf(atrstr);
        return -1;
    }
    str = alloc_lbuf("get_folder_num_str");
    bp = pat = alloc_lbuf("get_folder_num_pat");

    strcpy(str, atrstr);
    safe_tprintf_str(pat, &bp, ":%s:", upcasestr(name));
    res = (char *)strstr(str, pat);
    if (!res)
    {
        free_lbuf(str);
        free_lbuf(pat);
        free_lbuf(atrstr);
        return -1;
    }
    res += 2 + strlen(name);
    p = res;
    while (!Tiny_IsSpace[(unsigned char)*p])
    {
        p++;
    }
    p = '\0';
    free_lbuf(atrstr);
    free_lbuf(str);
    free_lbuf(pat);
    return Tiny_atol(res);
}

static char *get_folder_name(dbref player, int fld)
{
    static char str[LBUF_SIZE];
    char *pat;
    static char *old;
    char *r, *atrstr;
    int flags, len;

    /*
     * Get the name of the folder, or "nameless" 
     */
    pat = alloc_lbuf("get_folder_name");
    sprintf(pat, "%d:", fld);
    old = NULL;
    atrstr = atr_get(player, A_MAILFOLDERS, &player, &flags);
    if (!*atrstr)
    {
        strcpy(str, "unnamed");
        free_lbuf(pat);
        free_lbuf(atrstr);
        return str;
    }
    strcpy(str, atrstr);
    old = (char *)string_match(str, pat);
    free_lbuf(atrstr);
    if (old)
    {
        r = old + strlen(pat);
        while (*r != ':')
        {
            r++;
        }
        *r = '\0';
        len = strlen(pat);
        free_lbuf(pat);
        return old + len;
    }
    else
    {
        strcpy(str, "unnamed");
        free_lbuf(pat);
        return str;
    }
}

void add_folder_name(dbref player, int fld, char *name)
{
    char *old, *res, *r, *atrstr;
    char *new0, *pat, *str, *tbuf;
    int aflags;

    /*
     * Muck with the player's MAILFOLDERS attrib to add a string of the * 
     * 
     * *  * * form: number:name:number to it, replacing any such string
     * with a  *  * * * matching number. 
     */

    new0 = alloc_lbuf("add_folder_name.new");
    pat = alloc_lbuf("add_folder_name.pat");
    str = alloc_lbuf("add_folder_name.str");
    tbuf = alloc_lbuf("add_folder_name.tbuf");

    sprintf(new0, "%d:%s:%d ", fld, upcasestr(name), fld);
    sprintf(pat, "%d:", fld);
    /*
     * get the attrib and the old string, if any 
     */
    old = NULL;

    atrstr = atr_get(player, A_MAILFOLDERS, &player, &aflags);
    if (*atrstr)
    {
        strcpy(str, atrstr);
        old = (char *)string_match(str, pat);
    }
    if (old && *old)
    {
        strcpy(tbuf, str);
        r = old;
        while (!Tiny_IsSpace[(unsigned char)*r])
        {
            r++;
        }
        *r = '\0';
        res = (char *)replace_string(old, new0, tbuf);
    }
    else
    {
        r = res = alloc_lbuf("mail_folders");
        if (*atrstr)
        {
            safe_str(str, res, &r);
        }
        safe_str(new0, res, &r);
        *r = '\0';
    }
    /*
     * put the attrib back 
     */
    atr_add(player, A_MAILFOLDERS, res, player, AF_MDARK | AF_WIZARD | AF_NOPROG | AF_LOCK);
    free_lbuf(str);
    free_lbuf(pat);
    free_lbuf(new0);
    free_lbuf(tbuf);
    free_lbuf(atrstr);
    free_lbuf(res);
}

static int player_folder(dbref player)
{
    /*
     * Return the player's current folder number. If they don't have one, 
     * 
     * *  * *  * * set * it to 0 
     */
    int flags, number;
    char *atrstr;

    atrstr = atr_pget(player, A_MAILCURF, &player, &flags);
    if (!*atrstr)
    {
        free_lbuf(atrstr);
        set_player_folder(player, 0);
        return 0;
    }
    number = Tiny_atol(atrstr);
    free_lbuf(atrstr);
    return number;
}

void set_player_folder(dbref player, int fnum)
{
    /*
     * Set a player's folder to fnum 
     */
    ATTR *a;
    char *tbuf1;
    
    tbuf1 = alloc_lbuf("set_player_folder");
    Tiny_ltoa(fnum, tbuf1);
    a = (ATTR *)atr_num(A_MAILCURF);
    if (a)
    {
        atr_add(player, A_MAILCURF, tbuf1, GOD, a->flags);
    }
    else
    {
        // Shouldn't happen, but... 
        //
        atr_add(player, A_MAILCURF, tbuf1, GOD, AF_ODARK | AF_WIZARD | AF_NOPROG | AF_LOCK);
    }
    free_lbuf(tbuf1);
}

static int parse_folder(dbref player, char *folder_string)
{
    int fnum;

    /*
     * Given a string, return a folder #, or -1 The string is just a * *
     * * number, * for now. Later, this will be where named folders are * 
     * *  * handled 
     */
    if (!folder_string || !*folder_string)
        return -1;
    if (Tiny_IsDigit[(unsigned char)*folder_string])
    {
        fnum = Tiny_atol(folder_string);
        if ((fnum < 0) || (fnum > MAX_FOLDERS))
            return -1;
        else
            return fnum;
    }
    /*
     * Handle named folders here 
     */
    return get_folder_number(player, folder_string);
}

static int mail_match(struct mail *mp, struct mail_selector ms, int num)
{
    mail_flag mpflag;

    // Does a piece of mail match the mail_selector?
    //
    if (ms.low && num < ms.low)
        return 0;
    if (ms.high && num > ms.high)
        return 0;
    if (ms.player && mp->from != ms.player)
        return 0;
    mpflag = Read(mp) ? mp->read : (mp->read | M_MSUNREAD);
    if (!(ms.flags & M_ALL) && !(ms.flags & mpflag))
        return 0;

    if (ms.days == -1) return 1;

    // Get the time now, subtract mp->time, and compare the results with
    // ms.days (in manner of ms.day_comp)
    //
    CLinearTimeAbsolute ltaNow;
    ltaNow.GetLocal();

#ifdef RADIX_COMPRESSION
    string_decompress(mp->time, timebuff);
    const char *pMailTimeStr = timebuff;
#else
    const char *pMailTimeStr = mp->time;
#endif

    CLinearTimeAbsolute ltaMail;
    if (ltaMail.SetString(pMailTimeStr))
    {
        CLinearTimeDelta ltd(ltaMail, ltaNow);
        int iDiffDays = ltd.ReturnDays();
        if (sign(iDiffDays - ms.days) == ms.day_comp)
        {
            return 1;
        }
    }
    return 0;
}

static int parse_msglist(char *msglist, struct mail_selector *ms, dbref player)
{
    // Take a message list, and return the appropriate mail_selector setup.
    // For now, msglists are quite restricted. That'll change once all this
    // is working. Returns 0 if couldn't parse, and also notifies the player
    // why. 

    // Initialize the mail selector - this matches all messages.
    //
    ms->low = 0;
    ms->high = 0;
    ms->flags = 0x0FFF | M_MSUNREAD;
    ms->player = 0;
    ms->days = -1;
    ms->day_comp = 0;

    // Now, parse the message list.
    //
    if (!msglist || !*msglist)
    {
        // All messages 
        //
        return 1;
    }

    char *p = msglist;
    while (Tiny_IsSpace[(unsigned char)*p])
    {
        p++;
    }

    if (*p == '\0')
    {
        return 1;
    }

    if (Tiny_IsDigit[(unsigned char)*p])
    {
        // Message or range.
        //
        char *q = strchr(p, '-');
        if (q)
        {
            // We have a subrange, split it up and test to see if it is valid.
            //
            q++;
            ms->low = Tiny_atol(p);
            if (ms->low <= 0)
            {
                notify(player, "MAIL: Invalid message range");
                return 0;
            }
            if (*q == '\0')
            {
                // Unbounded range.
                //
                ms->high = 0;
            }
            else
            {
                ms->high = Tiny_atol(q);
                if (ms->low > ms->high)
                {
                    notify(player, "MAIL: Invalid message range");
                    return 0;
                }
            }
        }
        else
        {
            // A single message.
            //
            ms->low = ms->high = Tiny_atol(p);
            if (ms->low <= 0)
            {
                notify(player, "MAIL: Invalid message number");
                return 0;
            }
        }
    }
    else
    {
        switch (Tiny_ToUpper[(unsigned char)*p])
        {
        case '-':

            // Range with no start.
            //
            p++;
            if (*p == '\0')
            {
                notify(player, "MAIL: Invalid message range");
                return 0;
            }
            ms->high = Tiny_atol(p);
            if (ms->high <= 0)
            {
                notify(player, "MAIL: Invalid message range");
                return 0;
            }
            break;

        case '~':

            // Exact # of days old.
            //
            p++;
            if (*p == '\0')
            {
                notify(player, "MAIL: Invalid age");
                return 0;
            }
            ms->day_comp = 0;
            ms->days = Tiny_atol(p);
            if (ms->days < 0)
            {
                notify(player, "MAIL: Invalid age");
                return 0;
            }
            break;

        case '<':

            // Less than # of days old.
            //
            p++;
            if (*p == '\0')
            {
                notify(player, "MAIL: Invalid age");
                return 0;
            }
            ms->day_comp = -1;
            ms->days = Tiny_atol(p);
            if (ms->days < 0)
            {
                notify(player, "MAIL: Invalid age");
                return 0;
            }
            break;

        case '>':

            // Greater than # of days old.
            //
            p++;
            if (*p == '\0')
            {
                notify(player, "MAIL: Invalid age");
                return 0;
            }
            ms->day_comp = 1;
            ms->days = Tiny_atol(p);
            if (ms->days < 0)
            {
                notify(player, "MAIL: Invalid age");
                return 0;
            }
            break;

        case '#':

            // From db#.
            //
            p++;
            if (*p == '\0')
            {
                notify(player, "MAIL: Invalid dbref #");
                return 0;
            }
            ms->player = Tiny_atol(p);
            if (!Good_obj(ms->player) || !(ms->player))
            {
                notify(player, "MAIL: Invalid dbref #");
                return 0;
            }
            break;

        case '*':

            // From player name.
            //
            p++;
            if (*p == '\0')
            {
                notify(player, "MAIL: Invalid player");
                return 0;
            }
            ms->player = lookup_player(player, p, 1);
            if (ms->player == NOTHING)
            {
                notify(player, "MAIL: Invalid player");
                return 0;
            }
            break;

#if 0
        case 'A':

            // All messages, all folders
            //
            ms->flags = M_ALL;
            break;
#endif

        case 'U':

            // Urgent, Unread
            //
            p++;
            if (*p == '\0')
            {
                notify(player, "MAIL: U is ambiguous (urgent or unread?)");
                return 0;
            }
            switch (Tiny_ToUpper[(unsigned char)*p])
            {
            case 'R':

                // Urgent
                //
                ms->flags = M_URGENT;
                break;

            case 'N':

                // Unread
                //
                ms->flags = M_MSUNREAD;
                break;

            default:
            
                // Bad
                //
                notify(player, "MAIL: Invalid message specification");
                return 0;
                break;
            }
            break;

        case 'R':

            // Read
            //
            ms->flags = M_ISREAD;
            break;

        case 'C':

            // Cleared.
            //
            ms->flags = M_CLEARED;
            break;

        case 'T':

            // Tagged.
            //
            ms->flags = M_TAG;
            break;

        case 'M':

            // Mass, me.
            //
            p++;
            if (*p == '\0')
            {
                notify(player, "MAIL: M is ambiguous (mass or me?)");
                return 0;
            }
            switch (Tiny_ToUpper[(unsigned char)*p])
            {
            case 'A':

                ms->flags = M_MASS;
                break;

            case 'E':

                ms->player = player;
                break;

            default:

                notify(player, "MAIL: Invalid message specification");
                return 0;
                break;
            }
            break;

        default:

            // Bad news.
            //
            notify(player, "MAIL: Invalid message specification");
            return 0;
            break;
        }
    }
    return 1;
}

void check_mail_expiration(void)
{
    struct mail *mp, *nextp;
    dbref thing;

    /*
     * negative values for expirations never expire 
     */
    if (0 > mudconf.mail_expiration)
        return;

    int expire_secs = mudconf.mail_expiration * 86400;

    CLinearTimeAbsolute ltaNow;
    ltaNow.GetLocal();

    CLinearTimeAbsolute ltaMail;
    MAIL_ITER_SAFE(mp, thing, nextp)
    {
        if (M_Safe(mp))
        {
            nextp = mp->next;
            continue;
        }

#ifdef RADIX_COMPRESSION
        string_decompress(mp->time, timebuff);
        const char *pMailTimeStr = timebuff;
#else
        const char *pMailTimeStr = mp->time;
#endif
        if (!ltaMail.SetString(pMailTimeStr))
        {
            nextp = mp->next;
            continue;
        }

        CLinearTimeDelta ltd(ltaMail, ltaNow);
        if (ltd.ReturnSeconds() <= expire_secs)
        {
            nextp = mp->next;
            continue;
        }

        // Delete this one.
        //
        // head and tail of the list are special.
        //
        dbref nTo = mp->to;
        if (mp->prev == NULL)
        {
            if (mp->next == NULL)
            {
                hashdeleteLEN(&nTo, sizeof(nTo), &mudstate.mail_htab);
            }
            else
            {
                hashreplLEN(&nTo, sizeof(nTo), (int *)(mp->next), &mudstate.mail_htab);
            }
        }
        else if (mp->next == NULL)
        {
            mp->prev->next = NULL;
        }
        /*
         * relink the list 
         */
        if (mp->prev != NULL)
            mp->prev->next = mp->next;
        if (mp->next != NULL)
            mp->next->prev = mp->prev;
        /*
         * save the pointer 
         */
        nextp = mp->next;

        /*
         * then wipe 
         */
        MEMFREE((char *)mp->subject);
        delete_mail_message(mp->number);
        MEMFREE((char *)mp->tolist);
        MEMFREE((char *)mp->time);
        MEMFREE(mp);
    }
}

static char *status_chars(struct mail *mp)
{
    /*
     * Return a short description of message flags 
     */
    static char res[10];

    char *p = res;
    *p++ = Read(mp) ? '-' : 'N';
    *p++ = M_Safe(mp) ? 'S' : '-';
    *p++ = Cleared(mp) ? 'C' : '-';
    *p++ = Urgent(mp) ? 'U' : '-';
    *p++ = Mass(mp) ? 'M' : '-';
    *p++ = Forward(mp) ? 'F' : '-';
    *p++ = Tagged(mp) ? '+' : '-';
    *p = '\0';
    return res;
}


#define NUM_MAILSTATUSTABLE 7
struct tag_mailstatusentry
{
    int nMask;
    char *pYes;
    int   nYes;
    char *pNo;
    int   nNo;
}
aMailStatusTable[NUM_MAILSTATUSTABLE] =
{
    { M_ISREAD,  "Read",    4, "Unread", 6 },
    { M_CLEARED, "Cleared", 7,  0,       0 },
    { M_URGENT,  "Urgent",  6,  0,       0 },
    { M_MASS,    "Mass",    4,  0,       0 },
    { M_FORWARD, "Fwd",     3,  0,       0 },
    { M_TAG,     "Tagged",  6,  0,       0 },
    { M_SAFE,    "Safe",    4,  0,       0 }
};

static char *status_string(struct mail *mp)
{
    // Return a longer description of message flags.
    //
    char *tbuf1 = alloc_lbuf("status_string");
    char *p = tbuf1;
    struct tag_mailstatusentry *mse = aMailStatusTable;
    for (int i = 0; i < NUM_MAILSTATUSTABLE; i++, mse++)
    {
        if (mp->read & mse->nMask)
        {
            if (p != tbuf1) *p++ = ' ';
            memcpy(p, mse->pYes, mse->nYes);
            p += mse->nYes;
        }
        else if (mse->pNo)
        {
            if (p != tbuf1) *p++ = ' ';
            memcpy(p, mse->pNo, mse->nNo);
            p += mse->nNo;
        }
    }
    *p++ = '\0';
    return tbuf1;
}

void check_mail(dbref player, int folder, int silent)
{
    // Check for new @mail
    //

    int rc;     // Read messages.
    int uc;     // Unread messages.
    int cc;     // Cleared messages.
    int gc;     // urgent messages.

    // Just count messages
    //
    count_mail(player, folder, &rc, &uc, &cc);
    urgent_mail(player, folder, &gc);
#ifdef MAIL_ALL_FOLDERS
    notify(player,
           tprintf("MAIL: %d messages in folder %d [%s] (%d unread, %d cleared).\r\n",
               rc + uc, folder, get_folder_name(player, folder), uc, cc));
#else
    if (rc + uc > 0)
    {
        notify(player, tprintf("MAIL: %d messages in folder %d [%s] (%d unread, %d cleared).", rc + uc, folder, get_folder_name(player, folder), uc, cc));
    }
    else if (!silent)
    {
        notify(player, tprintf("\r\nMAIL: You have no mail.\r\n"));
    }
    if (gc > 0)
    {
        notify(player, tprintf("URGENT MAIL: You have %d urgent messages in folder %d [%s].", gc, folder, get_folder_name(player, folder)));
    }
#endif
}

static int sign(int x)
{
    if (x == 0)
    {
        return 0;
    }
    else if (x < 0)
    {
        return -1;
    }
    else
    {
        return 1;
    }
}


void do_malias_switch(dbref player, char *a1, char *a2)
{
    if ((!a2 || !*a2) && !(!a1 || !*a1))
    {
        do_malias_list(player, a1);
    }
    else if ((!*a1 || !a1) && (!*a2 || !a2))
    {
        do_malias_list_all(player);
    }
    else
    {
        do_malias_create(player, a1, a2);
    }
}

void do_malias(dbref player, dbref cause, int key, char *arg1, char *arg2)
{
    if (!mudconf.have_mailer) {
        notify(player, "Mailer is disabled.");
        return;
    }
    switch (key) {
    case 0:
        do_malias_switch(player, arg1, arg2);
        break;
    case 1:
        do_malias_desc(player, arg1, arg2);
        break;
    case 2:
        do_malias_chown(player, arg1, arg2);
        break;
    case 3:
        do_malias_add(player, arg1, arg2);
        break;
    case 4:
        do_malias_remove(player, arg1, arg2);
        break;
    case 5:
        do_malias_delete(player, arg1);
        break;
    case 6:
        do_malias_rename(player, arg1, arg2);
        break;
    case 7:
        /*
         * empty 
         */
        break;
    case 8:
        do_malias_adminlist(player);
        break;
    case 9:
        do_malias_status(player);
    }
}

// A mail alias can be any combination of upper-case letters, lower-case
// letters, and digits. No leading digits. No symbols. No ANSI. Length is
// limited to SIZEOF_MALIAS-1. Case is preserved.
//
char *MakeCanonicalMailAlias
(
    char *pMailAlias,
    int *pnValidMailAlias,
    BOOL *pbValidMailAlias
)
{
    static char Buffer[SIZEOF_MALIAS];

    *pnValidMailAlias = 0;
    *pbValidMailAlias = FALSE;

    if (  !pMailAlias
       || !Tiny_IsAlpha[(unsigned char)pMailAlias[0]])
    {
        return NULL;
    }
    char *p = Buffer;

    *p++ = pMailAlias[0];
    pMailAlias += 1;
    int nLeft = (sizeof(Buffer)-1) - 1;

    while (*pMailAlias && nLeft)
    {
        if (  !Tiny_IsAlpha[(unsigned char)*pMailAlias]
           && !Tiny_IsDigit[(unsigned char)*pMailAlias])
        {
            return Buffer;
        }
        *p = *pMailAlias;
        p++;
        pMailAlias++;
        nLeft--;
    }
    *p = '\0';

    *pnValidMailAlias = p - Buffer;
    *pbValidMailAlias = TRUE;
    return Buffer;
}

// A mail alias description can be any combination of upper-case letters,
// lower-case letters, digits, blanks, and symbols. ANSI is permitted.
// Length is limited to SIZEOF_MALIASDESC-1. Visual width is limited to
// WIDTHOF_MALIASDESC. Case is preserved.
//
char *MakeCanonicalMailAliasDesc
(
    char *pMailAliasDesc,
    int *pnValidMailAliasDesc,
    BOOL *pbValidMailAliasDesc,
    int *pnVisualWidth
)
{
    *pnValidMailAliasDesc = 0;
    *pbValidMailAliasDesc = FALSE;
    *pnVisualWidth = 0;

    if (!pMailAliasDesc)
    {
        return NULL;
    }

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

    // Optimize/terminate any ANSI in the string.
    //
    static char szFittedMailAliasDesc[SIZEOF_MALIASDESC];
    *pnValidMailAliasDesc = ANSI_TruncateToField
                            ( Buffer,
                              SIZEOF_MALIASDESC,
                              szFittedMailAliasDesc,
                              WIDTHOF_MALIASDESC,
                              pnVisualWidth,
                              FALSE
                             );
    *pbValidMailAliasDesc = TRUE;
    return szFittedMailAliasDesc;
}

#define GMA_NOTFOUND    1
#define GMA_FOUND       2
#define GMA_INVALIDFORM 3

struct malias *get_malias(dbref player, char *alias, int *pnResult)
{
    *pnResult = GMA_INVALIDFORM;
    if (!alias)
    {
        return NULL;
    }
    if (alias[0] == '#')
    {
        if (ExpMail(player))
        {
            int x = Tiny_atol(alias + 1);
            if (x < 0 || x >= ma_top)
            {
                *pnResult = GMA_NOTFOUND;
                return NULL;
            }
            *pnResult = GMA_FOUND;
            return malias[x];
        }
    }
    else if (alias[0] == '*')
    {
        int  nValidMailAlias;
        BOOL bValidMailAlias;
        char *pValidMailAlias = MakeCanonicalMailAlias
                                (   alias+1,
                                    &nValidMailAlias,
                                    &bValidMailAlias
                                );

        if (bValidMailAlias)
        {
            for (int i = 0; i < ma_top; i++)
            {
                struct malias *m = malias[i];
                if (  m->owner == player
                   || m->owner == GOD
                   || ExpMail(player))
                {
                    if (!strcmp(pValidMailAlias, m->name))
                    {
                        // Found it!
                        //
                        *pnResult = GMA_FOUND;
                        return m;
                    }
                }
            }
            *pnResult = GMA_NOTFOUND;
        }
    }
    if (*pnResult == GMA_INVALIDFORM)
    {
        if (ExpMail(player))
        {
            notify(player, "MAIL: Mail aliases must be of the form *<name> or #<num>.");
        }
        else
        {
            notify(player, "MAIL: Mail aliases must be of the form *<name>.");
        }
    }
    return NULL;
}

void do_malias_send(dbref player, char *tolist, char *listto, char *subject, int number, mail_flag flags, int silent)
{
    dbref vic;
    int nResult;
    struct malias *m = get_malias(player, tolist, &nResult);
    if (nResult == GMA_INVALIDFORM)
    {
        char *p;
        p = tprintf
            (  "MAIL: I can't figure out from '%s' who you want to mail to.",
               tolist
             );
        notify(player, p);
        return;
    }
    else if (nResult == GMA_NOTFOUND)
    {
        notify(player, tprintf("MAIL: Alias '%s' not found.", tolist));
        return;
    }

    // Parse the player list.
    //
    int k;
    for (k = 0; k < m->numrecep; k++)
    {
        vic = m->list[k];

        if (Typeof(vic) == TYPE_PLAYER)
        {
            send_mail(player, m->list[k], listto, subject, number, flags, silent);
        }
        else
        {
            // Complain about it.
            //
            char *pMail = tprintf("Alias Error: Bad Player %d for %s", vic, tolist);
            int iMail = add_mail_message(player, pMail);
            send_mail(GOD, GOD, listto, subject, iMail, 0, silent);
        }
    }
}

void do_malias_create(dbref player, char *alias, char *tolist)
{
    char *head, *tail, spot;
    struct malias **nm;
    char *buff;
    int i = 0;
    dbref target;

    int nResult;
    struct malias *m = get_malias(player, alias, &nResult);
    if (nResult == GMA_INVALIDFORM)
    {
        notify(player, "MAIL: What alias do you want to create?.");
        return;
    }
    else if (nResult == GMA_FOUND)
    {
        notify(player,
           tprintf("MAIL: Mail Alias '%s' already exists.", alias));
        return;
    }

    if (!ma_size)
    {
        ma_size = MA_INC;
        malias = (struct malias **)MEMALLOC(sizeof(struct malias *) * ma_size);
        ISOUTOFMEMORY(malias);
    }
    else if (ma_top >= ma_size)
    {
        ma_size += MA_INC;
        nm = (struct malias **)MEMALLOC(sizeof(struct malias *) * (ma_size));
        ISOUTOFMEMORY(nm);

        for (i = 0; i < ma_top; i++)
        {
            nm[i] = malias[i];
        }
        MEMFREE(malias);
        malias = nm;
    }
    malias[ma_top] = (struct malias *)MEMALLOC(sizeof(struct malias));
    ISOUTOFMEMORY(malias[ma_top]);

    i = 0;

    // Parse the player list.
    //
    head = (char *)tolist;
    while (head && *head && (i < (MAX_MALIAS_MEMBERSHIP - 1)))
    {
        while (*head == ' ')
            head++;
        tail = head;
        while (*tail && (*tail != ' '))
        {
            if (*tail == '"')
            {
                head++;
                tail++;
                while (*tail && (*tail != '"'))
                    tail++;
            }
            if (*tail)
                tail++;
        }
        tail--;
        if (*tail != '"')
            tail++;
        spot = *tail;
        *tail = '\0';

        // Now locate a target.
        //
        if (!_stricmp(head, "me"))
            target = player;
        else if (*head == '#')
        {
            target = Tiny_atol(head + 1);
            if (!Good_obj(target))
                target = NOTHING;
        }
        else
        {
            target = lookup_player(player, head, 1);
        }
        if ((target == NOTHING) || (Typeof(target) != TYPE_PLAYER))
        {
            notify(player, "MAIL: No such player.");
        }
        else
        {
            buff = unparse_object(player, target, 0);
            notify(player,
            tprintf("MAIL: %s added to alias %s", buff, alias));
            malias[ma_top]->list[i] = target;
            i++;
            free_lbuf(buff);
        }

        // Get the next recip.
        //
        *tail = spot;
        head = tail;
        if (*head == '"')
            head++;
    }
    int  nValidMailAlias;
    BOOL bValidMailAlias;
    char *pValidMailAlias = MakeCanonicalMailAlias
                            (   alias+1,
                                &nValidMailAlias,
                                &bValidMailAlias
                            );

    if (!bValidMailAlias)
    {
        notify(player, "MAIL: Invalid mail alias.");
        return;
    }

    // The Mail Alias Description is a superset of the Mail Alias,
    // so, the following code is not necessary unless the specification
    // of the Mail Alias Description becomes more restrictive at some
    // future time.
    //
#if 0
    int  nValidMailAliasDesc;
    BOOL bValidMailAliasDesc;
    char *pValidMailAliasDesc = MakeCanonicalMailAliasDesc
                                (   alias+1,
                                    &nValidMailAliasDesc,
                                    &bValidMailAliasDesc
                                );

    if (!bValidMailAliasDesc)
    {
        notify(player, "MAIL: Invalid mail alias description.");
        break;
    }
#else
    char *pValidMailAliasDesc = pValidMailAlias;
    int nValidMailAliasDesc = nValidMailAlias;
#endif

    malias[ma_top]->list[i] = NOTHING;
    malias[ma_top]->name = StringCloneLen(pValidMailAlias, nValidMailAlias);
    malias[ma_top]->numrecep = i;
    malias[ma_top]->owner = player;
    malias[ma_top]->desc = StringCloneLen(pValidMailAliasDesc, nValidMailAliasDesc);
    ma_top++;

    notify(player, tprintf("MAIL: Alias set '%s' defined.", alias));
}

void do_malias_list(dbref player, char *alias)
{
    int nResult;
    struct malias *m = get_malias(player, alias, &nResult);
    if (nResult == GMA_NOTFOUND)
    {
        notify(player, tprintf("MAIL: Alias '%s' not found.", alias));
        return;
    }
    if (nResult != GMA_FOUND)
    {
        return;
    }
    if (!ExpMail(player) && (player != m->owner) && !(God(m->owner)))
    {
        notify(player, "MAIL: Permission denied.");
        return;
    }
    char *buff;
    char *bp;
    bp = buff = alloc_lbuf("do_malias_list");
    safe_tprintf_str(buff, &bp, "MAIL: Alias *%s: ", m->name);
    for (int i = m->numrecep - 1; i > -1; i--)
    {
        char *p = Name(m->list[i]);
        if (strchr(p, ' '))
        {
            safe_chr('"', buff, &bp);
            safe_str(p, buff, &bp);
            safe_chr('"', buff, &bp);
        }
        else
        {
            safe_str(p, buff, &bp);
        }
        safe_chr(' ', buff, &bp);
    }
    *bp = '\0';
        
    notify(player, buff);
    free_lbuf(buff);
}

char *Spaces(int n)
{
    static char buffer[42] = "                                         ";
    static int nLast = 0;
    buffer[nLast] = ' ';
    if (0 <= n && n < sizeof(buffer)-1)
    {
        buffer[n] = '\0';
        nLast = n;
    }
    return buffer;
}

void do_malias_list_all(dbref player)
{
    int i = 0;
    int notified = 0;

    for (i = 0; i < ma_top; i++)
    {
        struct malias *m = malias[i];
        if (  m->owner == GOD
           || m->owner == player
           || God(player))
        {
            if (!notified)
            {
                notify(player, "Name         Description                              Owner");
                notified++;
            }
            char *p = tprintf( "%-12s %s%s %-15.15s",
                               m->name,
                               m->desc,
                               Spaces(40 - m->desc_width),
                               Name(m->owner));
            notify(player, p);
        }
    }
    notify(player, "*****  End of Mail Aliases *****");
}

void load_malias(FILE *fp)
{
    char buffer[200];

    getref(fp);
    if (  fscanf(fp, "*** Begin %s ***\n", buffer) == 1
       && !strcmp(buffer, "MALIAS"))
    {
        malias_read(fp);
    }
    else
    {
        Log.WriteString("ERROR: Couldn't find Begin MALIAS.\n");
        return;
    }
}

void save_malias(FILE *fp)
{

    fprintf(fp, "*** Begin MALIAS ***\n");
    malias_write(fp);
}

// This function acts like fgets except that any data on the end of the 
// line past the buffer size is truncated instead of being returned on
// the next call.
//
int GetLineTrunc(char *Buffer, size_t nBuffer, FILE *fp)
{
    fgets(Buffer, nBuffer, fp);
    int lenBuffer = strlen(Buffer);
    if (Buffer[lenBuffer-1] != '\n')
    {
        // The line was too long for the buffer. Continue reading until the
        // end of the line.
        //
        char TruncBuffer[SBUF_SIZE];
        int lenTruncBuffer;
        do
        {
            fgets(TruncBuffer, sizeof(TruncBuffer), fp);
            lenTruncBuffer = strlen(TruncBuffer);
        }
        while (TruncBuffer[lenTruncBuffer-1] != '\n');
    }
    return lenBuffer;
}

void malias_read(FILE *fp)
{
    int i, j;
    char buffer[1000];
    struct malias *m;

    ma_top = getref(fp);
    if (ma_top <= 0)
    {
        ma_size = ma_top = 0;
        malias = NULL;
        return;
    }
    ma_size = ma_top;

    malias = (struct malias **)MEMALLOC(sizeof(struct malias *) * ma_size);
    ISOUTOFMEMORY(malias);

    for (i = 0; i < ma_top; i++)
    {
        m = (struct malias *)MEMALLOC(sizeof(struct malias));
        ISOUTOFMEMORY(m);

        malias[i] = m;

        // Format is: "%d %d\n", &(m->owner), &(m->numrecep)
        //
        fgets(buffer, sizeof(buffer), fp);
        char *p = strchr(buffer, ' ');
        m->owner = m->numrecep = 0;
        if (p)
        {
            m->owner = Tiny_atol(buffer);
            m->numrecep = Tiny_atol(p+1);
        }

        // The format of @malias name is "N:<name>\n".
        //
        int nLen = GetLineTrunc(buffer, sizeof(buffer), fp);
        buffer[nLen-1] = '\0'; // Get rid of trailing '\n'.
        int  nMailAlias;
        BOOL bMailAlias;
        char *pMailAlias = MakeCanonicalMailAlias( buffer+2,
                                                   &nMailAlias,
                                                   &bMailAlias);
        if (bMailAlias)
        {
            m->name = StringCloneLen(pMailAlias, nMailAlias);
        }
        else
        {
            m->name = StringCloneLen("Invalid", 7);
        }

        // The format of the description is "D:<description>\n"
        //
        nLen = GetLineTrunc(buffer, sizeof(buffer), fp);
        int  nMailAliasDesc;
        BOOL bMailAliasDesc;
        int  nVisualWidth;
        char *pMailAliasDesc = MakeCanonicalMailAliasDesc( buffer+2,
                                                           &nMailAliasDesc,
                                                           &bMailAliasDesc,
                                                           &nVisualWidth);
        if (bMailAliasDesc)
        {
            m->desc = StringCloneLen(pMailAliasDesc, nMailAliasDesc);
            m->desc_width = nVisualWidth;
        }
        else
        {
            m->desc = StringCloneLen("Invalid Desc", 12);
            m->desc_width = 12;
        }

        if (m->numrecep > 0)
        {
            for (j = 0; j < m->numrecep; j++)
            {
                m->list[j] = getref(fp);
            }
        }
        else
        {
            m->list[0] = 0;
        }
    }
}

void malias_write(FILE *fp)
{
    int i, j;
    struct malias *m;

    putref(fp, ma_top);
    for (i = 0; i < ma_top; i++)
    {
        m = malias[i];
        fprintf(fp, "%d %d\n", m->owner, m->numrecep);
        fprintf(fp, "N:%s\n", m->name);
        fprintf(fp, "D:%s\n", m->desc);
        for (j = 0; j < m->numrecep; j++)
        {
            putref(fp, m->list[j]);
        }
    }
}

void do_expmail_start(dbref player, char *arg, char *subject)
{
    if (!arg || !*arg)
    {
        notify(player, "MAIL: I do not know whom you want to mail.");
        return;
    }
    if (!subject || !*subject)
    {
        notify(player, "MAIL: No subject.");
        return;
    }
    if (Flags2(player) & PLAYER_MAILS)
    {
        notify(player, "MAIL: Mail message already in progress.");
        return;
    }
    char *tolist = make_numlist(player, arg);
    if (!tolist) return;

    atr_add_raw(player, A_MAILTO, tolist);
    atr_add_raw(player, A_MAILSUB, subject);
    atr_add_raw(player, A_MAILFLAGS, "0");
    atr_clr(player, A_MAILMSG);
    Flags2(player) |= PLAYER_MAILS;
    char *names = make_namelist(player, tolist);
    notify(player, tprintf("MAIL: You are sending mail to '%s'.", names));
    free_lbuf(names);
    free_lbuf(tolist);
}

void do_mail_cc(dbref player, char *arg)
{
    char *fulllist, *bp;
    char *names;

    if (!(Flags2(player) & PLAYER_MAILS))
    {
        notify(player, "MAIL: No mail message in progress.");
        return;
    }
    if (!arg || !*arg)
    {
        notify(player, "MAIL: I do not know whom you want to mail.");
        return;
    }
    char *tolist = make_numlist(player, arg);
    if (!tolist) return;
    fulllist = alloc_lbuf("do_mail_cc");
    bp = fulllist;

    safe_str(tolist, fulllist, &bp);
    char *pPlayerMailTo = atr_get_raw(player, A_MAILTO);
    if (pPlayerMailTo)
    {
        safe_chr(' ', fulllist, &bp);
        safe_str(pPlayerMailTo, fulllist, &bp);
    }
    *bp = '\0';

    atr_add_raw(player, A_MAILTO, fulllist);
    names = make_namelist(player, fulllist);
    notify(player, tprintf("MAIL: You are sending mail to '%s'.", names));
    free_lbuf(names);
    free_lbuf(tolist);
    free_lbuf(fulllist);
}

static char *make_namelist(dbref player, char *arg)
{
    char *names, *oldarg;
    char *bp, *p;

    oldarg = alloc_lbuf("make_namelist.oldarg");
    names = alloc_lbuf("make_namelist.names");
    bp = names;

    strcpy(oldarg, arg);

    TINY_STRTOK_STATE tts;
    Tiny_StrTokString(&tts, oldarg);
    Tiny_StrTokControl(&tts, " ");
    for (p = Tiny_StrTokParse(&tts); p; p = Tiny_StrTokParse(&tts))
    {
        if (*p == '*')
        {
            safe_str(p, names, &bp);
            safe_str(", ", names, &bp);
        }
        else
        {
            safe_str(Name(Tiny_atol(p)), names, &bp);
            safe_str(", ", names, &bp);
        }
    }
    *(bp - 2) = '\0';
    free_lbuf(oldarg);
    return names;
}

static char *make_numlist(dbref player, char *arg)
{
    char *head, *tail, spot;
    char *numbuf;
    char buf[MBUF_SIZE];
    char *numbp;
    struct malias *m;
    struct mail *temp;
    dbref target;
    int num;

    numbuf = alloc_lbuf("make_numlist");
    numbp = numbuf;
    *numbp = '\0';

    head = (char *)arg;
    while (head && *head)
    {
        while (*head == ' ')
        {
            head++;
        }

        tail = head;
        while (*tail && (*tail != ' '))
        {
            if (*tail == '"')
            {
                head++;
                tail++;
                while (*tail && (*tail != '"'))
                {
                    tail++;
                }
            }
            if (*tail)
            {
                tail++;
            }
        }
        tail--;
        if (*tail != '"')
        {
            tail++;
        }
        spot = *tail;
        *tail = 0;

        num = Tiny_atol(head);
        if (num)
        {
            temp = mail_fetch(player, num);
            if (!temp)
            {
                notify(player, "MAIL: You can't reply to nonexistent mail.");
                free_lbuf(numbuf);
                return NULL;
            }
            sprintf(buf, "%d ", temp->from);
            safe_str(buf, numbuf, &numbp);
        }
        else if (*head == '*')
        {
            int nResult;
            m = get_malias(player, head, &nResult);
            if (nResult == GMA_NOTFOUND)
            {
                notify(player, tprintf("MAIL: Alias '%s' does not exist.", head));
                free_lbuf(numbuf);
                return NULL;
            }
            else if (nResult == GMA_INVALIDFORM)
            {
                notify(player, tprintf("MAIL: '%s' is a badly-formed alias.", head));
                free_lbuf(numbuf);
                return NULL;
            }
            safe_str(head, numbuf, &numbp);
            safe_chr(' ', numbuf, &numbp);
        }
        else
        {
            target = lookup_player(player, head, 1);
            if (target != NOTHING)
            {
                sprintf(buf, "%d ", target);
                safe_str(buf, numbuf, &numbp);
            }
            else
            {
                notify(player, tprintf("MAIL: '%s' does not exist.", head));
                free_lbuf(numbuf);
                return NULL;
            }
        }

        // Get the next recip.
        //
        *tail = spot;
        head = tail;
        if (*head == '"')
        {
            head++;
        }
    }

    if (!*numbuf)
    {
        notify(player, "MAIL: No players specified.");
        free_lbuf(numbuf);
        return NULL;
    }
    else
    {
        *(numbp - 1) = '\0';
        return numbuf;
    }
}

void do_mail_quick(dbref player, char *arg1, char *arg2)
{
    char *buf, *bp;

    if (!arg1 || !*arg1)
    {
        notify(player, "MAIL: I don't know who you want to mail.");
        return;
    }
    if (!arg2 || !*arg2)
    {
        notify(player, "MAIL: No message.");
        return;
    }
    if (Flags2(player) & PLAYER_MAILS)
    {
        notify(player, "MAIL: Mail message already in progress.");
        return;
    }
    buf = alloc_lbuf("do_mail_quick");
    bp = buf;
    strcpy(bp, arg1);

    parse_to(&bp, '/', 1);

    if (!bp)
    {
        notify(player, "MAIL: No subject.");
        free_lbuf(buf);
        return;
    }
    mail_to_list(player, make_numlist(player, buf), bp, arg2, 0, 0);
    free_lbuf(buf);
}

void mail_to_list(dbref player, char *list, char *subject, char *message, int flags, int silent)
{
    char *head, *tail, spot, *tolist;
    dbref target;
    int number;

    if (!list)
    {
        return;
    }
    if (!*list)
    {
        free_lbuf(list);
        return;
    }
    tolist = alloc_lbuf("mail_to_list");
    strcpy(tolist, list);

    number = add_mail_message(player, message);

    head = list;
    while (head && *head)
    {
        while (*head == ' ')
        {
            head++;
        }

        tail = head;
        while (*tail && (*tail != ' '))
        {
            if (*tail == '"')
            {
                head++;
                tail++;
                while (*tail && (*tail != '"'))
                    tail++;
            }
            if (*tail)
                tail++;
        }
        tail--;
        if (*tail != '"')
        {
            tail++;
        }
        spot = *tail;
        *tail = 0;

        if (*head == '*')
        {
            do_malias_send(player, head, tolist, subject, number, flags, silent);
        }
        else
        {
            target = Tiny_atol(head);
            if (target != NOTHING)
            {
                send_mail(player, target, tolist, subject, number, flags, silent);
            }
        }

        // Get the next recip.
        //
        *tail = spot;
        head = tail;
        if (*head == '"')
        {
            head++;
        }
    }
    free_lbuf(tolist);
    free_lbuf(list);
}

void do_expmail_stop(dbref player, int flags)
{
    char *tolist, *mailsub, *mailmsg, *mailflags;
    dbref aowner;
    dbref aflags;

    tolist = atr_get(player, A_MAILTO, &aowner, &aflags);
    mailmsg = atr_get(player, A_MAILMSG, &aowner, &aflags);
    mailsub = atr_get(player, A_MAILSUB, &aowner, &aflags);
    mailflags = atr_get(player, A_MAILFLAGS, &aowner, &aflags);

    if (!*tolist || !*mailmsg || !(Flags2(player) & PLAYER_MAILS))
    {
        notify(player, "MAIL: No such message to send.");
        free_lbuf(tolist);
    }
    else
    {
        mail_to_list(player, tolist, mailsub, mailmsg, flags | Tiny_atol(mailflags), 0);
    }
    free_lbuf(mailflags);
    free_lbuf(mailmsg);
    free_lbuf(mailsub);
    Flags2(player) &= ~PLAYER_MAILS;
}

void do_expmail_abort(dbref player)
{
    Flags2(player) &= ~PLAYER_MAILS;
    notify(player, "MAIL: Message aborted.");
}

void do_prepend(dbref player, dbref cause, int key, char *text)
{
    char *oldmsg, *newmsg, *bp, *attr;
    dbref aowner;
    int aflags;

    if (!mudconf.have_mailer)
    {
        return;
    }

    if (Flags2(player) & PLAYER_MAILS)
    {
        oldmsg = atr_get(player, A_MAILMSG, &aowner, &aflags);
        if (*oldmsg)
        {
            bp = newmsg = alloc_lbuf("do_prepend");
            safe_str(text + 1, newmsg, &bp);
            safe_chr(' ', newmsg, &bp);
            safe_str(oldmsg, newmsg, &bp);
            *bp = '\0';
            atr_add_raw(player, A_MAILMSG, newmsg);
            free_lbuf(newmsg);
        }
        else
        {
            atr_add_raw(player, A_MAILMSG, text + 1);
        }

        free_lbuf(oldmsg);
        int nLen;
        attr = atr_get_raw_LEN(player, A_MAILMSG, &nLen);
        notify(player, tprintf("%d/%d characters prepended.", nLen, LBUF_SIZE-1));
    }
    else
    {
        notify(player, "MAIL: No message in progress.");
    }
}

void do_postpend(dbref player, dbref cause, int key, char *text)
{
    char *oldmsg, *newmsg, *bp, *attr;
    dbref aowner;
    int aflags;

    if (!mudconf.have_mailer)
    {
        return;
    }

    if ((*(text + 1) == '-') && !(*(text + 2)))
    {
        do_expmail_stop(player, 0);
        return;
    }
    if (Flags2(player) & PLAYER_MAILS)
    {
        oldmsg = atr_get(player, A_MAILMSG, &aowner, &aflags);
        if (*oldmsg)
        {
            bp = newmsg = alloc_lbuf("do_postpend");
            safe_str(oldmsg, newmsg, &bp);
            safe_chr(' ', newmsg, &bp);
            safe_str(text + 1, newmsg, &bp);
            *bp = '\0';
            atr_add_raw(player, A_MAILMSG, newmsg);
            free_lbuf(newmsg);
        }
        else
        {
            atr_add_raw(player, A_MAILMSG, text + 1);
        }

        free_lbuf(oldmsg);
        int nLen;
        attr = atr_get_raw_LEN(player, A_MAILMSG, &nLen);
        notify(player, tprintf("%d/%d characters added.", nLen, LBUF_SIZE-1));
    }
    else
    {
        notify(player, "MAIL: No message in progress.");
    }
}

static void do_edit_msg(dbref player, char *from, char *to)
{
    char *result, *msg;
    dbref aowner;
    int aflags;

    if (Flags2(player) & PLAYER_MAILS)
    {
        msg = atr_get(player, A_MAILMSG, &aowner, &aflags);
        result = replace_string(from, to, msg);
        atr_add(player, A_MAILMSG, result, aowner, aflags);
        notify(player, "Text edited.");
        free_lbuf(result);
        free_lbuf(msg);
    }
    else
    {
        notify(player, "MAIL: No message in progress.");
    }
}

static void do_mail_proof(dbref player)
{
    char *mailto, *names;
    char *mailmsg, *msg, *bp, *str;
    dbref aowner;
    int aflags;

    mailto = atr_get(player, A_MAILTO, &aowner, &aflags);

    if (!atr_get_raw(player, A_MAILMSG))
    {
        notify(player, "MAIL: No text.");
        free_lbuf(mailto);
        return;
    }
    else
    {
        str = mailmsg = atr_get(player, A_MAILMSG, &aowner, &aflags);
        bp = msg = alloc_lbuf("do_mail_proof");
        TinyExec(msg, &bp, 0, player, player, EV_EVAL | EV_FCHECK, &str, (char **)NULL, 0);
        *bp = '\0';
        free_lbuf(mailmsg);
    }

    if (Flags2(player) & PLAYER_MAILS)
    {
        names = make_namelist(player, mailto);
        notify(player, DASH_LINE);
        notify(player, tprintf("From:  %-*s  Subject: %-35s\nTo: %s",
                       PLAYER_NAME_LIMIT - 6, Name(player),
                    atr_get_raw(player, A_MAILSUB), names));
        notify(player, DASH_LINE);
        notify(player, msg);
        notify(player, DASH_LINE);
        free_lbuf(names);
    }
    else
    {
        notify(player, "MAIL: No message in progress.");
    }
    free_lbuf(mailto);
    free_lbuf(msg);
}

void do_malias_desc(dbref player, char *alias, char *desc)
{
    int nResult;
    struct malias *m = get_malias(player, alias, &nResult);
    if (nResult == GMA_NOTFOUND)
    {
        notify(player, tprintf("MAIL: Alias '%s' not found.", alias));
        return;
    }
    if (nResult != GMA_FOUND)
    {
        return;
    }
    if (  m->owner != GOD
       || ExpMail(player))
    {
        int  nValidMailAliasDesc;
        BOOL bValidMailAliasDesc;
        int  nVisualWidth;
        char *pValidMailAliasDesc = MakeCanonicalMailAliasDesc
                                    (   desc,
                                        &nValidMailAliasDesc,
                                        &bValidMailAliasDesc,
                                        &nVisualWidth
                                    );

        if (bValidMailAliasDesc)
        {
            MEMFREE(m->desc);
            m->desc = StringCloneLen(pValidMailAliasDesc, nValidMailAliasDesc);
            m->desc_width = nVisualWidth;
            notify(player, "MAIL: Description changed.");
        }
        else
        {
            notify(player, "MAIL: Description is not valid.");
        }
    }
    else
    {
        notify(player, "MAIL: Permission denied.");
    }
}

void do_malias_chown(dbref player, char *alias, char *owner)
{
    dbref no = NOTHING;

    int nResult;
    struct malias *m = get_malias(player, alias, &nResult);
    if (nResult == GMA_NOTFOUND)
    {
        notify(player, tprintf("MAIL: Alias '%s' not found.", alias));
        return;
    }
    if (nResult != GMA_FOUND)
    {
        return;
    }
    if (!ExpMail(player))
    {
        notify(player, "MAIL: You cannot do that!");
        return;
    }
    else
    {
        if ((no = lookup_player(player, owner, 1)) == NOTHING)
        {
            notify(player, "MAIL: I do not see that here.");
            return;
        }
        m->owner = no;
        notify(player, "MAIL: Owner changed for alias.");
    }
}

void do_malias_add(dbref player, char *alias, char *person)
{
    int nResult;
    struct malias *m = get_malias(player, alias, &nResult);
    if (nResult == GMA_NOTFOUND)
    {
        notify(player, tprintf("MAIL: Alias '%s' not found.", alias));
        return;
    }
    else if (nResult != GMA_FOUND)
    {
        return;
    }
    dbref thing = NOTHING;
    if (*person == '#')
    {
        thing = parse_dbref(person + 1);
        if (!isPlayer(thing))
        {
            notify(player, "MAIL: Only players may be added.");
            return;
        }
    }
    
    if (thing == NOTHING)
    {
        thing = lookup_player(player, person, 1);
    }
    
    if (thing == NOTHING)
    {
        notify(player, "MAIL: I do not see that person here.");
        return;
    }
    
    if ((m->owner == GOD) && !ExpMail(player))
    {
        notify(player, "MAIL: Permission denied.");
        return;
    }
    int i;
    for (i = 0; i < m->numrecep; i++)
    {
        if (m->list[i] == thing)
        {
            notify(player, "MAIL: That person is already on the list.");
            return;
        }
    }

    if (i >= (MAX_MALIAS_MEMBERSHIP - 1))
    {
        notify(player, "MAIL: The list is full.");
        return;
    }

    m->list[m->numrecep] = thing;
    m->numrecep = m->numrecep + 1;
    notify(player, tprintf("MAIL: %s added to %s", Name(thing), m->name));
}

void do_malias_remove(dbref player, char *alias, char *person)
{
    int nResult;
    struct malias *m = get_malias(player, alias, &nResult);
    if (nResult == GMA_NOTFOUND)
    {
        notify(player, tprintf("MAIL: Alias '%s' not found.", alias));
        return;
    }
    if (nResult != GMA_FOUND)
    {
        return;
    }
    if ((m->owner == GOD) && !ExpMail(player))
    {
        notify(player, "MAIL: Permission denied.");
        return;
    }

    dbref thing = NOTHING;
    if (*person == '#')
    {
        thing = parse_dbref(person + 1);
    }
    if (thing == NOTHING)
    {
        thing = lookup_player(player, person, 1);
    }
    if (thing == NOTHING)
    {
        notify(player, "MAIL: I do not see that person here.");
        return;
    }

    BOOL ok = FALSE;
    for (int i = 0; i < m->numrecep; i++)
    {
        if (ok)
            m->list[i] = m->list[i + 1];
        else if ((m->list[i] == thing) && !ok)
        {
            m->list[i] = m->list[i + 1];
            ok = TRUE;
        }
    }

    if (ok)
    {
        m->numrecep--;
    }

    notify(player, tprintf("MAIL: %s removed from alias %s.",
                   Name(thing), alias));
}

void do_malias_rename(dbref player, char *alias, char *newname)
{
    int nResult;
    struct malias *m = get_malias(player, newname, &nResult);
    if (nResult == GMA_FOUND)
    {
        notify(player, "MAIL: That name already exists!");
        return;
    }
    if (nResult != GMA_NOTFOUND)
    {
        return;
    }
    m = get_malias(player, alias, &nResult);
    if (nResult == GMA_NOTFOUND)
    {
        notify(player, "MAIL: I cannot find that alias!");
        return;
    }
    if (nResult != GMA_FOUND)
    {
        return;
    }
    if (!ExpMail(player) && !(m->owner == player))
    {
        notify(player, "MAIL: Permission denied.");
        return;
    }

    int  nValidMailAlias;
    BOOL bValidMailAlias;
    char *pValidMailAlias = MakeCanonicalMailAlias
                            (   newname+1,
                                &nValidMailAlias,
                                &bValidMailAlias
                            );
    if (bValidMailAlias)
    {
        MEMFREE(m->name);
        m->name = StringClone(newname+1);
        notify(player, "MAIL: Mailing Alias renamed.");
    }
    else
    {
        notify(player, "MAIL: Alias is not valid.");
    }
}

void do_malias_delete(dbref player, char *alias)
{
    int nResult;
    struct malias *m = get_malias(player, alias, &nResult);
    if (nResult == GMA_NOTFOUND)
    {
        notify(player, tprintf("MAIL: Alias '%s' not found.", alias));
        return;
    }
    if (nResult != GMA_FOUND)
    {
        return;
    }
    BOOL done = FALSE;
    for (int i = 0; i < ma_top; i++)
    {
        if (done)
        {
            malias[i] = malias[i + 1];
        }
        else
        {
            if ((m->owner == player) || ExpMail(player))
            {
                if (m == malias[i])
                {
                    done = 1;
                    notify(player, "MAIL: Alias Deleted.");
                    malias[i] = malias[i + 1];
                }
            }
        }
    }

    if (!done)
    {
        notify(player, tprintf("MAIL: Alias '%s' not found.", alias));
    }
    else
    {
        ma_top--;
    }
}

void do_malias_adminlist(dbref player)
{
    struct malias *m;
    int i;

    if (!ExpMail(player)) {
        do_malias_list_all(player);
        return;
    }
    notify(player,
      "Num  Name         Description                              Owner");

    for (i = 0; i < ma_top; i++)
    {
        m = malias[i];
        notify(player, tprintf("%-4d %-12s %s%s %-15.15s",
                       i, m->name, m->desc, Spaces(40 - m->desc_width),
                       Name(m->owner)));
    }

    notify(player, "***** End of Mail Aliases *****");
}

void do_malias_status(dbref player)
{
    if (!ExpMail(player))
        notify(player, "MAIL: Permission denied.");
    else {
        notify(player, tprintf("MAIL: Number of mail aliases defined: %d", ma_top));
        notify(player, tprintf("MAIL: Allocated slots %d", ma_size));
    }
}