pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/*---------------------------------------------------------------
 * extmail.c - Javelin's improved @mail system
 * Based on Amberyl's linked list mailer
 *
 * Summary of mail command syntax:
 * Sending:
 *   @mail[/sendswitch] player-list = message
 *     sendswitches: /silent, /urgent
 *     player-list is a space-separated list of players or msg#'s to reply
 *       to. Players can be names or dbrefs
 * Reading/Handling:
 *   @mail[/readswitch] [msg-list [= target]]
 *     With no readswitch, @mail reads msg-list (same as /read)
 *     With no readswitch and no msg-list, @mail lists all messages (/list)
 *     readswitches: /list, /read, /fwd (requires target list of players
 *	to forward messages to), /file (requires target folder to file
 *	to), /tag, /untag, /clear, /unclear, /purge (no msg-list),
 *	/count
 *     Assumes messages in current folder, set by @folder or @mail/folder
 *     msg-list can be one of: a message number, a message range,
 *     (2-3, 4-, -6), sender references (*player), date comparisons
 *     (~0, >2, <5), or the strings "urgent", "tagged", "cleared",
 *     "read", "unread", "all", or "folder"
 *     You can also use 1:2 (folder 1, message 2) and 1:2-3 for ranges.
 * Admin stuff:
 *   @mail[/switch] [player]
 *     Switches include: nuke (used to be "purge"), [efd]stats, debug
 *
 * THEORY OF OPERATION:
 *  Prior to pl11, mail was an unsorted linked list. When mail was sent,
 * it was added onto the end. To read mail, you scanned the whole list.
 * This is still how origmail.c works.
 *  As of pl11, extmail.c maintains mail as a sorted linked list, sorted
 * by recipient and order of receipt. This makes sending mail less
 * efficient (because you have to scan the list to figure out where to
 * insert), but reading/checking/deleting more efficient, 
 * because once you've found where the player's mail starts, you just
 * read from there. 
 *  That wouldn't be so exciting unless there was a fast way to find
 * where a player's mail chain started. Fortunately, there is. We
 * record that information for connected players when they connect,
 * on their descriptor. So, when connected players do reading/etc,
 * it's O(1). Sending to a connected player is O(1). Sending to an
 * unconnected player still requires scanning (O(n)), but you send once, 
 * and read/list/delete etc, multiple times.
 *  And just to make the sending to disconnected players faster, 
 * instead of scanning the whole maildb to find the insertion point,
 * we start the scan from the chain of the connected player with the
 * closest db# to the target player. This scales up very well.
 *--------------------------------------------------------------------*/

#include "config.h"
#include "copyrite.h"

#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#include <ctype.h>
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif

#include "conf.h"
#include "mushdb.h"
#include "dbdefs.h"
#include "intrface.h"
#include "externs.h"
#include "match.h"
#include "extmail.h"
#include "parse.h"
#include "mymalloc.h"
#include "pueblo.h"
#include "confmagic.h"

#ifdef USE_MAILER
extern void desc_mail_set _((dbref player, struct mail * mp));	/* bsd.c */
extern int do_convtime _((char *str, struct tm * ttm));		/* funtime.c */

void do_mail_change_folder _((dbref player, char *fld, char *newname));
void do_mail_unfolder _((dbref player, char *fld));
void do_mail_tag _((dbref player, char *msglist));
void do_mail_clear _((dbref player, char *msglist));
void do_mail_untag _((dbref player, char *msglist));
void do_mail_unclear _((dbref player, char *msglist));
static void do_mail_flags _((dbref player, char *msglist, mail_flag flag, int negate));
void do_mail_file _((dbref player, char *msglist, char *folder));
void do_mail_read _((dbref player, char *msglist));
void do_mail_list _((dbref player, const char *msglist));
static char *mail_list_time _((const char *the_time, int flag));
void do_mail_purge _((dbref player));
void do_mail_fwd _((dbref player, char *msglist, char *tolist));
void do_mail_send _((dbref player, char *tolist, char *message, mail_flag flags, int silent));
static struct mail *mail_fetch _((dbref player, int num));
static struct mail *real_mail_fetch _((dbref player, int num, int folder));
static struct mail *mailfun_fetch _((dbref player, int nargs, char *arg1, char *arg2));
static void count_mail _((dbref player, int folder, int *rcount, int *ucount, int *ccount));
#ifdef MAIL_SUBJECTS
static void send_mail _((dbref player, dbref target, unsigned char *subject, char *message, mail_flag flags, int silent));
#else
static void send_mail _((dbref player, dbref target, char *message, mail_flag flags, int silent));
#endif
void do_mail_nuke _((dbref player));
void do_mail_debug _((dbref player, char *action, char *victim));
void do_mail_stats _((dbref player, char *name, int full));
void do_mail _((dbref player, char *arg1, char *arg2));
int dump_mail _((FILE * fp));
struct mail *find_exact_starting_point _((dbref player));
static struct mail *find_insertion_point _((dbref player));
void mail_init _((void));
int load_mail _((FILE * fp));
static int get_folder_number _((dbref player, char *name));
static char *get_folder_name _((dbref player, int fld));
void add_folder_name _((dbref player, int fld, const char *name));
static int player_folder _((dbref player));
void set_player_folder _((dbref player, int fnum));
static int parse_folder _((dbref player, char *folder_string));
static int mail_match _((dbref player, struct mail * mp, struct mail_selector ms, int num));
static int parse_msglist _((const char *msglist, struct mail_selector * ms, dbref player));
static int parse_message_spec _((dbref player, const char *s, int *msglow, int *msghigh, int *folder));
static char *status_chars _((struct mail * mp));
static char *status_string _((struct mail * mp));
void check_mail _((dbref player, int folder, int silent));
static int sign _((int x));
#ifdef MAIL_SUBJECTS
static char *get_subject _((struct mail * mp));
#endif

struct mail *maildb;
struct mail *tail_ptr;

#define HEAD  maildb
#define TAIL  tail_ptr

#define DASH_LINE  \
  "-----------------------------------------------------------------------------"

int mdb_top = 0;		/* total number of messages */

/*-------------------------------------------------------------------------*
 *   User mail functions (these are called from game.c)
 *
 * do_mail - cases without a /switch.
 * do_mail_send - sending mail
 * 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_count - count messages
 * do_mail_purge - purge cleared messages
 * do_mail_change_folder - change current folder
 * do_mail_unfolder - remove a folder name from MAILFOLDERS
 * do_mail_subject - set the current mail subject
 *-------------------------------------------------------------------------*/

#ifdef MAIL_SUBJECTS
/* Return the subject of a mail message, or (no subject) */
static char *
get_subject(mp)
    struct mail *mp;
{
  static char sbuf[SUBJECT_LEN + 1];
  char *p;
  if (mp->subject) {
    strncpy(sbuf, uncompress(mp->subject), SUBJECT_LEN);
    sbuf[SUBJECT_LEN] = '\0';
    /* Stop at a return or a tab */
    for (p = sbuf; *p; p++) {
      if ((*p == '\r') || (*p == '\n') || (*p == '\t')) {
	*p = '\0';
	break;
      }
      if (!isprint(*p)) {
	*p = ' ';
      }
    }
  } else
    strcpy(sbuf, "(no subject)");
  return sbuf;
}
#endif


