tinymush-3.1p1/game/backups/
tinymush-3.1p1/game/bin/
tinymush-3.1p1/game/data/
tinymush-3.1p1/game/modules/
tinymush-3.1p1/game/modules/old/
tinymush-3.1p1/src/modules/comsys/
tinymush-3.1p1/src/modules/hello/
tinymush-3.1p1/src/modules/mail/
tinymush-3.1p1/src/tools/
/* mail.c - module for penn-based mailer system */
/* $Id: mail.c,v 1.72 2004/02/23 04:35:15 rmg 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 (and then being imported wholesale into 3.0).
 * 
 * -------------------------------------------------------------------
 */

#include "../../api.h"
#include "mail.h"

/* --------------------------------------------------------------------------
 * Configuration.
 */

#define MAIL_STATS	1	/* Mail stats */
#define MAIL_DSTATS	2	/* More mail stats */
#define MAIL_FSTATS	3	/* Even more mail stats */
#define MAIL_DEBUG	4	/* Various debugging options */
#define MAIL_NUKE	5	/* Nuke the post office */
#define MAIL_FOLDER	6	/* Do folder stuff */
#define MAIL_LIST	7	/* List @mail by time */
#define MAIL_READ	8	/* Read @mail message */
#define MAIL_CLEAR	9	/* Clear @mail message */
#define MAIL_UNCLEAR	10	/* Unclear @mail message */
#define MAIL_PURGE	11	/* Purge cleared @mail messages */
#define MAIL_FILE	12	/* File @mail in folders */
#define MAIL_TAG	13	/* Tag @mail messages */
#define MAIL_UNTAG	14	/* Untag @mail messages */
#define MAIL_FORWARD	15	/* Forward @mail messages */
#define MAIL_SEND	16	/* Send @mail messages in progress */
#define MAIL_EDIT	17	/* Edit @mail messages in progress */
#define MAIL_URGENT	18	/* Sends a @mail message as URGENT */
#define MAIL_ALIAS	19	/* Creates an @mail alias */
#define MAIL_ALIST	20	/* Lists @mail aliases */
#define MAIL_PROOF	21	/* Proofs @mail messages in progress */
#define MAIL_ABORT	22	/* Aborts @mail messages in progress */
#define MAIL_QUICK	23	/* Sends a quick @mail message */
#define MAIL_REVIEW	24	/* Reviews @mail sent to a player */
#define MAIL_RETRACT	25	/* Retracts @mail sent to a player */
#define MAIL_CC		26	/* Carbon copy */
#define MAIL_SAFE	27	/* Defines a piece of mail as safe. */
#define MAIL_REPLY	28	/* Replies to a message. */
#define MAIL_REPLYALL	29	/* Replies to all recipients of msg */
#define MAIL_TO		30	/* Set recipient list */
#define MAIL_BCC	31	/* Blind Carbon copy */
#define MAIL_QUOTE	0x100	/* Quote back original in the reply? */

/* Note that we don't use all hex for mail flags, because the upper 3 bits
 * of the switch word gets occupied by SW_<foo>.
 */

#define MALIAS_DESC	1	/* Describes a mail alias */
#define MALIAS_CHOWN	2	/* Changes a mail alias's owner */
#define MALIAS_ADD	3	/* Adds a player to an alias */
#define MALIAS_REMOVE	4	/* Removes a player from an alias */
#define MALIAS_DELETE	5	/* Deletes a mail alias */
#define MALIAS_RENAME	6	/* Renames a mail alias */
#define MALIAS_LIST	8	/* Lists mail aliases */
#define MALIAS_STATUS	9	/* Status of mail aliases */


NHSHTAB mod_mail_msg_htab;

MODNHASHES mod_mail_nhashtable[] = {
{ "Mail messages",	&mod_mail_msg_htab,	50,	8},
{ NULL,			NULL,			0,	0}};

struct mod_mail_confstorage {
	int	mail_expiration; /* Number of days to wait to delete mail */
	int	mail_freelist;  /* The next free mail number */
	MENT	*mail_list;     /* The mail database */
	int	mail_db_top;    /* Like db_top */
	int	mail_db_size;	/* Like db_size */
} mod_mail_config;

CONF mod_mail_conftable[] = {
{(char *)"mail_expiration",		cf_int,		CA_GOD,		CA_PUBLIC,	&mod_mail_config.mail_expiration,	0},
{ NULL,					NULL,		0,		0,		NULL,				0}};

extern int FDECL(do_convtime, (char *, struct tm *));

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 *, \
			      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 *));
void FDECL(check_mail, (dbref, int, int));
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 *, char *, \
				 char *, int, int));
static void FDECL(do_edit_msg, (dbref, char *, char *));
static void FDECL(do_mail_proof, (dbref));
static void FDECL(do_mail_to, (dbref, char *, int));
static int FDECL(do_expmail_start, (dbref, char *, char *, char *));
static void FDECL(do_expmail_stop, (dbref, int));
static void FDECL(do_expmail_abort, (dbref));
struct mail *FDECL(mail_fetch, (dbref, int));

#define MALIAS_LEN 100

struct malias {
	int owner;
	char *name;
	char *desc;
	int numrecep;
	dbref list[MALIAS_LEN];
};

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(newtop)
int newtop;
{
	int newsize, i;
	MENT *newdb;

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

	newdb = (MENT *) XMALLOC((newsize + 1) * sizeof(MENT), "mail_db_grow");

	if (!newdb) {
	    fprintf(mainlog_fp, "ABORT! mail.c, unable to malloc new db in mail_db_grow().\n");
	    abort();
	}

	if (mod_mail_config.mail_list) {
		mod_mail_config.mail_list -= 1;
		memcpy((char *)newdb, (char *)mod_mail_config.mail_list,
		       (mod_mail_config.mail_db_top + 1) * sizeof(MENT));
		XFREE(mod_mail_config.mail_list, "mail_list");
	}
	mod_mail_config.mail_list = newdb + 1;
	newdb = NULL;

	for (i = mod_mail_config.mail_db_top; i < newtop; i++) {
		mod_mail_config.mail_list[i].count = 0;
		mod_mail_config.mail_list[i].message = NULL;
	}
	mod_mail_config.mail_db_top = newtop;
	mod_mail_config.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 < mod_mail_config.mail_db_top; i++) {
		if (mod_mail_config.mail_list[i].message == NULL) {
			mod_mail_config.mail_freelist = i;
			return;
		}
	}

	mail_db_grow(i + 1);
	mod_mail_config.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(player, message)
dbref player;
char *message;
{
	int number;
	char *atrstr, *execstr, *bp, *str;
	int aflags, alen;
	dbref aowner;

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

	atrstr = atr_get(player, A_SIGNATURE, &aowner, &aflags, &alen);
	execstr = bp = alloc_lbuf("add_mail_message");
	str = atrstr;
	exec(execstr, &bp, player, player, player,
	     EV_STRIP | EV_FCHECK | EV_EVAL, &str, (char **)NULL, 0);

	mod_mail_config.mail_list[number].message = XSTRDUP(tprintf("%s %s", message, execstr), "add_mail_message");
	free_lbuf(atrstr);
	free_lbuf(execstr);
	make_mail_freelist();
	return number;
}

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

static int add_mail_message_nosig(message)
char *message;
{
	int number;

	number = mod_mail_config.mail_freelist;
	if (!mod_mail_config.mail_list) {
		mail_db_grow(1);
	}
	mod_mail_config.mail_list[number].message = XSTRDUP(message, "add_mail_message_nosig");

	make_mail_freelist();
	return number;
}

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

static void new_mail_message(message, number)
char *message;
int number;
{
	mod_mail_config.mail_list[number].message = XSTRDUP(message, "new_mail_message");
}

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

static INLINE void add_count(number)
int number;
{
	mod_mail_config.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(number)
int number;
{
	mod_mail_config.mail_list[number].count--;

	if (mod_mail_config.mail_list[number].count < 1) {
		XFREE(mod_mail_config.mail_list[number].message, "delete_mail");
		mod_mail_config.mail_list[number].message = NULL;
		mod_mail_config.mail_list[number].count = 0;
	}
}

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

INLINE char *get_mail_message(number)
int number;
{
	static char err[] = "MAIL: This mail message does not exist in the database. Please alert your admin.";

	if (mod_mail_config.mail_list[number].message != NULL) {
		return mod_mail_config.mail_list[number].message;
	} else {
		delete_mail_message(number);
		return err;
	}
}

/*-------------------------------------------------------------------------*
 *   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_purge - purge cleared messages
 * do_mail_change_folder - change current folder
 *-------------------------------------------------------------------------*/

extern char *FDECL(upcasestr, (char *));

/*
 * Change or rename a folder 
 */
void do_mail_change_folder(player, fld, newname)
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; p && *p; p++) {
			if (!isalnum(*p)) {
				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(player, msglist)
dbref player;
char *msglist;
{
	do_mail_flags(player, msglist, M_TAG, 0);
}

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

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

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

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

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

static void do_mail_flags(player, msglist, flag, negate)
dbref player;
char *msglist;
mail_flag flag;
int negate;			/* if 1, clear the flag */
{
	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 *)nhashfind((int)player, &mod_mail_msg_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(player, msglist, folder)
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 *)nhashfind((int)player, &mod_mail_msg_htab);
	     mp; mp = mp->next) {
		if (All(ms) || (Folder(mp) == origfold)) {
			i++;
			if (mail_match(mp, ms, i)) {
				j++;
				mp->read &= M_FMASK;	/*
							 * Clear the folder 
							 */
				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(player, msglist)
dbref player;
char *msglist;
{
	struct mail *mp;
	char *tbuf1, *buff, *status, *names, *ccnames;
	struct mail_selector ms;
	int i = 0, j = 0, folder;

	tbuf1 = alloc_lbuf("do_mail_read");

	if (!parse_msglist(msglist, &ms, player)) {
		free_lbuf(tbuf1);
		return;
	}
	folder = player_folder(player);
	for (mp = (struct mail *)nhashfind((int)player, &mod_mail_msg_htab);
	     mp; mp = mp->next) {
		if (Folder(mp) == folder) {
			i++;
			if (mail_match(mp, ms, i)) {
				/*
				 * Read it 
				 */
				j++;
				buff = alloc_lbuf("do_mail_read");
				StringCopy(buff, get_mail_message(mp->number));
				notify(player, DASH_LINE);
				status = status_string(mp);
				names = make_namelist(player, (char *)mp->tolist);
				ccnames = make_namelist(player, (char *)mp->cclist);
				notify(player, tprintf("%-3d         From:  %-*s  At: %-25s  %s\r\nFldr   : %-2d Status: %s\r\nTo     : %-65s",
						       i,
						       PLAYER_NAME_LIMIT - 6,
						       Name(mp->from),
						       mp->time,
						       (Connected(mp->from) && (!Hidden(mp->from) || See_Hidden(player))) ? " (Conn)" : "      ",
						       folder,
						       status,
						       names));
				if (*ccnames)
					notify(player, tprintf("Cc     : %-65s", ccnames));
				notify(player, tprintf("Subject: %-65s", mp->subject));
				free_lbuf(names);
				free_lbuf(ccnames);
				free_lbuf(status);
				notify(player, DASH_LINE);
				StringCopy(tbuf1, buff);
				notify(player, tbuf1);
				notify(player, DASH_LINE);
				free_lbuf(buff);
				if (Unread(mp)) {
					/* mark message as read */
					mp->read |= M_ISREAD;
				}
			}
		}
	}

	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_retract(player, name, msglist)
dbref player;
char *name, *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, player)) {
		return;
	}
	for (mp = (struct mail *)nhashfind((int)target, &mod_mail_msg_htab);
	     mp; mp = nextp) {
		if (mp->from == player) {
			i++;
			if (mail_match(mp, ms, i)) {
				j++;
				if (Unread(mp)) {
					if (mp->prev == NULL)
						nhashrepl((int)target,
							  (int *)mp->next,
						       &mod_mail_msg_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;
					XFREE(mp->subject,
					      "mail_retract.subject");
					delete_mail_message(mp->number);
					XFREE(mp->time,
					      "mail_retract.time");
					XFREE(mp->tolist,
					      "mail_retract.tolist");
					XFREE(mp->cclist,
					      "mail_retract.cclist");
					XFREE(mp->bcclist,
					      "mail_retract.bcclist");
					XFREE(mp, "mail_retract");
					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(player, name, msglist)
dbref player;
char *name;
char *msglist;
{
	struct mail *mp;
	struct mail_selector ms;
	int i = 0, j = 0;
	dbref target, thing;
	char *status, *names, *ccnames, *bccnames;
	int all = 0;

	if (!*name || !strcmp(name, "all")) {
		target = player;
		all = 1;
	} else {
		target = lookup_player(player, name, 1);
		if (target == NOTHING) {
			notify(player, "MAIL: No such player.");
			return;
		}
	}

	if (!msglist || !*msglist) {
		/* no specific list of messages to review -- either see all to
		 * one player, or all sent
		 */
		notify(player, tprintf("%.*s   MAIL: %s   %.*s",
				       (63 - strlen(Name(target))) >> 1,
				       "--------------------------------",
				       Name(target),
				       (64 - strlen(Name(target))) >> 1,
				       "--------------------------------"));
		if (all) {
			/* ok, review all sent messages */
			MAIL_ITER_ALL(mp, thing) {
				if (mp->from == player) {
					i++;
					/*
					 * list it 
					 */
					notify(player, tprintf("[%s] %-3d (%4d) To: %-*s Sub: %.25s",
							       status_chars(mp),
							       i,
							       strlen(get_mail_message(mp->number)),
							       PLAYER_NAME_LIMIT - 6,
							       Name(mp->to),
							       mp->subject));
				}
			}
		} else {
			/* review all messages we sent to the target */
			for (mp = (struct mail *)nhashfind((int)target, &mod_mail_msg_htab); mp; mp = mp->next) {
				if (mp->from == player) {
					i++;
					/*
					 * list it 
					 */
					notify(player, tprintf("[%s] %-3d (%4d) To: %-*s Sub: %.25s",
							       status_chars(mp),
							       i,
							       strlen(get_mail_message(mp->number)),
							       PLAYER_NAME_LIMIT - 6,
							       Name(mp->to),
							       mp->subject));
				}
			}
		}
		notify(player, DASH_LINE);
	} else {
		/* specific list of messages, either chosen from all our sent
		 * messages, or from messages sent to a particular player
		 */

		if (!parse_msglist(msglist, &ms, player)) {
			return;
		}
		if (all) {
			/* ok, choose messages from among all sent */
			MAIL_ITER_ALL(mp, thing){
				if (mp->from == player) {
					i++;
					if (mail_match(mp, ms, i)) {
						/*
						 * Read it 
						 */
						j++;
						status = status_string(mp);
						names = make_namelist(player, (char *)mp->tolist);
						ccnames = make_namelist(player, (char *)mp->cclist);
						bccnames = make_namelist(player, (char *)mp->bcclist);
						notify(player, DASH_LINE);
						notify(player, tprintf("%-3d         From:  %-*s  At: %-25s\r\nFldr   : %-2d Status: %s\r\nTo     : %-65s",
							i, PLAYER_NAME_LIMIT - 6, Name(mp->from), mp->time, 0, status, names));
						if (*ccnames)
							notify(player, tprintf("Cc     : %-65s", ccnames));
						if (*bccnames)
							notify(player, tprintf("Bcc    : %-65s", bccnames));
						notify(player, tprintf("Subject: %-65s", mp->subject));
						free_lbuf(status);
						free_lbuf(names);
						free_lbuf(ccnames);
						free_lbuf(bccnames);
						notify(player, DASH_LINE);
						notify(player, get_mail_message(mp->number));
						notify(player, DASH_LINE);
					}
				}
			}
		} else {
			/* choose messages from among those sent to a
			 * particular player
			 */
			for (mp = (struct mail *)nhashfind((int)target, &mod_mail_msg_htab); mp; mp = mp->next) {
				if (mp->from == player) {
					i++;
					if (mail_match(mp, ms, i)) {
						/*
						 * Read it 
						 */
						j++;
						status = status_string(mp);
						names = make_namelist(player, (char *)mp->tolist);
						ccnames = make_namelist(player, (char *)mp->cclist);
						bccnames = make_namelist(player, (char *)mp->bcclist);
						notify(player, DASH_LINE);
						notify(player, tprintf("%-3d         From:  %-*s  At: %-25s\r\nFldr   : %-2d Status: %s\r\nTo     : %-65s",
							i, PLAYER_NAME_LIMIT - 6, Name(mp->from), mp->time, 0, status, names));
						if (*ccnames)
							notify(player, tprintf("Cc     : %-65s", ccnames));
						if (*bccnames)
							notify(player, tprintf("Bcc    : %-65s", bccnames));
						notify(player, tprintf("Subject: %-65s", mp->subject));
						free_lbuf(status);
						free_lbuf(names);
						free_lbuf(ccnames);
						free_lbuf(bccnames);
						notify(player, DASH_LINE);
						notify(player, get_mail_message(mp->number));
						notify(player, DASH_LINE);
					}
				}
			}

		}
		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_list(player, msglist, sub)
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("--------------------------%s   MAIL: Folder %d   ----------------------------",
		       ((folder > 9) ? "" : "-"), folder));

	for (mp = (struct mail *)nhashfind((int)player, &mod_mail_msg_htab);
	     mp; mp = mp->next) {
		if (Folder(mp) == folder) {
			i++;
			if (mail_match(mp, ms, i)) {
				/*
				 * list it 
				 */

				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) || See_Hidden(player))) ? "Conn" : " ")));
				free_lbuf(time);
			}
		}
	}
	notify(player, DASH_LINE);
}

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

	p = (char *)the_time;
	q = new = alloc_lbuf("mail_list_time");
	if (!p || !*p) {
		*new = '\0';
		return new;
	}
	/*
	 * 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 new;
}

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

	/*
	 * Go through player's mail, and remove anything marked cleared 
	 */
	for (mp = (struct mail *)nhashfind((int)player, &mod_mail_msg_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)
					nhashdelete((int)player, &mod_mail_msg_htab);
				else
					nhashrepl((int)player, (int *)mp->next, &mod_mail_msg_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 
			 */
			XFREE(mp->subject, "mail_purge.subject");
			delete_mail_message(mp->number);
			XFREE(mp->time, "mail_purge.time");
			XFREE(mp->tolist, "mail_purge.tolist");
			XFREE(mp->cclist, "mail_purge.cclist");
			XFREE(mp->bcclist, "mail_purge.bcclist");
			XFREE(mp, "mail_purge");
		} else {
			nextp = mp->next;
		}
	}
	notify(player, "MAIL: Mailbox purged.");
}

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

        if (Sending_Mail(player)) {
                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 = atoi(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;
	}
	mail_ok = do_expmail_start(player, tolist, NULL, tprintf("%s (fwd from %s)", mp->subject, Name(mp->from)));
	if (mail_ok) {
	    atr_add_raw(player, A_MAILMSG, get_mail_message(mp->number));
	    atr_add_raw(player, A_MAILFLAGS, tprintf("%d", atoi(atr_get_raw(player, A_MAILFLAGS)) | M_FORWARD));
	}
}

void do_mail_reply(player, msg, all, key)
dbref player;
char *msg;
int all, key;
{
	struct mail *mp;
	int num, mail_ok;
	char *tolist, *ccnames, *bp, *p, *oldlist, *tokst;

        if (Sending_Mail(player)) {
                notify(player, "MAIL: Mail message already in progress.");
                return;
        }

	if (!msg || !*msg) {
		notify(player, "MAIL: No message list.");
		return;
	}
	num = atoi(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 reply to non-existent messages.");
		return;
	}
	ccnames = NULL;
	if (all) {
        	bp = oldlist = alloc_lbuf("do_mail_reply.oldlist");
		safe_str((char *)mp->tolist, oldlist, &bp);
		if (*mp->cclist)
			safe_tprintf_str(oldlist, &bp, " %s", mp->cclist);

		bp = ccnames = alloc_lbuf("do_mail_reply.ccnames");
	        for (p = strtok_r(oldlist, " ", &tokst);
		     p != NULL;
		     p = strtok_r(NULL, " ", &tokst)) {
			if (bp != ccnames) {
				safe_chr(' ', ccnames, &bp);
			}
                	if (*p == '*') {
                        	safe_str(p, ccnames, &bp);
                	} else if (atoi(p) != mp->from) {
				safe_chr('"', ccnames, &bp);
                        	safe_name(atoi(p), ccnames, &bp);
				safe_chr('"', ccnames, &bp);
                	}
        	}
        	free_lbuf(oldlist);
	}
	tolist = msg;

	if (strncmp(mp->subject, "Re:", 3)) {
		mail_ok = do_expmail_start(player, tolist, ccnames, tprintf("Re: %s", mp->subject));
	} else {
		mail_ok = do_expmail_start(player, tolist, ccnames, tprintf("%s", mp->subject));
	}
	if (mail_ok) {
	    if (key & MAIL_QUOTE) {
	        atr_add_raw(player, A_MAILMSG,
			tprintf("On %s, %s wrote:\r\n\r\n%s\r\n\r\n********** End of included message from %s\r\n",
				mp->time, Name(mp->from),
				get_mail_message(mp->number), Name(mp->from)));
	    }

	    atr_add_raw(player, A_MAILFLAGS, tprintf("%d", (atoi(atr_get_raw(player, A_MAILFLAGS)) | M_REPLY)));
	}
	if (ccnames) {
        	free_lbuf(ccnames);
	}
}

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

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

	for (mp = (struct mail *)nhashfind((int)player, &mod_mail_msg_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(player, folder, rcount, ucount, ccount)
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 *)nhashfind((int)player, &mod_mail_msg_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(player, folder, ucount)
dbref player;
int folder;
int *ucount;
{
	struct mail *mp;
	int uc;

	uc = 0;

	for (mp = (struct mail *)nhashfind((int)player, &mod_mail_msg_htab);
	     mp; mp = mp->next) {
		if (Folder(mp) == folder) {
			if (!(Read(mp)) && (Urgent(mp)))
				uc++;
		}
	}
	*ucount = uc;
}

static void send_mail(player, target, tolist, cclist, bcclist, subject,
		      number, flags, silent)
dbref player, target;
const char *tolist, *cclist, *bcclist, *subject;
int number, silent;
mail_flag flags;
{
	struct mail *newp;
	struct mail *mp;
	time_t tt;
	char tbuf1[30];

	if (Typeof(target) != TYPE_PLAYER) {
		notify(player, "MAIL: You cannot send mail to non-existent people.");
		delete_mail_message(number);
		return;
	}
	tt = time(NULL);
	StringCopy(tbuf1, ctime(&tt));
	tbuf1[strlen(tbuf1) - 1] = '\0';	/*
						 * whack the newline 
						 */

	/*
	 * initialize the appropriate fields 
	 */
	newp = (struct mail *) XMALLOC(sizeof(struct mail), "send_mail");

	newp->to = target;
	newp->from = player;
	newp->tolist = XSTRDUP(tolist, "send_mail.tolist");
	if (!cclist) {
		newp->cclist = XSTRDUP("", "send_mail.cclist");
	} else {
		newp->cclist = XSTRDUP(cclist, "send_mail.cclist");
	}
	if (!bcclist) {
		newp->bcclist = XSTRDUP("", "send_mail.bcclist");
	} else {
		newp->bcclist = XSTRDUP(bcclist, "send_mail.bcclist");
	}

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

	newp->time = XSTRDUP(tbuf1, "send_mail.time");
	newp->subject = XSTRDUP(subject, "send_mail.subj");
	newp->read = flags & M_FMASK;	/*
					 * Send to folder 0 
					 */

	/*
	 * if this is the first message, it is the head and the tail 
	 */
	if (!nhashfind((int)target, &mod_mail_msg_htab)) {
		nhashadd((int)target, (int *)newp, &mod_mail_msg_htab);
		newp->next = NULL;
		newp->prev = NULL;
	} else {
		for (mp = (struct mail *)nhashfind((int)target, &mod_mail_msg_htab);
		     mp->next; mp = mp->next) ;

		mp->next = newp;
		newp->next = NULL;
		newp->prev = mp;
	}

	/*
	 * 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, A_NULL, NULL, A_AMAIL, 0,
	       (char **) NULL, 0, 0);
	return;
}

void do_mail_nuke(player)
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 
	 */
	DO_WHOLE_DB(thing) {
		for (mp = (struct mail *)nhashfind((int)thing,
						   &mod_mail_msg_htab);
		     mp != NULL; mp = nextp) {
			nextp = mp->next;
			delete_mail_message(mp->number);
			XFREE(mp->subject, "mail_nuke.subject");
			XFREE(mp->tolist, "mail_nuke.tolist");
			XFREE(mp->cclist, "mail_nuke.cclist");
			XFREE(mp->bcclist, "mail_nuke.bcclist");
			XFREE(mp->time, "mail_nuke.time");
			XFREE(mp, "mail_nuke");
		}
		nhashdelete((int)thing, &mod_mail_msg_htab);
	}

	STARTLOG(LOG_ALWAYS, "WIZ", "MNUKE")
		log_printf("** MAIL PURGE ** done by ");
		log_name(player);
	ENDLOG

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

void do_mail_debug(player, action, victim)
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)
						nhashdelete((int)thing, &mod_mail_msg_htab);
					else
						nhashrepl((int)thing, (int *)mp->next, &mod_mail_msg_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 
				 */
				XFREE(mp->subject,
				      "mail_debug.subject");
				delete_mail_message(mp->number);
				XFREE(mp->time, "mail_debug.time");
				XFREE(mp->tolist, "mail_debug.tolist");
				XFREE(mp->cclist, "mail_debug.cclist");
				XFREE(mp->bcclist, "mail_debug.bcclist");
				XFREE(mp, "mail_debug");
			} else
				nextp = mp->next;
		}
		notify(player, "Mail sanity fix completed.");
	} else {
		notify(player, "That is not a debugging option.");
		return;
	}
}