/* 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 (!isdigit(*p) && !isalpha(*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)));
  }
}

/* Remove a folder name from MAILFOLDERS */
void
do_mail_unfolder(player, fld)
    dbref player;
    char *fld;
{
  int pfld;

  if (!fld || !*fld) {
    notify(player, "MAIL: You must specify a folder name or number");
    return;
  }
  pfld = parse_folder(player, fld);
  if (pfld < 0) {
    notify(player, "MAIL: What folder is that?");
    return;
  }
  add_folder_name(player, pfld, NULL);
  notify(player, tprintf("MAIL: Folder %d now has no name", pfld));
}


void
do_mail_tag(player, msglist)
    dbref player;
    char *msglist;
{
  do_mail_flags(player, msglist, M_TAG, 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);
}


static void
do_mail_flags(player, msglist, flag, negate)
    dbref player;
    char *msglist;
    mail_flag flag;
    int negate;
{
  /* Set (negate=0) or clear (negate=1) a flag on a player's messages */

  struct mail *mp;
  struct mail_selector ms;
  int j, folder;
  folder_array i;
  int notified = 0;

  if (!parse_msglist(msglist, &ms, player)) {
    return;
  }
  FA_Init(i, j);
  j = 0;
  folder = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
  for (mp = find_exact_starting_point(player);
       mp && (mp->to == player);
       mp = mp->next) {
    if ((mp->to == player) && (All(ms) || (Folder(mp) == folder))) {
      i[Folder(mp)]++;
      if (mail_match(player, mp, ms, i[Folder(mp)])) {
	j++;
	if (negate) {
	  mp->read &= ~flag;
	} else {
	  mp->read |= flag;
	}
	switch (flag) {
	case M_TAG:
	  if (All(ms)) {
	    if (!notified) {
	      notify(player, tprintf("MAIL: All messages in all folders %s.",
				     negate ? "untagged" : "tagged"));
	      notified++;
	    }
	  } else
	    notify(player, tprintf("MAIL: Msg #%d:%d %s.", Folder(mp), i[Folder(mp)],
				   negate ? "untagged" : "tagged"));
	  break;
	case M_CLEARED:
	  if (All(ms)) {
	    if (!notified) {
	      notify(player, tprintf("MAIL: All messages in all folders %s.",
				     negate ? "uncleared" : "cleared"));
	      notified++;
	    }
	  } else {
	    if (Unread(mp) && !negate) {
	      notify(player,
		     tprintf("MAIL: Unread Msg #%d:%d cleared! Use @mail/unclear %d:%d to recover.", Folder(mp), i[Folder(mp)], Folder(mp), i[Folder(mp)]));
	    } else {
	      notify(player, tprintf("MAIL: Msg #%d:%d %s.", Folder(mp), i[Folder(mp)],
				     negate ? "uncleared" : "cleared"));
	    }
	  }
	  break;
	}
      }
    }
  }
  if (!j) {
    /* ran off the end of the list without finding anything */
    notify(player, "MAIL: You don't have any matching messages!");
  }
  return;
}

void
do_mail_file(player, msglist, folder)
    dbref player;
    char *msglist;
    char *folder;
{
  /* Change a message's folder */
  struct mail *mp;
  struct mail_selector ms;
  int j, foldernum, origfold;
  folder_array i;
  int notified = 0;

  if (!parse_msglist(msglist, &ms, player)) {
    return;
  }
  if ((foldernum = parse_folder(player, folder)) == -1) {
    notify(player, "MAIL: Invalid folder specification");
    return;
  }
  FA_Init(i, j);
  j = 0;
  origfold = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
  for (mp = find_exact_starting_point(player);
       mp && (mp->to == player);
       mp = mp->next) {
    if ((mp->to == player) && (All(ms) || (Folder(mp) == origfold))) {
      i[Folder(mp)]++;
      if (mail_match(player, mp, ms, i[Folder(mp)])) {
	j++;
	mp->read &= M_FMASK;	/* Clear the folder */
	mp->read |= FolderBit(foldernum);
	if (All(ms)) {
	  if (!notified) {
	    notify(player, tprintf("MAIL: All messages filed in folder %d", foldernum));
	    notified++;
	  }
	} else
	  notify(player, tprintf("MAIL: Msg %d:%d filed in folder %d", origfold, i[origfold], foldernum));
      }
    }
  }
  if (!j) {
    /* ran off the end of the list without finding anything */
    notify(player, "MAIL: You don't have any matching messages!");
  }
  return;
}

void
do_mail_read(player, msglist)
    dbref player;
    char *msglist;
{
  /* print a mail message(s) */

  struct mail *mp;
  char tbuf1[BUFFER_LEN];
  static char folderheader[BUFFER_LEN] = "Folder:";
  struct mail_selector ms;
  int j, folder;
  folder_array i;

  if (!parse_msglist(msglist, &ms, player)) {
    return;
  }
  folder = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
  FA_Init(i, j);
  j = 0;
  for (mp = find_exact_starting_point(player);
       mp && (mp->to == player);
       mp = mp->next) {
    if ((mp->to == player) && (All(ms) || Folder(mp) == folder)) {
      i[Folder(mp)]++;
      if (mail_match(player, mp, ms, i[Folder(mp)])) {
	/* Read it */
	j++;
	if (SUPPORT_PUEBLO) {
	  notify_noenter(player, tprintf("%cPRE%c", TAG_START, TAG_END));
	  sprintf(folderheader, "%cA XCH_HINT=\"List messages in this folder\" XCH_CMD=\"@mail/list %d:1-\"%cFolder:%c/A%c",
		  TAG_START, Folder(mp), TAG_END, TAG_START, TAG_END);
	}
	notify(player, DASH_LINE);
	if (Typeof(mp->from) == TYPE_PLAYER)
	  sprintf(tbuf1, "%s", Name(mp->from));
	else
	  sprintf(tbuf1, "%s (owner: %s)", Name(mp->from), Name(Owner(mp->from)));
	notify(player, tprintf("From: %-55s %s\nDate: %-25s   %s %2d   Message: %d\nStatus: %s",
			       tbuf1,
	     ((Typeof(mp->from) == TYPE_PLAYER) && Connected(mp->from) &&
	      (!hidden(mp->from) || Hasprivs(player))) ?
			       " (Conn)" : "      ",
			       uncompress(mp->time), folderheader,
			       Folder(mp), i[Folder(mp)],
			       status_string(mp)));
#ifdef MAIL_SUBJECTS
	notify(player, tprintf("Subject: %s", get_subject(mp)));
#endif
	notify(player, DASH_LINE);
	if (SUPPORT_PUEBLO)
	  notify_noenter(player, tprintf("%c/PRE%c", TAG_START, TAG_END));
	strcpy(tbuf1, uncompress(mp->message));
	notify(player, tbuf1);
	if (SUPPORT_PUEBLO)
	  notify(player, tprintf("%cPRE%c%s%c/PRE%c", TAG_START, TAG_END,
				 DASH_LINE, TAG_START, TAG_END));
	else
	  notify(player, DASH_LINE);
	if (Unread(mp))
	  mp->read |= M_READ;	/* mark message as read */
      }
    }
  }
  if (!j) {
    /* ran off the end of the list without finding anything */
    notify(player, "MAIL: You don't have that many matching messages!");
  }
  return;
}