void do_mail_stats(player, name, full)
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;

	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 = atoi(&name[1]);
		if (!Good_obj(target) || (Typeof(target) != TYPE_PLAYER))
			target = NOTHING;
	} else if (!strcasecmp(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 command 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) {
				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));
				}
			}
			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)
				fchars += strlen(get_mail_message(mp->number));
		}
		if (mp->to == target) {
			if (!tr && !tu) {
				StringCopy(last, mp->time);
			}
			if (Cleared(mp))
				tc++;
			else if (Read(mp))
				tr++;
			else
				tu++;
			if (full == 2) {
				tchars += strlen(get_mail_message(mp->number));
			}
		}
	}

	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));
	return;
}


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

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

	if (Typeof(player) != TYPE_PLAYER) {
		notify(player, "MAIL: Only players may send and receive mail.");
		return;
	}
	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 (!strcasecmp(arg1, "purge")) {
		do_mail_purge(player);
		return;
	}
	/*
	 * clear message 
	 */
	if (!strcasecmp(arg1, "clear")) {
		do_mail_clear(player, arg2);
		return;
	}
	if (!strcasecmp(arg1, "unclear")) {
		do_mail_unclear(player, arg2);
		return;
	}
	if (arg2 && *arg2) {
		/*
		 * Sending mail 
		 */
		(void) do_expmail_start(player, arg1, NULL, arg2);
		return;
	} else {
		/*
		 * Must be reading or listing mail - no arg2 
		 */
		if (isdigit(*arg1) && !strchr(arg1, '-'))
			do_mail_read(player, arg1);
		else
			do_mail_list(player, arg1, 1);
		return;
	}
}