void
do_mail_list(player, msglist)
    dbref player;
    const char *msglist;
{
  /* print a mail message(s) */
#ifdef MAIL_SUBJECTS
  char subj[30];
#endif
  struct mail *mp;
  struct mail_selector ms;
  int j, folder;
  folder_array i;

  if (!parse_msglist(msglist, &ms, player)) {
    return;
  }
  FA_Init(i, j);
  j = 0;
  folder = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
  if (SUPPORT_PUEBLO)
    notify_noenter(player, tprintf("%cPRE%c", TAG_START, TAG_END));
  notify(player, tprintf(
			  "---------------------------------   MAIL   ----------------------------------", folder));
  for (mp = find_exact_starting_point(player);
       mp && (mp->to == player);
       mp = mp->next) {
    if ((mp->to == player) && (All(ms) || Folder(mp) == folder)) {
      i[Folder(mp)]++;
      if (mail_match(player, mp, ms, i[Folder(mp)])) {
	/* list it */
	if (SUPPORT_PUEBLO)
	  notify_noenter(player,
			 tprintf("%cA XCH_CMD=\"@mail/read %d:%d\" XCH_HINT=\"Read message %d in folder %d\"%c", TAG_START, Folder(mp), i[Folder(mp)], i[Folder(mp)], Folder(mp), TAG_END));
#ifdef MAIL_SUBJECTS
	strcpy(subj, chopstr(get_subject(mp), 28));
	notify(player, tprintf("[%s] %2d:%-3d %c%-12s  %-*s %s",
			     status_chars(mp), Folder(mp), i[Folder(mp)],
			       ((Connected(mp->from) &&
				 (!hidden(mp->from) || Hasprivs(player)))
				? '*' : ' '),
			       chopstr(Name(mp->from), 12),
			       30, subj,
			       mail_list_time(uncompress(mp->time), 1)));
#else
	notify(player, tprintf("[%s] %2d:%-3d (%4d) From: %-*s At: %s %s",
			       status_chars(mp), Folder(mp),
			  i[Folder(mp)], strlen(uncompress(mp->message)),
			       PLAYER_NAME_LIMIT + 1, Name(mp->from),
			       mail_list_time(uncompress(mp->time), 0),
			       ((Connected(mp->from) &&
				 (!hidden(mp->from) || Hasprivs(player)))
				? "Conn" : " ")));
#endif
	if (SUPPORT_PUEBLO)
	  notify_noenter(player, tprintf("%c/A%c", TAG_START, TAG_END));
      }
    }
  }
  notify(player, DASH_LINE);
  if (SUPPORT_PUEBLO)
    notify(player, tprintf("%c/PRE%c", TAG_START, TAG_END));
  return;
}

static char *
mail_list_time(the_time, flag)
    const char *the_time;
    int flag;			/* 1 for no year */
{
  static char new[BUFFER_LEN];
  char *p, *q;
  int i;
  p = (char *) the_time;
  q = new;
  if (!p || !*p)
    return NULL;
  /* 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++;
  }
  if (!flag) {
    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 = find_exact_starting_point(player);
       mp && (mp->to == player);
       mp = nextp) {
    if ((mp->to == player) && Cleared(mp)) {
      /* Delete this one */
      /* head and tail of the list are special */
      if (mp == HEAD)
	HEAD = mp->next;
      else if (mp == TAIL)
	TAIL = mp->prev;
      /* 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 */
      mdb_top--;
#ifdef MAIL_SUBJECTS
      if (mp->subject != mp->message)
	free(mp->subject);
#endif
      free(mp->message);
      free(mp->time);
      free(mp);
    } else {
      nextp = mp->next;
    }
  }
  /* Clean up the player's mailp */
  if (Connected(player)) {
    desc_mail_set(player, NULL);
    desc_mail_set(player, find_exact_starting_point(player));
  }
  notify(player, "MAIL: Mailbox purged.");
  return;
}

void
do_mail_fwd(player, msglist, tolist)
    dbref player;
    char *msglist;
    char *tolist;
{
  struct mail *mp;
  struct mail *last;
  struct mail_selector ms;
  int j, num, folder;
  folder_array i;
  char *head, *tail, spot;
  struct mail *temp;
  dbref target;
  int num_recpts = 0;

  if (!parse_msglist(msglist, &ms, player)) {
    return;
  }
  if (!tolist || !*tolist) {
    notify(player, "MAIL: To whom should I forward?");
    return;
  }
  folder = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
  /* Mark the player's last message. This prevents a loop if
   * the forwarding command happens to forward a message back
   * to the player itself 
   */
  last = mp = find_exact_starting_point(player);
  if (!last) {
    notify(player, "MAIL: You have no messages to forward.");
    return;
  }
  while (last->next && (last->next->to == player))
    last = last->next;

  FA_Init(i, j);
  while (mp && (mp->to == player) && (mp != last->next)) {
    if ((mp->to == player) && (All(ms) || (Folder(mp) == folder))) {
      i[Folder(mp)]++;
      if (mail_match(player, mp, ms, i[Folder(mp)])) {
	/* forward it to all players listed */
	head = (char *) tolist;
	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;
	  /* Now locate a target */
	  num = atoi(head);
	  if (num) {
	    /* reply to a mail message */
	    temp = mail_fetch(player, num);
	    if (!temp) {
	      notify(player, "MAIL: You can't reply to nonexistant mail.");
	    } else {
#ifdef MAIL_SUBJECTS
	      send_mail(player, temp->from, mp->subject, (char *) mp->message, M_FORWARD | M_REPLY, 1);
#else
	      send_mail(player, temp->from, (char *) mp->message, M_FORWARD | M_REPLY, 1);
#endif
	      num_recpts++;
	    }
	  } else {
	    /* forwarding to a player */
	    if (!strcasecmp(head, "me"))
	      target = player;
	    else if (*head == '#') {
	      target = atoi(head + 1);
	      if (!GoodObject(target))
		target = NOTHING;
	    } else
	      target = lookup_player(head);
	    if ((target == NOTHING) || (Typeof(target) != TYPE_PLAYER)) {
	      notify(player, "No such player.");
	    } else {
#ifdef MAIL_SUBJECTS
	      send_mail(player, target, mp->subject, (char *) mp->message, M_FORWARD, 1);
#else
	      send_mail(player, target, (char *) mp->message, M_FORWARD, 1);
#endif
	      num_recpts++;
	    }
	  }
	  /* Get the next recip */
	  *tail = spot;
	  head = tail;
	  if (*head == '"')
	    head++;
	}
      }
    }
    mp = mp->next;
  }
  notify(player, tprintf("MAIL: %d messages forwarded.", num_recpts));
}