void do_mail(player, cause, key, arg1, arg2)
dbref player, cause;
int key;
char *arg1;
char *arg2;
{
	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_TO:
		do_mail_to(player, arg1, A_MAILTO);
		break;
	case MAIL_CC:
		do_mail_to(player, arg1, A_MAILCC);
		break;
	case MAIL_BCC:
		do_mail_to(player, arg1, A_MAILBCC);
		break;
	case MAIL_SAFE:
		do_mail_safe(player, arg1);
		break;
	}
}

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

	/* Write out version number */
	
	fprintf(fp, "+V6\n");
	putref(fp, mod_mail_config.mail_db_top);
	DO_WHOLE_DB(thing) {
		if (isPlayer(thing)) {
			mptr = (struct mail *)nhashfind((int)thing, &mod_mail_msg_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);
					putstring(fp, mp->cclist);
					putstring(fp, mp->bcclist);
					putstring(fp, mp->time);
					putstring(fp, mp->subject);
					putref(fp, mp->read);
					count++;
				}
		}
	}

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

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

	save_malias(fp);
	fflush(fp);
}

void mod_mail_load_database(fp)
FILE *fp;
{
	char nbuf1[8];
	int mail_top = 0;
	int new = 0;
	int pennsub = 0;
	int read_tolist = 0;
	int read_cclist = 0;
	int read_newdb = 0;
	int read_new_strings = 0;
	int number = 0;
	struct mail *mp, *mptr;

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

	if (!strncmp(nbuf1, "+V1", 3)) {
		new = 0;
	} else if (!strncmp(nbuf1, "+V2", 3)) {
		new = 1;
	} else if (!strncmp(nbuf1, "+V3", 3)) {
		new = 1;
		read_tolist = 1;
	} else if (!strncmp(nbuf1, "+V4", 3)) {
		new = 1;
		read_tolist = 1;
		read_newdb = 1;
	} else if (!strncmp(nbuf1, "+V5", 3)) {
		new = 1;
		read_tolist = 1;
		read_newdb = 1;
		read_new_strings = 1;
	} else if (!strncmp(nbuf1, "+V6", 3)) {
		new = 1;
		read_tolist = 1;
		read_cclist = 1;
		read_newdb = 1;
		read_new_strings = 1;
	} else if (!strncmp(nbuf1, "+1", 2)) {
		pennsub = 1;
	} else {
		/* Version number mangled */
	        fclose(fp);
		return;
	}
	
	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)) {
		mp = (struct mail *) XMALLOC(sizeof(struct mail), "load_mail");

		mp->to = atoi(nbuf1);

		if (!nhashfind((int)mp->to, &mod_mail_msg_htab)) {
			nhashadd((int)mp->to, (int *)mp, &mod_mail_msg_htab);
			mp->prev = NULL;
			mp->next = NULL;
		} else {
			for (mptr = (struct mail *)nhashfind((int)mp->to, &mod_mail_msg_htab);
			     mptr->next != NULL; mptr = mptr->next) ;
			mptr->next = mp;
			mp->prev = mptr;
			mp->next = NULL;
		}

		mp->from = getref(fp);

		if (read_newdb) {
			mp->number = getref(fp);
			add_count(mp->number);
		}
		if (read_tolist)
			mp->tolist = XSTRDUP(getstring_noalloc(fp, read_new_strings), "load_mail.tolist");
		else
			mp->tolist = XSTRDUP(tprintf("%d", mp->to), "load_mail.tolist");

		if (read_cclist){
			mp->cclist = XSTRDUP(getstring_noalloc(fp, read_new_strings), "load_mail.cclist");
			mp->bcclist = XSTRDUP(getstring_noalloc(fp, read_new_strings), "load_mail.bcclist");
		} else {
			mp->cclist = XSTRDUP("", "load_mail.cclist");
			mp->bcclist = XSTRDUP("", "load_mail.bcclist");
		}

		mp->time = XSTRDUP(getstring_noalloc(fp, read_new_strings), "load_mail.time");
		if (pennsub)
			mp->subject = XSTRDUP(getstring_noalloc(fp, read_new_strings), "load_mail.subj");
		else if (!new)
			mp->subject = XSTRDUP("No subject", "load_mail.subj");

		if (!read_newdb) {
			number = add_mail_message_nosig(getstring_noalloc(fp, read_new_strings));
			add_count(number);
			mp->number = number;
		}
		if (new)
			mp->subject = XSTRDUP(getstring_noalloc(fp, read_new_strings), "load_mail.subj");
		else if (!pennsub)
			mp->subject = XSTRDUP("No subject", "load_mail.subj");
		mp->read = getref(fp);
		fgets(nbuf1, sizeof(nbuf1), fp);
	}

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

		while (strncmp(nbuf1, "+++", 3)) {
			number = atoi(nbuf1);
			new_mail_message(getstring_noalloc(fp, read_new_strings), number);
			fgets(nbuf1, sizeof(nbuf1), fp);
		}
	}
	load_malias(fp);
}

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

	/* Look up a folder name and return the appropriate number */
	
	atrstr = atr_get(player, A_MAILFOLDERS, &aowner, &aflags, &alen);
	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 (!isspace(*p))
		p++;
	p = '\0';
	free_lbuf(atrstr);
	free_lbuf(str);
	free_lbuf(pat);
	return atoi(res);
}

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

	/*
	 * 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, &alen);
	if (!*atrstr) {
		StringCopy(str, "unnamed");
		free_lbuf(pat);
		free_lbuf(atrstr);
		return str;
	}
	StringCopy(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 {
		StringCopy(str, "unnamed");
		free_lbuf(pat);
		return str;
	}
}

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

	/*
	 * 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.
	 */

	new = 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(new, "%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, &alen);
	if (*atrstr) {
		StringCopy(str, atrstr);
		old = (char *)string_match(str, pat);
	}
	if (old && *old) {
		StringCopy(tbuf, str);
		r = old;
		while (!isspace(*r))
			r++;
		*r = '\0';
		res = (char *)replace_string(old, new, tbuf);
	} else {
		r = res = alloc_lbuf("mail_folders");
		if (*atrstr)
			safe_str(str, res, &r);
		safe_str(new, 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(new);
	free_lbuf(tbuf);
	free_lbuf(atrstr);
	free_lbuf(res);
}

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

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

void set_player_folder(player, fnum)
dbref player;
int fnum;
{
	/*
	 * Set a player's folder to fnum 
	 */
	ATTR *a;
	char *tbuf1;

	tbuf1 = alloc_lbuf("set_player_folder");
	sprintf(tbuf1, "%d", fnum);
	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_WIZARD | AF_NOPROG | AF_LOCK);
	}
	free_lbuf(tbuf1);
}

static int parse_folder(player, folder_string)
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 (isdigit(*folder_string)) {
		fnum = atoi(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(mp, ms, num)
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) {
		time_t now, msgtime, diffdays;
		char *msgtimestr;
		struct tm msgtm;
		/*
		 * Get the time now, subtract mp->time, and compare the * * * 
		 * results with * ms.days (in manner of ms.day_comp) 
		 */
		time(&now);
		msgtimestr = alloc_lbuf("mail_match");
		StringCopy(msgtimestr, mp->time);
		if (do_convtime(msgtimestr, &msgtm)) {
			msgtime = timelocal(&msgtm);
			diffdays = (now - msgtime) / 86400;
			free_lbuf(msgtimestr);
			if (sign(diffdays - ms.days) != ms.day_comp) {
				return 0;
			}
		} else {
			free_lbuf(msgtimestr);
			return 0;
		}
	}
	return 1;
}

static int parse_msglist(msglist, ms, player)
char *msglist;
struct mail_selector *ms;
dbref player;
{
	char *p, *q;
	char tbuf1[LBUF_SIZE];

	/*
	 * 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 player of 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;
	}
	/*
	 * Don't mess with msglist itself 
	 */
	StringCopyTrunc(tbuf1, msglist, LBUF_SIZE - 1);
	p = tbuf1;
	while (p && *p && isspace(*p))
		p++;
	if (!p || !*p) {
		return 1;	/*
				 * all messages 
				 */
	}
	if (isdigit(*p)) {
		/*
		 * Message or range 
		 */
		q = strchr(p, '-');
		if (q) {
			/*
			 * We have a subrange, split it up and test to see if 
			 * it is valid 
			 */
			*q++ = '\0';
			ms->low = atoi(p);
			if (ms->low <= 0) {
				notify(player, "MAIL: Invalid message range");
				return 0;
			}
			if (!*q) {
				/*
				 * Unbounded range 
				 */
				ms->high = 0;
			} else {
				ms->high = atoi(q);
				if ((ms->low > ms->high) || (ms->high <= 0)) {
					notify(player, "MAIL: Invalid message range");
					return 0;
				}
			}
		} else {
			/*
			 * A single message 
			 */
			ms->low = ms->high = atoi(p);
		}
	} else {
		switch (*p) {
		case '-':	/*
				 * Range with no start 
				 */
			p++;
			if (!p || !*p) {
				notify(player, "MAIL: Invalid message range");
				return 0;
			}
			ms->high = atoi(p);
			if (ms->high <= 0) {
				notify(player, "MAIL: Invalid message range");
				return 0;
			}
			break;
		case '~':	/*
				 * exact # of days old 
				 */
			p++;
			if (!p || !*p) {
				notify(player, "MAIL: Invalid age");
				return 0;
			}
			ms->day_comp = 0;
			ms->days = atoi(p);
			if (ms->days < 0) {
				notify(player, "MAIL: Invalid age");
				return 0;
			}
			break;
		case '<':	/*
				 * less than # of days old 
				 */
			p++;
			if (!p || !*p) {
				notify(player, "MAIL: Invalid age");
				return 0;
			}
			ms->day_comp = -1;
			ms->days = atoi(p);
			if (ms->days < 0) {
				notify(player, "MAIL: Invalid age");
				return 0;
			}
			break;
		case '>':	/*
				 * greater than # of days old 
				 */
			p++;
			if (!p || !*p) {
				notify(player, "MAIL: Invalid age");
				return 0;
			}
			ms->day_comp = 1;
			ms->days = atoi(p);
			if (ms->days < 0) {
				notify(player, "MAIL: Invalid age");
				return 0;
			}
			break;
		case '#':	/*
				 * From db# 
				 */
			p++;
			if (!p || !*p) {
				notify(player, "MAIL: Invalid dbref #");
				return 0;
			}
			ms->player = atoi(p);
			if (!Good_obj(ms->player) || !(ms->player)) {
				notify(player, "MAIL: Invalid dbref #");
				return 0;
			}
			break;
		case '*':	/*
				 * From player name 
				 */
			p++;
			if (!p || !*p) {
				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;
		case 'u':	/* Urgent, Unread */
		case 'U':
			p++;
			if (!p || !*p) {
				notify(player, "MAIL: U is ambiguous (urgent or unread?)");
				return 0;
			}
			switch (*p) {
			case 'r':	/*
					 * Urgent 
					 */
			case 'R':
				ms->flags = M_URGENT;
				break;
			case 'n':	/*
					 * Unread 
					 */
			case 'N':
				ms->flags = M_MSUNREAD;
				break;
			default:	/* bad */
				notify(player, "MAIL: Invalid message specification");
				return 0;
			}
			break;
		case 'r':	/* read */
		case 'R':
			ms->flags = M_ISREAD;
			break;
		case 'c':	/* cleared */
		case 'C':
			ms->flags = M_CLEARED;
			break;
		case 't':	/* tagged */
		case 'T':
			ms->flags = M_TAG;
			break;
		case 'm':
		case 'M':	/*
				 * Mass, me 
				 */
			p++;
			if (!p || !*p) {
				notify(player, "MAIL: M is ambiguous (mass or me?)");
				return 0;
			}
			switch (*p) {
			case 'a':
			case 'A':
				ms->flags = M_MASS;
				break;
			case 'e':
			case 'E':
				ms->player = player;
				break;
			default:
				notify(player, "MAIL: Invalid message specification");
				return 0;
			}
			break;
		default:	/* Bad news */
			notify(player, "MAIL: Invalid message specification");
			return 0;
		}
	}
	return 1;
}

void check_mail_expiration()
{
	struct mail *mp, *nextp;
	struct tm then_tm;
	time_t then, now = time(0);
	time_t expire_secs = mod_mail_config.mail_expiration * 86400;
	dbref thing;

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

	MAIL_ITER_SAFE(mp, thing, nextp) {
		if (do_convtime((char *) mp->time, &then_tm))
		{
			then = timelocal(&then_tm);
			if (((now - then) > expire_secs) && !(M_Safe(mp))) {
				/*
				 * Delete this one 
				 */
				/*
				 * head and tail of the list are special 
				 */
				if (mp->prev == NULL) {
					if (mp->next == NULL)
						nhashdelete((int)mp->to, &mod_mail_msg_htab);
					else
						nhashrepl((int)mp->to, (int *)mp->next, &mod_mail_msg_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 
				 */
				XFREE(mp->subject,
				      "mail_expire.subject");
				delete_mail_message(mp->number);
				XFREE(mp->tolist,
				      "mail_expire.tolist");
				XFREE(mp->cclist,
				      "mail_expire.cclist");
				XFREE(mp->bcclist,
				      "mail_expire.bcclist");
				XFREE(mp->time, "mail_expire.time");
				XFREE(mp, "mail_expire");
			} else
				nextp = mp->next;
		} else
			nextp = mp->next;
	}
}

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

	StringCopy(res, "");
	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;
}

static char *status_string(mp)
struct mail *mp;
{
	/*
	 * Return a longer description of message flags 
	 */
	char *tbuf1;

	tbuf1 = alloc_lbuf("status_string");
	StringCopy(tbuf1, "");
	if (Read(mp))
		strcat(tbuf1, "Read ");
	else
		strcat(tbuf1, "Unread ");
	if (Cleared(mp))
		strcat(tbuf1, "Cleared ");
	if (Urgent(mp))
		strcat(tbuf1, "Urgent ");
	if (Mass(mp))
		strcat(tbuf1, "Mass ");
	if (Forward(mp))
		strcat(tbuf1, "Fwd ");
	if (Tagged(mp))
		strcat(tbuf1, "Tagged ");
	if (M_Safe(mp))
		strcat(tbuf1, "Safe");
	return tbuf1;
}

void check_mail(player, folder, silent)
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).",
		 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, "MAIL: You have no mail.");
	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
	return;
}

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


void do_malias_switch(player, a1, a2)
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(player, cause, key, arg1, arg2)
dbref player, cause;
int key;
char *arg1;
char *arg2;
{
	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);
	}
}