void
do_mail_send(player, tolist, message, flags, silent)
    dbref player;
    char *tolist;
    char *message;
    mail_flag flags;
    int silent;
{
  char *head, *tail, spot;
  int num;
  dbref target;
  int mail_flags;
#ifdef MAIL_SUBJECTS
  char sbuf[SUBJECT_LEN + 1], *sb, *mb;
  int i = 0, subject_given = 0;
#endif

  if (!tolist || !*tolist) {
    notify(player, "MAIL: I can't figure out who you want to mail to.");
    return;
  }
  if (!message || !*message) {
    notify(player, "MAIL: I can't figure out what you want to send.");
    return;
  }
#ifdef MAIL_SUBJECTS
  sb = sbuf;
  mb = message;			/* Save the message pointer */
  while (*message && (i < SUBJECT_LEN) && *message != SUBJECT_COOKIE) {
    *sb++ = *message++;
    i++;
  }
  *sb = '\0';
  if (*message && (*message == SUBJECT_COOKIE)) {
    message++;
    subject_given = 1;
  } else
    message = mb;		/* Rewind the pointer to the beginning */
#ifdef ALLOW_NOSUBJECT
  if (!subject_given)
    strcpy(sbuf, "(no subject)");
#endif
#endif
  /* Parse the player list */
  head = (char *) tolist;
  while (head && *head) {
    mail_flags = flags;
    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 */
    num = atoi(head);
    if (num) {
      /* reply to a mail message */
      struct mail *temp;

      temp = mail_fetch(player, num);
      if (!temp) {
	notify(player, "MAIL: You can't reply to nonexistant mail.");
	return;
      }
#ifdef MAIL_SUBJECTS
      if (subject_given)
	send_mail(player, temp->from, compress(sbuf), message, mail_flags, silent);
      else
	send_mail(player, temp->from, temp->subject, message,
		  mail_flags | M_REPLY, silent);
#else
      send_mail(player, temp->from, message, mail_flags, silent);
#endif
    } else {
      /* send a new mail message */
      if (!strcasecmp(head, "me"))
	target = player;
      else if (*head == '#') {
	target = atoi(head + 1);
	if (!GoodObject(target))
	  target = NOTHING;
      } else
	target = lookup_player(head);
      if (!GoodObject(target) || (Typeof(target) != TYPE_PLAYER))
	notify(player, "No such player.");
      else
#ifdef MAIL_SUBJECTS
	send_mail(player, target, compress(sbuf), message, mail_flags, silent);
#else
	send_mail(player, target, message, mail_flags, silent);
#endif
    }
    /* Get the next recip */
    *tail = spot;
    head = tail;
    if (*head == '"')
      head++;
  }
}

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

/*-------------------------------------------------------------------------*
 *   Basic mail functions
 *-------------------------------------------------------------------------*/
static struct mail *
mail_fetch(player, num)
    dbref player;
    int num;
{
  /* get an arbitrary mail message in the current folder */
  return real_mail_fetch(player, num, player_folder(player));
}

static struct mail *
real_mail_fetch(player, num, folder)
    dbref player;
    int num;
    int folder;
{
  struct mail *mp;
  int i = 0;

  for (mp = find_exact_starting_point(player); mp != NULL; mp = mp->next) {
    if (mp->to > player)
      break;
    if ((mp->to == player) && ((folder < 0) || (Folder(mp) == folder)))
      i++;
    if (i == num)
      return mp;
  }
  return NULL;
}


static void
count_mail(player, folder, rcount, ucount, ccount)
    dbref player;
    int folder;
    int *rcount;
    int *ucount;
    int *ccount;
{
  /* returns count of read, unread, & cleared messages as rcount, ucount,
   * ccount. folder=-1 returns for all folders */

  struct mail *mp;
  int rc, uc, cc;

  cc = rc = uc = 0;
  for (mp = find_exact_starting_point(player);
       mp && (mp->to == player);
       mp = mp->next) {
    if ((mp->to == player) && ((folder == -1) || (Folder(mp) == folder))) {
      if (Cleared(mp))
	cc++;
      else if (Read(mp))
	rc++;
      else
	uc++;
    }
  }
  *rcount = rc;
  *ucount = uc;
  *ccount = cc;
}


#ifdef MAIL_SUBJECTS
static void
send_mail(player, target, subject, message, flags, silent)
#else
static void
send_mail(player, target, message, flags, silent)
#endif
    dbref player;
    dbref target;
#ifdef MAIL_SUBJECTS
    unsigned char *subject;
#endif
    char *message;
    mail_flag flags;
    int silent;
{
  /* send a mail message */

  struct mail *newp, *mp;
  time_t tt;
  char tbuf1[30];
#ifdef MAIL_SUBJECTS
  char sbuf[BUFFER_LEN];
  ATTR *a;
#endif

  if (Typeof(target) != TYPE_PLAYER) {
    notify(player, "MAIL: You cannot send mail to non-existent people.");
    return;
  }
  if (!strcasecmp(message, "clear")) {
    notify(player, "MAIL: You probably don't wanna send mail saying 'clear'.");
    return;
  }
  if (!(Hasprivs(player) || eval_lock(player, target, Mail_Lock))) {
    notify(player, tprintf("MAIL: %s is not accepting mail from you right now.", Name(target)));
    return;
  }
  tt = time(NULL);
  strcpy(tbuf1, ctime(&tt));
  tbuf1[strlen(tbuf1) - 1] = '\0';	/* whack the newline */

  /* Append the MAILSIGNATURE to the mail - Cordin@Dune's idea */

  /* initialize the appropriate fields */
  newp = (struct mail *) mush_malloc(sizeof(struct mail), "mail");
  newp->to = target;
  newp->from = player;
#ifdef MAIL_SUBJECTS
  /* Deal with the subject */
  if (subject)
    strcpy(sbuf, uncompress(subject));
  else
    strcpy(sbuf, "(no subject)");
  if ((flags & M_FORWARD) && !string_prefix(sbuf, "Fwd:"))
    newp->subject = u_strdup(compress(chopstr(tprintf("Fwd: %s", sbuf), SUBJECT_LEN)));
  else if ((flags & M_REPLY) && !string_prefix(sbuf, "Re:"))
    newp->subject = u_strdup(compress(chopstr(tprintf("Re: %s", sbuf), SUBJECT_LEN)));
  else if ((a = atr_get_noparent(player, "MAILSUBJECT")) != NULL)
    /* Don't bother to uncompress a->value */
    newp->subject = u_strdup(a->value);
  else
    newp->subject = u_strdup(compress(sbuf));
#endif
  if (flags & M_FORWARD) {
    /* Forwarding passes the message already compressed */
    newp->message = u_strdup((unsigned char *) message);
  } else
    newp->message = u_strdup(compress(message));
  newp->time = u_strdup(compress(tbuf1));
  newp->read = flags & M_FMASK;	/* Send to folder 0 */

  /* Where do we insert it? After mp, wherever that is.
   * This can return NULL if there are no messages or
   * if we insert at the head of the list 
   */
  mp = find_insertion_point(target);

  if (mp) {
    newp->prev = mp;
    newp->next = mp->next;
    if (mp == TAIL)
      TAIL = newp;
    else
      mp->next->prev = newp;
    mp->next = newp;
  } else {
    if (HEAD) {
      /* Insert at the front */
      newp->next = HEAD;
      newp->prev = NULL;
      HEAD->prev = newp;
      HEAD = newp;
    } else {
      /* This is the first message in the maildb */
      HEAD = newp;
      TAIL = newp;
      newp->prev = NULL;
      newp->next = NULL;
    }
  }

  /* If the target's mailp isn't pointing to their list, we'd better
   * set it
   */
  if (Connected(target))
    desc_mail_set(target, find_exact_starting_point(target));

  mdb_top++;

  /* 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)));

  if (AMAIL_ATTR && (atr_get_noparent(target, "AMAIL")) && (player != target) &&
      Hasprivs(target))
    did_it(player, target, NULL, NULL, NULL, NULL, "AMAIL", NOTHING);

  return;
}


void
do_mail_nuke(player)
    dbref player;
{
  /* wipes the entire maildb */

  struct mail *mp, *nextp;

  if (!God(player)) {
    notify(player, "The postal service issues a warrant for your arrest.");
    return;
  }
  /* walk the list */
  for (mp = HEAD; mp != NULL; mp = nextp) {
    nextp = mp->next;
#ifdef MAIL_SUBJECTS
    if (mp->subject != mp->message)
      free(mp->subject);
#endif
    free(mp->message);
    free(mp->time);
    mush_free((Malloc_t) mp, "mail");
  }

  HEAD = TAIL = NULL;
  mdb_top = 0;

  do_log(LT_ERR, 0, 0, "** MAIL PURGE ** done by %s(#%d).",
	 Name(player), player);
  notify(player, "You annihilate the post office. All messages cleared.");
}