struct malias *get_malias(player, alias)
dbref player;
char *alias;
{
	char *mal;
	struct malias *m;
	int i = 0;
	int x = 0;

	if ((*alias == '#') && ExpMail(player)) {
		x = atoi(alias + 1);
		if (x < 0 || x >= ma_top)
			return NULL;
		else
			return malias[x];
	} else {
		if (*alias != '*')
			return NULL;

		mal = alias + 1;

		for (i = 0; i < ma_top; i++) {
			m = malias[i];
			if ((m->owner == player) || God(m->owner)) {
				if (!strcmp(mal, m->name))	/*
								 * Found it! 
								 */
					return m;
			}
		}
	}
	return NULL;
}

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

	if (Typeof(player) != TYPE_PLAYER) {
		notify(player, "MAIL: Only players may create mail aliases.");
		return;
	}
	if (!alias || !*alias || !tolist || !*tolist) {
		notify(player, "MAIL: What alias do you want to create?.");
		return;
	}
	if (*alias != '*') {
		notify(player, "MAIL: All Mail aliases must begin with '*'.");
		return;
	}
	if (strlen(alias) > 31)  {
		notify(player, "MAIL: Alias name too long, truncated.");
		alias[31] = '\0';
	}
	m = get_malias(player, alias);
	if (m) {
		notify(player,
		   tprintf("MAIL: Mail Alias '%s' already exists.", alias));
		return;
	}
	if (!ma_size) {
		ma_size = MA_INC;
		malias = (struct malias **) XMALLOC(sizeof(struct malias *) *
						    ma_size, "malias_create");
	} else if (ma_top >= ma_size) {
		ma_size += MA_INC;
		nm = (struct malias **) XMALLOC(sizeof(struct malias *) *
						ma_size, "malias_create");

		for (i = 0; i < ma_top; i++)
			nm[i] = malias[i];
		XFREE(malias, "malias_create.malias");
		malias = nm;
	}
	malias[ma_top] = (struct malias *) XMALLOC(sizeof(struct malias),
						   "malias_create.top");

	i = 0;

	/* Parse the player list */
	head = (char *)tolist;
	while (head && *head && (i < (MALIAS_LEN - 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 (!strcasecmp(head, "me"))
			target = player;
		else if (*head == '#') {
			target = atoi(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++;
	}
	malias[ma_top]->list[i] = NOTHING;

	na = alias + 1;
	malias[ma_top]->name = (char *) XMALLOC(sizeof(char) *
						(strlen(na) + 1),
						"malias_create.top.name");
	malias[ma_top]->numrecep = i;
	malias[ma_top]->owner = player;
	StringCopy(malias[ma_top]->name, na);
	malias[ma_top]->desc = (char *) XMALLOC(sizeof(char) *
						(strlen(na) + 1),
						"malias_create.top.desc");
	StringCopy(malias[ma_top]->desc, na);	/*
						 * For now do this. 
						 */
	ma_top++;


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

void do_malias_list(player, alias)
dbref player;
char *alias;
{
	struct malias *m;
	int i = 0;
	char *buff, *bp;

	m = get_malias(player, alias);

	if (!m) {
		notify(player, tprintf("MAIL: Alias '%s' not found.", alias));
		return;
	}
	if (!ExpMail(player) && (player != m->owner) && !(God(m->owner))) {
		notify(player, "MAIL: Permission denied.");
		return;
	}
	bp = buff = alloc_lbuf("do_malias_list");
	safe_tprintf_str(buff, &bp, "MAIL: Alias *%s: ", m->name);
	for (i = m->numrecep - 1; i > -1; i--) {
		safe_name(m->list[i], buff, &bp);
		safe_chr(' ', buff, &bp);
	}
	*bp = '\0';

	notify(player, buff);
	free_lbuf(buff);
}

void do_malias_list_all(player)
dbref player;
{
	struct malias *m;
	int i = 0;
	int notified = 0;

	for (i = 0; i < ma_top; i++) {
		m = malias[i];
		if (God(m->owner) || (m->owner == player) || God(player)) {
			if (!notified) {
				notify(player,
				       "Name         Description                         Owner");
				notified++;
			}
			notify(player,
			       tprintf("%-12.12s %-35.35s %-15.15s", m->name, m->desc,
				       Name(m->owner)));
		}
	}

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

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


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

	}
}

void save_malias(fp)
FILE *fp;
{

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

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

	fscanf(fp, "%d\n", &ma_top);

	ma_size = ma_top;

	if (ma_top > 0)
		malias = (struct malias **) XMALLOC(sizeof(struct malias *) *
						    ma_size, "malias_read");
	else
		malias = NULL;

	for (i = 0; i < ma_top; i++) {
		malias[i] = (struct malias *) XMALLOC(sizeof(struct malias),
						      "malias_read.element");

		m = (struct malias *)malias[i];

		fscanf(fp, "%d %d\n", &(m->owner), &(m->numrecep));

		fscanf(fp, "%[^\n]\n", buffer);
		m->name = (char *) XMALLOC(sizeof(char) * (strlen(buffer) - 1),
					   "malias_read.name");
		StringCopy(m->name, buffer + 2);
		fscanf(fp, "%[^\n]\n", buffer);
		m->desc = (char *) XMALLOC(sizeof(char) * (strlen(buffer) - 1),
					   "malias_read.desc");
		StringCopy(m->desc, buffer + 2);

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

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

	fprintf(fp, "%d\n", 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++)
			fprintf(fp, "%d\n", m->list[j]);
	}
}

static int do_expmail_start(player, arg, arg2, subject)
dbref player;
char *arg, *arg2, *subject;
{
	char *tolist, *cclist, *names, *ccnames;

	if (!arg || !*arg) {
		notify(player, "MAIL: I do not know whom you want to mail.");
		return 0;
	}
	if (!subject || !*subject) {
		notify(player, "MAIL: No subject.");
		return 0;
	}
	if (Sending_Mail(player)) {
		notify(player, "MAIL: Mail message already in progress.");
		return 0;
	}
	if (!(tolist = make_numlist(player, arg))) {
		return 0;
	}
	atr_add_raw(player, A_MAILTO, tolist);
	names = make_namelist(player, tolist);
	free_lbuf(tolist);
	if (arg2) {
		if (!(cclist = make_numlist(player, arg2))) {
			return 0;
		}
		atr_add_raw(player, A_MAILCC, cclist);
		ccnames = make_namelist(player, cclist);
		free_lbuf(cclist);
	} else {
		atr_clr(player, A_MAILCC);
		ccnames = NULL;
	}
	atr_clr(player, A_MAILBCC);
	atr_add_raw(player, A_MAILSUB, subject);
	atr_add_raw(player, A_MAILFLAGS, "0");
	atr_clr(player, A_MAILMSG);
	Flags2(player) |= PLAYER_MAILS;
	if (ccnames && *ccnames) {
		notify(player, tprintf("MAIL: You are sending mail to '%s', carbon-copied to '%s'.", names, ccnames));
	} else {
		notify(player, tprintf("MAIL: You are sending mail to '%s'.", names));
	}
	free_lbuf(names);
	if (ccnames) {
		free_lbuf(ccnames);
	}
	return 1;
}

static void do_mail_to(player, arg, attr)
dbref player;
char *arg;
int attr;
{
	char *tolist, *names, *cclist, *ccnames, *bcclist, *bccnames;
	dbref aowner;
	int aflags, alen;

	if (!Sending_Mail(player)) {
		notify(player, "MAIL: No mail message in progress.");
		return;
	}
	if (!arg || !*arg) {
		if (attr == A_MAILTO) {
			notify(player, "MAIL: I do not know whom you want to mail.");
		} else {
			atr_clr(player, attr);
			notify_quiet(player, "Cleared.");
		}
	} else if ((tolist = make_numlist(player, arg))) {
		atr_add_raw(player, attr, tolist);
		notify_quiet(player, "Set.");
		free_lbuf(tolist);
	}

	tolist = atr_get(player, A_MAILTO, &aowner, &aflags, &alen);
	cclist = atr_get(player, A_MAILCC, &aowner, &aflags, &alen);
	bcclist = atr_get(player, A_MAILBCC, &aowner, &aflags, &alen);

	names = make_namelist(player, tolist);
	ccnames = make_namelist(player, cclist);
	bccnames = make_namelist(player, bcclist);

	if (*ccnames) {
		if (*bccnames) {
			notify(player, tprintf("MAIL: You are sending mail to '%s', carbon-copied to '%s', blind carbon-copied to '%s'.",
					       names, ccnames, bccnames));
		} else {
			notify(player, tprintf("MAIL: You are sending mail to '%s', carbon-copied to '%s'.",
					       names, ccnames));
		}
	} else {
		if (*bcclist) {
			notify(player, tprintf("MAIL: You are sending mail to '%s', blind carbon-copied to '%s'.",
					       names, bccnames));
		} else {
			notify(player, tprintf("MAIL: You are sending mail to '%s'.",
					       names));
		}
	}

	free_lbuf(names);
	free_lbuf(ccnames);
	free_lbuf(bccnames);

	free_lbuf(tolist);
	free_lbuf(cclist);
	free_lbuf(bcclist);
}

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

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

	StringCopy(oldarg, arg);

	for (p = strtok_r(oldarg, " ", &tokst);
	     p != NULL;
	     p = strtok_r(NULL, " ", &tokst)) {
		if (bp != names) {
			safe_str(", ", names, &bp);
		}
		if (*p == '*') {
			safe_str(p, names, &bp);
		} else {
			safe_name(atoi(p), names, &bp);
		}
	}
	free_lbuf(oldarg);
	return names;
}

static char *make_numlist(player, arg)
dbref player;
char *arg;
{
	char *head, *tail, spot;
	static char *numbuf;
	static 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;

		if (strlen(head) == 0)
			continue;

		num = atoi(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 == '*') {
			m = get_malias(player, head);
			if (!m) {
				notify(player, tprintf("MAIL: Alias '%s' does not exist.", 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(player, arg1, arg2)
dbref player;
char *arg1, *arg2;
{
	char *buf, *bp, *tolist;

	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 (Sending_Mail(player)) {
		notify(player, "MAIL: Mail message already in progress.");
		return;
	}
	buf = alloc_lbuf("do_mail_quick");
	bp = buf;
	StringCopy(bp, arg1);

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

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

void mail_to_list(player, tolist, cclist, bcclist, subject, message, flags, silent)
dbref player;
char *tolist, *cclist, *bcclist, *subject, *message;
int flags, silent;
{
	char *head, *tail, spot, *list, *bp;
	struct malias *m;
	dbref target;
	int number, i, j, n_recips, max_recips, *recip_array, *tmp;

	if (!tolist || !*tolist) {
		return;
	}
	bp = list = alloc_lbuf("mail_to_list");
	safe_str(tolist, list, &bp);
	if (cclist && *cclist) {
		safe_chr(' ', list, &bp);
		safe_str(cclist, list, &bp);
	}
	if (bcclist && *bcclist) {
		safe_chr(' ', list, &bp);
		safe_str(bcclist, list, &bp);
	}

	number = add_mail_message(player, message);

	n_recips = 0;
	max_recips = SBUF_SIZE;
	recip_array = (int *) XCALLOC(max_recips, sizeof(int),
				      "mail_to_list.recip_array");

	head = (char *)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 == '*') {
		    m = get_malias(player, head);
		    if (!m) {
			free_lbuf(list);
			XFREE(recip_array, "mail_to_list.recip_array");
			return;
		    }
		    for (i = 0; i < m->numrecep; i++) {
			if (isPlayer(m->list[i]) && !Going(m->list[i])) {
			    if (n_recips >= max_recips) {
				max_recips += SBUF_SIZE;
				tmp = (int *) XREALLOC(recip_array,
					max_recips * sizeof(int),
					"mail_to_list.recip_array");
				if (!tmp) {
				    free_lbuf(list);
				    XFREE(recip_array,
					  "mail_to_list.recip_array");
				    return;
				}
				recip_array = tmp;
			    }
			    recip_array[n_recips] = m->list[i];
			    n_recips++;
			} else {
			    send_mail(GOD, GOD, tolist, NULL, NULL, subject,
				      add_mail_message(player, 
			       tprintf("Alias Error: Bad Player %d for %s",
				       m->list[i], head)),
				      flags, silent);
			}
		    }
		} else {
		    target = atoi(head);
		    if (Good_obj(target) && !Going(target)) {
			if (n_recips >= max_recips) {
			    max_recips += SBUF_SIZE;
			    tmp = (int *) XREALLOC(recip_array,
						   max_recips * sizeof(int),
						   "mail_to_list.recip_array");
			    if (!tmp) {
				free_lbuf(list);
				XFREE(recip_array,
				      "mail_to_list.recip_array");
				return;
			    }
			    recip_array = tmp;
			}
			recip_array[n_recips] = target;
			n_recips++;
		    }
		}

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

	/* Eliminate duplicates. */

	for (i = 0; i < n_recips; i++) {
	    for (j = i + 1;
		 (recip_array[i] != NOTHING) && (j < n_recips);
		 j++) {
		if (recip_array[i] == recip_array[j])
		    recip_array[i] = NOTHING;
	    }
	    if (Typeof(recip_array[i]) != TYPE_PLAYER)
		    recip_array[i] = NOTHING;
	}

	/* Send it. */

	for (i = 0; i < n_recips; i++) {
	    if (recip_array[i] != NOTHING) {
		send_mail(player, recip_array[i], tolist, cclist, bcclist, subject, number,
			  flags, silent);
	    }
	}

	/* Clean up. */

	free_lbuf(list);
	XFREE(recip_array, "mail_to_list.recip_array");
}

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

	tolist = atr_get(player, A_MAILTO, &aowner, &aflags, &alen);
	cclist = atr_get(player, A_MAILCC, &aowner, &aflags, &alen);
	bcclist = atr_get(player, A_MAILBCC, &aowner, &aflags, &alen);

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

	if (!*tolist || !*mailmsg || !Sending_Mail(player)) {
		notify(player, "MAIL: No such message to send.");
	} else {
		mail_to_list(player, tolist, cclist, bcclist, mailsub, mailmsg, flags | atoi(mailflags), 0);
	}
	free_lbuf(tolist);
	free_lbuf(cclist);
	free_lbuf(bcclist);

	free_lbuf(mailmsg);
	free_lbuf(mailsub);
	free_lbuf(mailflags);
	Flags2(player) &= ~PLAYER_MAILS;
}

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

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

	if (Sending_Mail(player)) {
		oldmsg = atr_get(player, A_MAILMSG, &aowner, &aflags, &alen);
		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);
		attr = atr_get_raw(player, A_MAILMSG);
		notify(player, tprintf("%d/%d characters prepended.", 
			attr ? strlen(attr) : 0, LBUF_SIZE));
	} else {
		notify(player, "MAIL: No message in progress.");
	}

}

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

	if ((*(text + 1) == '-') && !(*(text + 2))) {
		do_expmail_stop(player, 0);
		return;
	}
	if (Sending_Mail(player)) {
		oldmsg = atr_get(player, A_MAILMSG, &aowner, &aflags, &alen);
		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);
		attr = atr_get_raw(player, A_MAILMSG);
		notify(player, tprintf("%d/%d characters added.", 
			attr ? strlen(attr) : 0, LBUF_SIZE));
	} else {
		notify(player, "MAIL: No message in progress.");
	}
}

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

	if (Sending_Mail(player)) {
		msg = atr_get(player, A_MAILMSG, &aowner, &aflags, &alen);
		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(player)
dbref player;
{
	char *mailto, *names, *ccnames, *bccnames, *message;
	dbref aowner;
	int aflags, alen;

	if (Sending_Mail(player)) {
		mailto = atr_get(player, A_MAILTO, &aowner, &aflags, &alen);
		names = make_namelist(player, mailto);
		free_lbuf(mailto);
		mailto = atr_get(player, A_MAILCC, &aowner, &aflags, &alen);
		ccnames = make_namelist(player, mailto);
		free_lbuf(mailto);
		mailto = atr_get(player, A_MAILBCC, &aowner, &aflags, &alen);
		bccnames = make_namelist(player, mailto);
		free_lbuf(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));
		if (*ccnames)
			notify(player, tprintf("Cc: %s", ccnames));
		if (*bccnames)
			notify(player, tprintf("Bcc: %s", bccnames));
		notify(player, DASH_LINE);

		if (!(message = atr_get_raw(player, A_MAILMSG))) {
			notify(player, "MAIL: No text.");
		} else {
			notify(player, message);
			notify(player, DASH_LINE);
		}
		free_lbuf(names);
		free_lbuf(ccnames);
		free_lbuf(bccnames);
	} else {
		notify(player, "MAIL: No message in progress.");
	}
}

void do_malias_desc(player, alias, desc)
dbref player;
char *alias;
char *desc;
{
	struct malias *m;

	if (!(m = get_malias(player, alias))) {
		notify(player, tprintf("MAIL: Alias %s not found.", alias));
		return;
	} else if ((m->owner != GOD) || ExpMail(player)) {
	        XFREE(m->desc, "malias_desc");	/* free it up */
		if (strlen(desc) > 63) {
			notify(player, "MAIL: Description too long, truncated.");
			desc[63] = '\0';
		}
		m->desc = (char *) XMALLOC(sizeof(char) * (strlen(desc) + 1),
					   "malias_desc");
		StringCopy(m->desc, desc);
		notify(player, "MAIL: Description changed.");
	} else
		notify(player, "MAIL: Permission denied.");
	return;
}

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

	if (!(m = get_malias(player, alias))) {
		notify(player, tprintf("MAIL: Alias %s not found.", alias));
		return;
	} else {
		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(player, alias, person)
dbref player;
char *alias;
char *person;
{
	int i = 0;
	dbref thing;
	struct malias *m;

	thing = NOTHING; 
	
	if (!(m = get_malias(player, alias))) {
		notify(player, tprintf("MAIL: Alias %s not found.", alias));
		return;
	}
	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 (God(m->owner) && !ExpMail(player)) {
		notify(player, "MAIL: Permission denied.");
		return;
	}
	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 >= (MALIAS_LEN - 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(player, alias, person)
dbref player;
char *alias;
char *person;
{
	int i, ok = 0;
	dbref thing;
	struct malias *m;

	thing = NOTHING;
	
	if (!(m = get_malias(player, alias))) {
		notify(player, "MAIL: Alias not found.");
		return;
	}
	if (God(m->owner) && !ExpMail(player)) {
		notify(player, "MAIL: Permission denied.");
		return;
	}
	
	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;
	}
		
	for (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 = 1;
		}
	}

	if (ok)
		m->numrecep--;

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

void do_malias_rename(player, alias, newname)
dbref player;
char *alias;
char *newname;
{
	struct malias *m;

	if ((m = get_malias(player, alias)) == NULL) {
		notify(player, "MAIL: I cannot find that alias!");
		return;
	}
	if (!ExpMail(player) && !(m->owner == player)) {
		notify(player, "MAIL: Permission denied.");
		return;
	}
	if (*newname != '*') {
		notify(player, "MAIL: Bad alias.");
		return;
	}
	if (strlen(newname) > 31)  {
		notify(player, "MAIL: Alias name too long, truncated.");
		newname[31] = '\0';
	}
	if (get_malias(player, newname) != NULL) {
		notify(player, "MAIL: That name already exists!");
		return;
	}
	XFREE(m->name, "malias_rename");
	m->name = (char *) XMALLOC(sizeof(char) * strlen(newname),
				   "malias_rename");
	StringCopy(m->name, newname + 1);

	notify(player, "MAIL: Mailing Alias renamed.");
}

void do_malias_delete(player, alias)
dbref player;
char *alias;
{
	int i = 0;
	int done = 0;
	struct malias *m;

	m = get_malias(player, alias);
	
	if (!m) {
		notify(player, "MAIL: Not a valid alias. Remember to prefix the alias name with *.");
		return;
	}

	for (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, "MAIL: Alias not found.");
	else
		ma_top--;
}

void do_malias_adminlist(player)
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 %-10.10s %-40.40s %-11.11s",
				       i, m->name, m->desc, Name(m->owner)));
	}

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

void do_malias_status(player)
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));
	}
}

/* --------------------------------------------------------------------------
 * Functions.
 */

FUNCTION(fun_mail)
{
	/* This function can take one of three formats: 1.  mail(num)  -->
	 * returns message <num> for privs. 2.  mail(player)  -->
	 * returns number of messages for <player>. 3.
	 * mail(player, num)  -->  returns message <num> for
	 * <player>. 
	 *
	 * It can now take one more format: 4.  mail() --> returns number of
	 * messages for executor 
	 */

	struct mail *mp;
	dbref playerask;
	int num, rc, uc, cc;

	VaChk_Range(0, 2);

	if ((nfargs == 0) || !fargs[0] || !fargs[0][0]) {
		count_mail(player, 0, &rc, &uc, &cc);
		safe_ltos(buff, bufc, rc + uc);
		return;
	}
	if (nfargs == 1) {
		if (!is_number(fargs[0])) {
			/* handle the case of wanting to count the number of
			 * messages 
			 */
			if ((playerask = lookup_player(player, fargs[0], 1)) == NOTHING) {
				safe_str("#-1 NO SUCH PLAYER", buff, bufc);
				return;
			} else if ((player != playerask) && !Wizard(player)) {
				safe_noperm(buff, bufc);
				return;
			} else {
				count_mail(playerask, 0, &rc, &uc, &cc);
				safe_tprintf_str(buff, bufc, "%d %d %d", rc, uc, cc);
				return;
			}
		} else {
			playerask = player;
			num = atoi(fargs[0]);
		}
	} else {
		if ((playerask = lookup_player(player, fargs[0], 1)) == NOTHING) {
			safe_str("#-1 NO SUCH PLAYER", buff, bufc);
			return;
		} else if ((player != playerask) && !God(player)) {
			safe_noperm(buff, bufc);
			return;
		}
		num = atoi(fargs[1]);
	}

	if ((num < 1) || (Typeof(playerask) != TYPE_PLAYER)) {
		safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
		return;
	}
	mp = mail_fetch(playerask, num);
	if (mp != NULL) {
		safe_str(get_mail_message(mp->number), buff, bufc);
		return;
	}
	/* ran off the end of the list without finding anything */
	safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
}