void
do_mail_debug(player, action, victim)
    dbref player;
    char *action;
    char *victim;
{
  /* how to fix mail with a sledgehammer */

  dbref target;
  struct mail *mp, *nextp;
  int i;

  if (!Wizard(player)) {
    notify(player, "Go get some bugspray.");
    return;
  }
  if (string_prefix("clear", action)) {
    target = lookup_player(victim);
    if (target == NOTHING) {
      target = match_result(player, victim, NOTYPE, MAT_ABSOLUTE);
    }
    if (target == NOTHING) {
      notify(player, tprintf("%s: no such player.", victim));
      return;
    }
    do_mail_clear(target, (char *) "ALL");
    do_mail_purge(target);
    notify(player, tprintf("Mail cleared for %s(#%d).", Name(target), target));
    return;
  } else if (string_prefix("sanity", action)) {
    for (i = 0, mp = HEAD; mp != NULL; i++, mp = mp->next) {
      if (!GoodObject(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));
    }
    if (i != mdb_top) {
      notify(player,
	     tprintf(
	  "Mail database top is %d, actual message count is %d. Fixing.",
		      mdb_top, i));
      mdb_top = i;
    }
    notify(player, "Mail sanity check completed.");
  } else if (string_prefix("fix", action)) {
    for (i = 0, mp = HEAD; mp != NULL; i++, mp = nextp) {
      if (!GoodObject(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 == HEAD)
	  HEAD = mp->next;
	else if (mp == TAIL)
	  TAIL = mp->prev;
	/* 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 */
	mdb_top--;
#ifdef MAIL_SUBJECTS
	if (mp->subject != mp->message)
	  free(mp->subject);
#endif
	free(mp->message);
	free(mp->time);
	mush_free((Malloc_t) mp, "mail");
      } 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;
{
  /* mail database statistics */

  dbref target;
  int fc, fr, fu, tc, tr, tu, fchars, tchars, cchars;
  char last[50];
  struct mail *mp;

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

  /* find player */

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

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

  if (options.daytime) {
    notify(player, "Sorry, that command has been temporarily disabled.");
    return;
  }
  if (!payfor(player, FIND_COST)) {
    notify(player, tprintf("Finding mail stats costs %d %s.", FIND_COST,
			   (FIND_COST == 1) ? MONEY : MONIES));
    return;
  }
  if (target == AMBIGUOUS) {	/* stats for all */
    if (full == 0) {
      notify(player,
	   tprintf("There are %d messages in the mail spool.", mdb_top));
      return;
    } else if (full == 1) {
      for (mp = HEAD; mp != NULL; mp = mp->next) {
	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 {
      for (mp = HEAD; mp != NULL; mp = mp->next) {
	if (Cleared(mp)) {
	  fc++;
	  cchars += strlen((char *) uncompress(mp->message));
	} else if (Read(mp)) {
	  fr++;
	  fchars += strlen((char *) uncompress(mp->message));
	} else {
	  fu++;
	  tchars += strlen((char *) uncompress(mp->message));
	}
      }
      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 */
    for (mp = HEAD; mp != NULL; mp = mp->next) {
      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 */
  for (mp = HEAD; mp != NULL; mp = mp->next) {
    if (mp->from == target) {
      if (Cleared(mp))
	fc++;
      else if (Read(mp))
	fr++;
      else
	fu++;
      if (full == 2)
	fchars += strlen((char *) uncompress(mp->message));
    }
    if (mp->to == target) {
      if (!tr && !tu)
	strcpy(last, (char *) uncompress(mp->time));
      if (Cleared(mp))
	tc++;
      else if (Read(mp))
	tr++;
      else
	tu++;
      if (full == 2)
	tchars += strlen((char *) uncompress(mp->message));
    }
  }

  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(player, arg1, arg2)
    dbref player;
    char *arg1;
    char *arg2;
{
  dbref sender;
  /* Force player to be a real player, but keep track of the
   * enactor in case we're sending mail, which objects can do
   */
  sender = player;
  player = Owner(player);
  if (!arg1 || !*arg1) {
    if (arg2 && *arg2) {
      notify(player, "MAIL: Invalid mail command.");
      return;
    }
    /* just the "@mail" command */
    do_mail_list(player, "");
    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 */
    do_mail_send(sender, arg1, arg2, 0, 0);
  } else {
    /* Must be reading or listing mail - no arg2 */
    if (isdigit(*arg1) && !index(arg1, '-'))
      do_mail_read(player, arg1);
    else
      do_mail_list(player, arg1);
  }
  return;
}

/*-------------------------------------------------------------------------*
 *   Auxiliary functions
 *-------------------------------------------------------------------------*/
/* ARGSUSED */
FUNCTION(fun_folderstats)
{
  /* This function can take one of four formats:
   * folderstats() -> returns stats for my current folder
   * folderstats(folder#) -> returns stats for my folder folder#
   * folderstats(player) -> returns stats for player's current folder
   * folderstats(player,folder#) -> returns stats for player's folder folder#
   */
  dbref player;
  int rc, uc, cc;

  if (nargs == 0) {
    count_mail(executor, player_folder(executor), &rc, &uc, &cc);
    safe_str(unparse_integer(rc + uc), buff, bp);
    return;
  }
  if (nargs == 1) {
    if (!is_integer(args[0])) {
      /* handle the case of wanting to count the number of messages */
      if ((player = noisy_match_result(executor, args[0], TYPE_PLAYER, MAT_OBJECTS)) == NOTHING) {
	safe_str("#-1 NO SUCH PLAYER", buff, bp);
	return;
      } else if ((executor != player) && !Wizard(executor)) {
	safe_str("#-1 PERMISSION DENIED", buff, bp);
	return;
      } else {
	count_mail(player, player_folder(player), &rc, &uc, &cc);
	safe_str(unparse_integer(rc), buff, bp);
	safe_chr(' ', buff, bp);
	safe_str(unparse_integer(uc), buff, bp);
	safe_chr(' ', buff, bp);
	safe_str(unparse_integer(cc), buff, bp);
	return;
      }
    } else {
      count_mail(executor, parse_integer(args[0]), &rc, &uc, &cc);
      safe_str(unparse_integer(rc + uc), buff, bp);
      return;
    }
  } else {
    if ((player = lookup_player(args[0])) == NOTHING) {
      safe_str("#-1 NO SUCH PLAYER", buff, bp);
      return;
    } else if ((executor != player) && !Wizard(executor)) {
      safe_str("#-1 PERMISSION DENIED", buff, bp);
      return;
    }
    if (!is_integer(args[1])) {
      safe_str("#-1 FOLDER MUST BE INTEGER", buff, bp);
      return;
    }
    count_mail(player, parse_integer(args[1]), &rc, &uc, &cc);
    safe_str(unparse_integer(rc), buff, bp);
    safe_chr(' ', buff, bp);
    safe_str(unparse_integer(uc), buff, bp);
    safe_chr(' ', buff, bp);
    safe_str(unparse_integer(cc), buff, bp);
    return;
  }
}

/* ARGSUSED */
FUNCTION(fun_mail)
{
  /* mail([<player>,] [<folder #>:]<message #>)
   * mail() --> return total # of messages for executor
   * mail(<player>) --> return total # of messages for player
   */

  struct mail *mp;
  dbref player;
  int rc, uc, cc;

  if (nargs == 0) {
    count_mail(executor, -1, &rc, &uc, &cc);
    safe_str(unparse_integer(rc + uc + cc), buff, bp);
    return;
  }
  /* Try mail(<player>) */
  if (nargs == 1) {
    player = match_result(executor, args[0], TYPE_PLAYER, MAT_OBJECTS);
    if (GoodObject(player)) {
      if ((executor != player) && !Wizard(executor)) {
	safe_str("#-1 PERMISSION DENIED", buff, bp);
      } else {
	count_mail(player, -1, &rc, &uc, &cc);
	safe_str(unparse_integer(rc), buff, bp);
	safe_chr(' ', buff, bp);
	safe_str(unparse_integer(uc), buff, bp);
	safe_chr(' ', buff, bp);
	safe_str(unparse_integer(cc), buff, bp);
      }
      return;
    }
  }
  /* That didn't work. Ok, try mailfun_fetch */
  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
  if (mp) {
    safe_str(uncompress(mp->message), buff, bp);
    return;
  }
  safe_str("#-1 INVALID MESSAGE OR PLAYER", buff, bp);
  return;
}

/* A helper routine used by all the mail*() functions
 * We parse the following format:
 *  func([<player>,] [<folder #>:]<message #>)
 * and return the matching message or NULL
 */
static struct mail *
mailfun_fetch(player, nargs, arg1, arg2)
    dbref player;
    int nargs;
    char *arg1;
    char *arg2;
{
  dbref target;
  int msg;
  int folder;

  if (nargs == 1) {
    /* Simply a message number or folder:message */
    if (parse_message_spec(player, arg1, &msg, NULL, &folder))
      return real_mail_fetch(player, msg, folder);
    else {
      return NULL;
    }
  } else {
    /* Both a target and a message */
    if ((target = noisy_match_result(player, arg1, TYPE_PLAYER, MAT_OBJECTS)) == NOTHING) {
      return NULL;
    } else if ((player != target) && !Wizard(player)) {
      notify(player, "Permission denied");
      return NULL;
    }
    if (parse_message_spec(target, arg2, &msg, NULL, &folder))
      return real_mail_fetch(target, msg, folder);
    else {
      notify(player, "Invalid message specification");
      return NULL;
    }
  }
  /* NOTREACHED */
  return NULL;
}


/* ARGSUSED */
FUNCTION(fun_mailfrom)
{
  struct mail *mp;

  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
  if (!mp)
    safe_str("#-1", buff, bp);
  else
    safe_str(unparse_dbref(mp->from), buff, bp);
  return;
}


/* ARGSUSED */
FUNCTION(fun_mailtime)
{
  struct mail *mp;

  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
  if (!mp)
    safe_str("#-1", buff, bp);
  else
    safe_str(uncompress(mp->time), buff, bp);
  return;
}

/* ARGSUSED */
FUNCTION(fun_mailstatus)
{
  struct mail *mp;

  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
  if (!mp)
    safe_str("#-1", buff, bp);
  else
    safe_str(status_chars(mp), buff, bp);
  return;
}


#ifdef MAIL_SUBJECTS
/* ARGSUSED */
FUNCTION(fun_mailsubject)
{
  struct mail *mp;

  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
  if (!mp)
    safe_str("#-1", buff, bp);
  else
    safe_str(uncompress(mp->subject), buff, bp);
  return;
}
#endif

int
dump_mail(fp)
    FILE *fp;
{
  struct mail *mp;
  int count = 0;
  int mail_flags = 0;

#ifdef MAIL_SUBJECTS
  mail_flags += MDBF_SUBJECT;
#endif

  if (mail_flags)
    fprintf(fp, "+%d\n", mail_flags);

  fprintf(fp, "%d\n", mdb_top);

  for (mp = HEAD; mp != NULL; mp = mp->next) {
    putref(fp, mp->to);
    putref(fp, mp->from);
    putstring(fp, uncompress(mp->time));
#ifdef MAIL_SUBJECTS
    if (mp->subject)
      putstring(fp, uncompress(mp->subject));
    else
      putstring(fp, "");
#endif
    putstring(fp, uncompress(mp->message));
    putref(fp, mp->read);
    count++;
  }

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

  if (count != mdb_top) {
    do_log(LT_ERR, 0, 0, "MAIL: Count of messages is %d, mdb_top is %d.",
	   count, mdb_top);
/*    mdb_top = count;    */
/*  Removed since it won't make a difference unless the process isn't forked */
  }
  return (count);
}



/* Find the first message in a player's mail chain, or NULL if none */
struct mail *
find_exact_starting_point(player)
    dbref player;
{
  static struct mail *mp;

  if (!HEAD)
    return NULL;
  mp = desc_mail(player);
  if (!mp) {
    /* Player is connected and has no mail, or nobody's connected who
     * has mail - we have to scan the maildb.
     */
    if (HEAD->to > player)
      return NULL;		/* No mail chain */
    for (mp = HEAD; mp && (mp->to < player); mp = mp->next) ;
  } else {
    while (mp && (mp->to >= player))
      mp = mp->prev;
    if (!mp)
      mp = HEAD;
    while (mp && (mp->to < player))
      mp = mp->next;
  }
  if (mp && (mp->to == player))
    return mp;
  return NULL;
}


/* Find the place where new mail to this player should go (after):
 *  1. The last message in the player's mail chain, or
 *  2. The last message before where the player's chain should start, or
 *  3. NULL (meaning TAIL)
 */
static struct mail *
find_insertion_point(player)
    dbref player;
{
  static struct mail *mp;

  if (!HEAD)
    return NULL;
  mp = desc_mail(player);
  if (!mp) {
    /* Player is connected and has no mail, or nobody's connected who
     * has mail - we have to scan the maildb.
     */
    if (HEAD->to > player)
      return NULL;		/* No mail chain */
    for (mp = TAIL; mp && (mp->to > player); mp = mp->prev) ;
  } else {
    while (mp && (mp->to <= player))
      mp = mp->next;
    if (!mp)
      mp = TAIL;
    while (mp && (mp->to > player))
      mp = mp->prev;
  }
  return mp;
}



void
mail_init()
{
  mdb_top = 0;
  HEAD = NULL;
  TAIL = NULL;
}

int
load_mail(fp)
    FILE *fp;
{
  char nbuf1[8];
  unsigned char *tbuf = NULL;
  int mail_top = 0;
  int mail_flags = 0;
  int i = 0;
  struct mail *mp, *tmpmp;
  int done;
#ifdef MAIL_SUBJECTS
  char sbuf[BUFFER_LEN];
#endif

  mail_init();

  /* find out how many messages we should be loading */
  fgets(nbuf1, sizeof(nbuf1), fp);
  /* If it starts with +, it's telling us the mail db flags */
  if (*nbuf1 == '+') {
    mail_flags = atoi(nbuf1 + 1);
    /* If the flags indicates aliases, we'll read them now */
    fgets(nbuf1, sizeof(nbuf1), fp);
  }
  mail_top = atoi(nbuf1);
  if (!mail_top)
    return 0;

  /* first one is a special case */
  mp = (struct mail *) mush_malloc(sizeof(struct mail), "mail");
  mp->to = getref(fp);
  mp->from = getref(fp);
  mp->time = u_strdup(compress((char *) getstring_noalloc(fp)));
  if (mail_flags & MDBF_SUBJECT) {
    tbuf = u_strdup(compress((char *) getstring_noalloc(fp)));
  }
  mp->message = u_strdup(compress((char *) getstring_noalloc(fp)));
#ifdef MAIL_SUBJECTS
  if (mail_flags & MDBF_SUBJECT)
    mp->subject = tbuf;
  else {
    strcpy(sbuf, uncompress(mp->message));
    mp->subject = u_strdup(compress(chopstr(sbuf, SUBJECT_LEN)));
  }
#endif
  mp->read = getref(fp);
  mp->next = NULL;
  mp->prev = NULL;
  HEAD = mp;
  TAIL = mp;
  i++;

  /* now loop through */
  for (; i < mail_top; i++) {
    mp = (struct mail *) mush_malloc(sizeof(struct mail), "mail");
    mp->to = getref(fp);
    mp->from = getref(fp);
    mp->time = u_strdup(compress((char *) getstring_noalloc(fp)));
    if (mail_flags & MDBF_SUBJECT)
      tbuf = u_strdup(compress((char *) getstring_noalloc(fp)));
    else
      tbuf = NULL;
    mp->message = u_strdup(compress((char *) getstring_noalloc(fp)));
#ifdef MAIL_SUBJECTS
    if (tbuf)
      mp->subject = tbuf;
    else {
      strcpy(sbuf, uncompress(mp->message));
      mp->subject = u_strdup(compress(chopstr(sbuf, SUBJECT_LEN)));
    }
#endif
    mp->read = getref(fp);

    /* We now to a sorted insertion, sorted by recipient db# */
    if (mp->to >= TAIL->to) {
      /* Pop it onto the end */
      mp->next = NULL;
      mp->prev = TAIL;
      TAIL->next = mp;
      TAIL = mp;
    } else {
      /* Search for where to put it */
      mp->prev = NULL;
      for (done = 0, tmpmp = HEAD; tmpmp && !done; tmpmp = tmpmp->next) {
	if (tmpmp->to > mp->to) {
	  /* Insert before tmpmp */
	  mp->next = tmpmp;
	  mp->prev = tmpmp->prev;
	  if (tmpmp->prev) {
	    /* tmpmp isn't HEAD */
	    tmpmp->prev->next = mp;
	  } else {
	    /* tmpmp is HEAD */
	    HEAD = mp;
	  }
	  tmpmp->prev = mp;
	  done = 1;
	}
      }
      if (!done) {
	/* This is bad */
	fprintf(stderr, "MAIL: bad code.\n");
      }
    }
  }


  mdb_top = i;

  if (i != mail_top) {
    fprintf(stderr, "MAIL: mail_top is %d, only read in %d messages.\n",
	    mail_top, i);
  }
  do_mail_debug(GOD, (char *) "fix", (char *) "");
  return (mdb_top);
}

static int
get_folder_number(player, name)
    dbref player;
    char *name;
{
  ATTR *a;
  char str[BUFFER_LEN], pat[BUFFER_LEN], *res, *p;
  /* Look up a folder name and return the appopriate number */
  a = (ATTR *) atr_get_noparent(player, "MAILFOLDERS");
  if (!a)
    return -1;
  strcpy(str, uncompress(a->value));
  sprintf(pat, ":%s:", strupper(name));
  res = strstr(str, pat);
  if (!res)
    return -1;
  res += 2 + strlen(name);
  p = res;
  while (!isspace(*p))
    p++;
  p = '\0';
  return atoi(res);
}

static char *
get_folder_name(player, fld)
    dbref player;
    int fld;
{
  static char str[BUFFER_LEN];
  char pat[BUFFER_LEN];
  static char *old;
  char *r;
  ATTR *a;

  /* Get the name of the folder, or "nameless" */
  sprintf(pat, "%d:", fld);
  old = NULL;
  a = (ATTR *) atr_get_noparent(player, "MAILFOLDERS");
  if (!a) {
    strcpy(str, "unnamed");
    return str;
  }
  strcpy(str, (char *) uncompress(a->value));
  old = (char *) string_match(str, pat);
  if (old) {
    r = old + strlen(pat);
    while (*r != ':')
      r++;
    *r = '\0';
    return old + strlen(pat);
  } else {
    strcpy(str, "unnamed");
    return str;
  }
}

void
add_folder_name(player, fld, name)
    dbref player;
    int fld;
    const char *name;
{
  char *old, *res, *r;
  char new[BUFFER_LEN], pat[BUFFER_LEN];
  char str[BUFFER_LEN], tbuf[BUFFER_LEN];
  ATTR *a;

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

  if (name && *name)
    sprintf(new, "%d:%s:%d ", fld, strupper(name), fld);
  else
    strcpy(new, " ");
  sprintf(pat, "%d:", fld);
  /* get the attrib and the old string, if any */
  old = NULL;
  a = (ATTR *) atr_get_noparent(player, "MAILFOLDERS");
  if (a) {
    strcpy(str, (char *) uncompress(a->value));
    old = (char *) string_match(str, pat);
  }
  if (old && *old) {
    strcpy(tbuf, str);
    r = old;
    while (!isspace(*r))
      r++;
    *r = '\0';
    res = (char *) replace_string(old, new, tbuf);	/* mallocs mem! */
  } else {
    r = res = (char *) malloc(BUFFER_LEN + 1);
    if (a)
      safe_str(str, res, &r);
    safe_str(new, res, &r);
    *r = '\0';
  }
  /* put the attrib back */
  atr_add(player, "MAILFOLDERS", res, GOD, AF_WIZARD | AF_NOPROG | AF_LOCKED);
  free(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 */
  ATTR *a;

  a = (ATTR *) atr_get_noparent(player, "MAILCURF");
  if (!a) {
    set_player_folder(player, 0);
    return 0;
  }
  return atoi(uncompress(a->value));
}

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

  sprintf(tbuf1, "%d", fnum);
  a = (ATTR *) atr_match("MAILCURF");
  if (a)
    atr_add(player, a->name, tbuf1, GOD, a->flags);
  else				/* Shouldn't happen, but... */
    atr_add(player, "MAILCURF", tbuf1, GOD, AF_WIZARD | AF_NOPROG | AF_LOCKED);
}

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(player, mp, ms, num)
    dbref player;
    struct mail *mp;
    struct mail_selector ms;
    int num;
{
  time_t now, msgtime, diffdays;
  char msgtimestr[BUFFER_LEN];
  struct tm *msgtm;
  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;
  if (AllInFolder(ms) && (Folder(mp) == player_folder(player)))
    return 1;
  mpflag = Read(mp) ? mp->read : (mp->read | M_MSUNREAD);
  if (!All(ms) && !(ms.flags & mpflag))
    return 0;
  if (ms.days != -1) {
    /* Get the time now, subtract mp->time, and compare the results with
     * ms.days (in manner of ms.day_comp) */
    time(&now);
    msgtm = (struct tm *) malloc(sizeof(struct tm));
    if (!msgtm) {
      /* Ugly malloc failure */
      do_log(LT_ERR, 0, 0, "MAIL: Couldn't malloc struct tm!");
      return 0;
    }
    strcpy(msgtimestr, (char *) uncompress(mp->time));
    if (do_convtime(msgtimestr, msgtm)) {
#ifdef HAS_TIMELOCAL
      msgtime = timelocal(msgtm);
#else
      msgtime = mktime(msgtm);
#endif				/* HAS_TIMELOCAL */
      free(msgtm);
      diffdays = (now - msgtime) / 86400;
      if (sign(diffdays - ms.days) != ms.day_comp)
	return 0;
    } else {
      free(msgtm);
      return 0;
    }
  }
  return 1;
}

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

  /* 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 notifys player of why. */
  /* Initialize the mail selector - this matches all messages */
  ms->low = 0;
  ms->high = 0;
  ms->flags = 0x00FF | 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 */
  strncpy(tbuf1, msglist, BUFFER_LEN - 1);
  p = tbuf1;
  while (p && *p && isspace(*p))
    p++;
  if (!p || !*p) {
    ms->flags |= M_FOLDER;
    return 1;			/* all messages in current folder */
  }
  if (isdigit(*p) || *p == '-') {
    if (!parse_message_spec(player, p, &ms->low, &ms->high, &folder)) {
      notify(player, "MAIL: Invalid message specification");
      return 0;
    }
    ms->flags |= FolderBit(folder);
  } else if (*p == '~') {
    /* exact # of days old */
    p++;
    if (!p || !*p) {
      notify(player, "MAIL: Invalid age");
      return 0;
    }
    if (!is_integer(p)) {
      notify(player, "MAIL: Message ages must be integers");
      return 0;
    }
    ms->day_comp = 0;
    ms->days = atoi(p);
    if (ms->days < 0) {
      notify(player, "MAIL: Invalid age");
      return 0;
    }
  } else if (*p == '<') {
    /* less than # of days old */
    p++;
    if (!p || !*p) {
      notify(player, "MAIL: Invalid age");
      return 0;
    }
    if (!is_integer(p)) {
      notify(player, "MAIL: Message ages must be integers");
      return 0;
    }
    ms->day_comp = -1;
    ms->days = atoi(p);
    if (ms->days < 0) {
      notify(player, "MAIL: Invalid age");
      return 0;
    }
  } else if (*p == '>') {
    /* greater than # of days old */
    p++;
    if (!p || !*p) {
      notify(player, "MAIL: Invalid age");
      return 0;
    }
    if (!is_integer(p)) {
      notify(player, "MAIL: Message ages must be integers");
      return 0;
    }
    ms->day_comp = 1;
    ms->days = atoi(p);
    if (ms->days < 0) {
      notify(player, "MAIL: Invalid age");
      return 0;
    }
  } else if (*p == '#') {
    /* From db# */
    if (!is_dbref(p)) {
      notify(player, "MAIL: Invalid dbref #");
      return 0;
    }
    ms->player = parse_dbref(p);
    if (!GoodObject(ms->player) || !(ms->player)) {
      notify(player, "MAIL: Invalid dbref #");
      return 0;
    }
  } else if (*p == '*') {
    /* From player name */
    p++;
    if (!p || !*p) {
      notify(player, "MAIL: Invalid player");
      return 0;
    }
    ms->player = lookup_player(p);
    if (ms->player == NOTHING) {
      notify(player, "MAIL: Invalid player");
      return 0;
    }
  } else if (!strcasecmp(p, "all")) {
    ms->flags = M_ALL;
  } else if (!strcasecmp(p, "folder")) {
    ms->flags = M_FOLDER;
  } else if (!strcasecmp(p, "urgent")) {
    ms->flags = M_URGENT;
  } else if (!strcasecmp(p, "unread")) {
    ms->flags = M_MSUNREAD;
  } else if (!strcasecmp(p, "read")) {
    ms->flags = M_READ;
  } else if (!strcasecmp(p, "clear") || !strcasecmp(p, "cleared")) {
    ms->flags = M_CLEARED;
  } else if (!strcasecmp(p, "tag") || !strcasecmp(p, "tagged")) {
    ms->flags = M_TAG;
  } else if (!strcasecmp(p, "mass")) {
    ms->flags = M_MASS;
  } else if (!strcasecmp(p, "me")) {
    ms->player = player;
  } else {
    notify(player, "MAIL: Invalid message specification");
    return 0;
  }
  return 1;
}

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

  strcpy(res, "");
  p = res;
  *p++ = Read(mp) ? '-' : 'N';
  *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 */
  static char tbuf1[BUFFER_LEN];

  strcpy(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");
  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 */

  /* just count messages */
  count_mail(player, folder, &rc, &uc, &cc);
  if (rc + uc + cc > 0)
    notify(player,
	   tprintf("MAIL: %d messages in folder %d [%s] (%d unread, %d cleared).",
	 rc + uc + cc, folder, get_folder_name(player, folder), uc, cc));
  else if (!silent)
    notify(player, tprintf("\nMAIL: You have no mail.\n"));
  return;
}

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

/* See if we've been given something of the form [f:]m1[-m2]
 * If so, return 1 and set f and mlow and mhigh
 * If not, return 0
 * If msghigh is given as NULL, don't allow ranges 
 * Used in parse_msglist, fun_mail and relatives.
 */
static int
parse_message_spec(player, s, msglow, msghigh, folder)
    dbref player;
    const char *s;
    int *msglow;
    int *msghigh;
    int *folder;
{
  char buf[BUFFER_LEN];
  char *p, *q;
  if (!s || !*s)
    return 0;
  strcpy(buf, s);
  if ((p = strchr(buf, ':'))) {
    *p++ = '\0';
    if (!is_integer(buf))
      return 0;
    *folder = parse_integer(buf);
    if (msghigh && (q = strchr(p, '-'))) {
      /* f:low-high */
      *q++ = '\0';
      if (!*p)
	*msglow = 0;
      else if (!is_integer(p))
	return 0;
      else {
	*msglow = parse_integer(p);
	if (*msglow == 0)
	  *msglow = -1;
      }
      if (!*q)
	*msghigh = 0;
      else if (!is_integer(q))
	return 0;
      else {
	*msghigh = parse_integer(q);
	if (*msghigh == 0)
	  *msghigh = -1;
      }
    } else {
      /* f:m */
      if (!is_integer(p))
	return 0;
      *msglow = parse_integer(p);
      if (*msglow == 0)
	*msglow = -1;
      if (msghigh)
	*msghigh = *msglow;
    }
    if (*msglow < 0 || (msghigh && *msghigh < 0) || *folder < 0 || *folder > MAX_FOLDERS)
      return 0;
  } else {
    /* No folder spec */
    *folder = player_folder(player);
    if (msghigh && (q = strchr(buf, '-'))) {
      /* low-high */
      *q++ = '\0';
      if (!*buf)
	*msglow = 0;
      else if (!is_integer(buf))
	return 0;
      else {
	*msglow = parse_integer(buf);
	if (*msglow == 0)
	  *msglow = -1;
      }
      if (!*q)
	*msghigh = 0;
      else if (!is_integer(q))
	return 0;
      else {
	*msghigh = parse_integer(q);
	if (*msghigh == 0)
	  *msghigh = -1;
      }
    } else {
      /* m */
      if (!is_integer(buf))
	return 0;
      *msglow = parse_integer(buf);
      if (*msglow == 0)
	*msglow = -1;
      if (msghigh)
	*msghigh = *msglow;
    }
    if (*msglow < 0 || (msghigh && *msghigh < 0))
      return 0;
  }
  return 1;
}

#endif				/* USE_MAILER */