FUNCTION(fun_mailfrom)
{
	/* This function can take these formats: 1) mailfrom(<num>) 2)
	 * mailfrom(<player>,<num>) It returns the dbref of the player the
	 * mail is from 
	 */
	struct mail *mp;
	dbref playerask;
	int num;

	VaChk_Range(1, 2);

	if (nfargs == 1) {
		playerask = player;
		num = atoi(fargs[0]);
	} else {
		if ((playerask = lookup_player(player, fargs[0], 1)) == NOTHING) {
			safe_str("#-1 NO SUCH PLAYER", buff, bufc);
			return;
		} else if ((player != playerask) && !Wizard(player)) {
			safe_noperm(buff, bufc);
			return;
		}
		num = atoi(fargs[1]);
	}

	if ((num < 1) || (Typeof(playerask) != TYPE_PLAYER)) {
		safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
		return;
	}
	mp = mail_fetch(playerask, num);
	if (mp != NULL) {
		safe_dbref(buff, bufc, mp->from);
		return;
	}
	/* ran off the end of the list without finding anything */
	safe_str("#-1 NO SUCH MESSAGE", buff, bufc);
}

FUN mod_mail_functable[] = {
{"MAIL",        fun_mail,       0,  FN_VARARGS, CA_PUBLIC,	NULL},
{"MAILFROM",    fun_mailfrom,   0,  FN_VARARGS, CA_PUBLIC,	NULL},
{NULL,		NULL,		0,  0,		0,		NULL}};

/* --------------------------------------------------------------------------
 * Command tables.
 */

NAMETAB mail_sw[] = {
{(char *)"abort",	1,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_ABORT},
{(char *)"alias",       4,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_ALIAS},
{(char *)"alist",       2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_ALIST},
{(char *)"bcc",		2,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_BCC},
{(char *)"cc",		2,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_CC},
{(char *)"clear",       1,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_CLEAR},
{(char *)"debug",       1,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_DEBUG},
{(char *)"dstats",      2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_DSTATS},
{(char *)"edit",        2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_EDIT},
{(char *)"file",        2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_FILE},
{(char *)"folder",      3,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_FOLDER},
{(char *)"forward",     2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_FORWARD},
{(char *)"fstats",      2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_FSTATS},
{(char *)"fwd",         2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_FORWARD},
{(char *)"list",        1,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_LIST},
{(char *)"nuke",        1,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_NUKE},
{(char *)"proof",       2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_PROOF},
{(char *)"purge",       1,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_PURGE},
{(char *)"quick",	1,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_QUICK},
{(char *)"quote",	3,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_QUOTE|SW_MULTIPLE},
{(char *)"read",        1,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_READ},
{(char *)"reply",	3,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_REPLY},
{(char *)"replyall",	6,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_REPLYALL},
{(char *)"retract",	3,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_RETRACT},
{(char *)"review",	3,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_REVIEW},
{(char *)"safe",	2,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_SAFE},
{(char *)"send",        0,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_SEND},
{(char *)"stats",       2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_STATS},
{(char *)"tag",         1,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_TAG},
{(char *)"to",		2,	CA_NO_SLAVE|CA_NO_GUEST,      MAIL_TO},
{(char *)"unclear",     1,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_UNCLEAR},
{(char *)"untag",       3,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_UNTAG},
{(char *)"urgent",      2,      CA_NO_SLAVE|CA_NO_GUEST,      MAIL_URGENT},
{ NULL,                 0,      0,              0}};

NAMETAB malias_sw[] = {
{(char *)"desc",        1,      CA_NO_SLAVE|CA_NO_GUEST,      MALIAS_DESC},
{(char *)"chown",       1,      CA_NO_SLAVE|CA_NO_GUEST,      MALIAS_CHOWN},
{(char *)"add",         1,      CA_NO_SLAVE|CA_NO_GUEST,      MALIAS_ADD},
{(char *)"remove",      1,      CA_NO_SLAVE|CA_NO_GUEST,      MALIAS_REMOVE},
{(char *)"delete",      1,      CA_NO_SLAVE|CA_NO_GUEST,      MALIAS_DELETE},
{(char *)"rename",      1,      CA_NO_SLAVE|CA_NO_GUEST,      MALIAS_RENAME},
{(char *)"list",        1,      CA_NO_SLAVE|CA_NO_GUEST,      MALIAS_LIST},
{(char *)"status",      1,      CA_NO_SLAVE|CA_NO_GUEST,      MALIAS_STATUS},
{ NULL,                 0,      0,              0}};

CMDENT mod_mail_cmdtable[] = {
{(char *)"@mail",               mail_sw,           CA_NO_SLAVE|CA_NO_GUEST,
        0,              CS_TWO_ARG|CS_INTERP,
	NULL,		NULL,	NULL,		{do_mail}},
{(char *)"@malias",             malias_sw,         CA_NO_SLAVE|CA_NO_GUEST,
        0,              CS_TWO_ARG|CS_INTERP,
	NULL,		NULL,	NULL,		{do_malias}},
{(char *)"-",			NULL,
	CA_NO_GUEST|CA_NO_SLAVE|CF_DARK,
	0,		CS_ONE_ARG|CS_INTERP|CS_LEADIN,	
	NULL,		NULL,	NULL,		{do_postpend}},
{(char *)"~",			NULL,
	CA_NO_GUEST|CA_NO_SLAVE|CF_DARK,
	0,		CS_ONE_ARG|CS_INTERP|CS_LEADIN,	
	NULL,		NULL,	NULL,		{do_prepend}},
{(char *)NULL,			NULL,		0,
	0,		0,				
	NULL,		NULL,	NULL,		{NULL}}};

/* --------------------------------------------------------------------------
 * Handlers.
 */

void mod_mail_announce_connect(player, reason, num)
    dbref player;
    const char *reason;
    int num;
{
    check_mail(player, 0, 0);
    if (Sending_Mail(player)) {
	notify(player, "MAIL: You have a mail message in progress.");
    }
}

void mod_mail_announce_disconnect(player, reason, num)
    dbref player;
    const char *reason;
    int num;
{
    do_mail_purge(player);
}

void mod_mail_destroy_player(player, victim)
    dbref player, victim;
{
    do_mail_clear(victim, NULL);
    do_mail_purge(victim);
}

void mod_mail_cleanup_startup()
{
    check_mail_expiration();
}

void mod_mail_init()
{
    mod_mail_config.mail_expiration = 14;
    mod_mail_config.mail_db_top = 0;
    mod_mail_config.mail_db_size = 0;
    mod_mail_config.mail_freelist = 0;

    register_commands(mod_mail_cmdtable);
    register_prefix_cmds("-~");
    register_functions(mod_mail_functable);
    register_hashtables(NULL, mod_mail_nhashtable);
}