tmi2_fluffos_v2/
tmi2_fluffos_v2/bin/
tmi2_fluffos_v2/etc/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/ChangeLog.old/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/Win32/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/compat/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/compat/simuls/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/include/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/clone/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/command/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/data/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/etc/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/include/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/inherit/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/inherit/master/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/log/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/single/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/single/tests/compiler/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/single/tests/efuns/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/single/tests/operators/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/testsuite/u/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/tmp/
tmi2_fluffos_v2/fluffos-2.7-ds2.018/windows/
tmi2_fluffos_v2/lib/
tmi2_fluffos_v2/lib/adm/
tmi2_fluffos_v2/lib/adm/daemons/languages/
tmi2_fluffos_v2/lib/adm/daemons/network/I3/
tmi2_fluffos_v2/lib/adm/daemons/virtual/
tmi2_fluffos_v2/lib/adm/daemons/virtual/template/
tmi2_fluffos_v2/lib/adm/news/
tmi2_fluffos_v2/lib/adm/obj/
tmi2_fluffos_v2/lib/adm/obj/master/
tmi2_fluffos_v2/lib/adm/priv/
tmi2_fluffos_v2/lib/adm/shell/
tmi2_fluffos_v2/lib/adm/tmp/
tmi2_fluffos_v2/lib/cmds/
tmi2_fluffos_v2/lib/d/
tmi2_fluffos_v2/lib/d/Conf/
tmi2_fluffos_v2/lib/d/Conf/adm/
tmi2_fluffos_v2/lib/d/Conf/boards/
tmi2_fluffos_v2/lib/d/Conf/cmds/
tmi2_fluffos_v2/lib/d/Conf/data/
tmi2_fluffos_v2/lib/d/Conf/logs/
tmi2_fluffos_v2/lib/d/Conf/obj/
tmi2_fluffos_v2/lib/d/Conf/text/help/
tmi2_fluffos_v2/lib/d/Fooland/adm/
tmi2_fluffos_v2/lib/d/Fooland/data/
tmi2_fluffos_v2/lib/d/Fooland/data/attic/
tmi2_fluffos_v2/lib/d/Fooland/items/
tmi2_fluffos_v2/lib/d/TMI/
tmi2_fluffos_v2/lib/d/TMI/adm/
tmi2_fluffos_v2/lib/d/TMI/boards/
tmi2_fluffos_v2/lib/d/TMI/data/
tmi2_fluffos_v2/lib/d/TMI/rooms/
tmi2_fluffos_v2/lib/d/grid/
tmi2_fluffos_v2/lib/d/grid/adm/
tmi2_fluffos_v2/lib/d/grid/data/
tmi2_fluffos_v2/lib/d/std/
tmi2_fluffos_v2/lib/d/std/adm/
tmi2_fluffos_v2/lib/data/adm/
tmi2_fluffos_v2/lib/data/adm/daemons/
tmi2_fluffos_v2/lib/data/adm/daemons/doc_d/
tmi2_fluffos_v2/lib/data/adm/daemons/emoted/
tmi2_fluffos_v2/lib/data/adm/daemons/network/http/
tmi2_fluffos_v2/lib/data/adm/daemons/network/services/mail_q/
tmi2_fluffos_v2/lib/data/adm/daemons/network/smtp/
tmi2_fluffos_v2/lib/data/adm/daemons/news/archives/
tmi2_fluffos_v2/lib/data/attic/connection/
tmi2_fluffos_v2/lib/data/attic/user/
tmi2_fluffos_v2/lib/data/std/connection/b/
tmi2_fluffos_v2/lib/data/std/connection/l/
tmi2_fluffos_v2/lib/data/std/user/a/
tmi2_fluffos_v2/lib/data/std/user/b/
tmi2_fluffos_v2/lib/data/std/user/d/
tmi2_fluffos_v2/lib/data/std/user/f/
tmi2_fluffos_v2/lib/data/std/user/l/
tmi2_fluffos_v2/lib/data/std/user/x/
tmi2_fluffos_v2/lib/data/u/d/dm/working/doc_d/
tmi2_fluffos_v2/lib/data/u/l/leto/doc_d/
tmi2_fluffos_v2/lib/data/u/l/leto/smtp/
tmi2_fluffos_v2/lib/doc/
tmi2_fluffos_v2/lib/doc/driverdoc/applies/
tmi2_fluffos_v2/lib/doc/driverdoc/applies/interactive/
tmi2_fluffos_v2/lib/doc/driverdoc/concepts/
tmi2_fluffos_v2/lib/doc/driverdoc/driver/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/arrays/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/buffers/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/compile/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/ed/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/filesystem/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/floats/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/functions/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/general/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/mappings/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/numbers/
tmi2_fluffos_v2/lib/doc/driverdoc/efuns/parsing/
tmi2_fluffos_v2/lib/doc/driverdoc/lpc/constructs/
tmi2_fluffos_v2/lib/doc/driverdoc/lpc/preprocessor/
tmi2_fluffos_v2/lib/doc/driverdoc/lpc/types/
tmi2_fluffos_v2/lib/doc/driverdoc/platforms/
tmi2_fluffos_v2/lib/doc/mudlib/
tmi2_fluffos_v2/lib/ftp/
tmi2_fluffos_v2/lib/include/driver/
tmi2_fluffos_v2/lib/log/
tmi2_fluffos_v2/lib/log/driver/
tmi2_fluffos_v2/lib/obj/net/
tmi2_fluffos_v2/lib/obj/shells/
tmi2_fluffos_v2/lib/obj/tools/
tmi2_fluffos_v2/lib/std/adt/
tmi2_fluffos_v2/lib/std/board/
tmi2_fluffos_v2/lib/std/body/
tmi2_fluffos_v2/lib/std/fun/
tmi2_fluffos_v2/lib/std/living/
tmi2_fluffos_v2/lib/std/object/
tmi2_fluffos_v2/lib/std/shop/
tmi2_fluffos_v2/lib/std/socket/
tmi2_fluffos_v2/lib/std/user/
tmi2_fluffos_v2/lib/std/virtual/
tmi2_fluffos_v2/lib/student/
tmi2_fluffos_v2/lib/student/kalypso/
tmi2_fluffos_v2/lib/student/kalypso/armor/
tmi2_fluffos_v2/lib/student/kalypso/rooms/
tmi2_fluffos_v2/lib/student/kalypso/weapons/
tmi2_fluffos_v2/lib/u/l/leto/
tmi2_fluffos_v2/lib/u/l/leto/cmds/
tmi2_fluffos_v2/lib/www/errors/
tmi2_fluffos_v2/lib/www/gateways/
tmi2_fluffos_v2/lib/www/images/
tmi2_fluffos_v2/old/
tmi2_fluffos_v2/win32/
#pragma save_binary

#define MAX_MESGS	50
#define PAGE	20

// Intermud Mailer by Huthar@portals
// Touched up by Buddha@TMI for his own mudlib (name unknown).
// Zak 930127   Added 'Q' (quit no changes), 's' (save note, mark for
//              delete, 'c' (save note, leave alone)
// Watcher 930327 added 'more' reading of the mail
// Zak 930411   fixed null subject bugs, added 'x' (same as 'q').
//
// Zak 930413   V2.1: rewrote most of it, basically to use mappings
//              added '$' (resync folder), 'n' (set current msg #)
//
// Zak 930502   V2.2: added 'p' (current message info). Now need a space
//              for 1st arg (excess chars chopped off).
// Zak 930601   improved group handling.
//              fixed a lot of the 'reply' code.
// Zak 930603   added do_resync, put in safety valve for null 'from' entry
//
// Zak 930605   upped to v2.5 (why not? :)
// Zak 930606   cleaned up text, refer to mail as `items' not `messages'.
// Zak 930622   fixed reply text.. now puts `<from> writes:' now
// Zak 930703   to save mail, now need a home dir. also, default
//              directory for saving is ~ NOT $CWD.
// Zak 930704   incorporated Dainia@TMI's personal group code. Sets
//              `using_mail' if in mailer. Implemented forwarding
//              (idea was Huthar's)
// Zak 930705   fixed `owner' bugs, display more accurate illegal note #
//              mesg. Display prompt after edit abort().
// Zak 930708   minor bugs fixed: cmsg not reset between resync
//              using_mail not being reset within `frm'
// Zak, 930714  check for null entries in TO/CC fields & ignore them.
// Grendel, 930809 Put a \\n after the subject when saving to a file.
// Grendel, 930904 Fixed a bug in mailing to remote groups
// Inspiral, 931230 added support for udp mail system.
// Inspiral, 940111 'w' option to save to file without headers
// Blue, 941027 Changed to allow saving to current directory,
//              not just home.  Added extra security there,
//              just to be on the safe side.  It was possible
//              before anyway, so there's no extra leak - just
//              extra convenience.
//              Also limited headers display to PAGE [20] lines
//              at a time.
// Blue, 941031 Added 'mail_headers' env variable for the number
//              of headers to display in a page.
// Leto 941109 Changed status to int.

#include <mailer.h>
#include <config.h>
#include <mudlib.h>
#include <uid.h>
#include <commands.h>
#include <daemons.h>
#include <net/daemons.h>
#include <net/services.h>
#include <net/macros.h>

inherit SECURE_OBJECT;


static int cmsg, maxmsg, start_flag, in_mailer, quitmailflag;
static string owner, tmpfilename;
static mapping newmsg;
static string mail_a_file;

mapping	my_groups;
mapping	*mailbox;
//      contains:
//      "from", "date", "cc", "subject", "message", "flags", "to"


static void parse_mailcmd( string cmd );
void get_text();
static void mail( string arg );


void
create() {
  seteuid( ROOT_UID );
  set( "id", ({ "mailer" }) );
  mailbox = ({  });
  my_groups = ([   ]);
} // create


void
init() {
  int i, j;
  
  if( in_mailer ) {
    remove();
    destruct( this_object() );
    return;
  }
  
  my_groups = ([   ]);
  owner = ( string ) this_player() -> link_data( "name" );
  if (!owner) {
    remove();
    destruct(this_object());
    return;
  }
  
  tmpfilename = temp_file( owner, this_player() ) + ".m";
  restore_object( user_mbox_file( owner ), 1 );
  maxmsg = sizeof( mailbox );
  
  i = maxmsg;

  if( !i ) {
    cmsg = 0;
    return;
  }
  
  for( cmsg =  - 1, i = 0; i <maxmsg; i++ )
  if( !( mailbox[i]["flags"]&MAIL_READ ) ) {
    cmsg = i;
    break;
  }
  
  if( cmsg ==  - 1 ) {
    cmsg = maxmsg - 1;
    if( cmsg < 0 )
      cmsg = 0;
  }
  this_player() -> set( "using_mail", 1 );
} // init


string
get_time( int t ) {
  return extract( ctime( t ), 0, 9 );
} // get_time


string
implode_caps( string *a ) {
  int i;
  string temp;

  i = sizeof( a );

  while( i-- )
    if( a[i] ) {
	  temp = "";
	  if( sscanf( a[i], "%s@%s", a[i], temp ) )
	     temp = "@" + nntoh( temp );
      a[i] = capitalize( a[i] ) + temp;
    }

  return( implode( a, ", " ) );
} // implode_caps


int
validate_num( string str ) {
  int num;
  
  if( str == "" )
    num = cmsg + 1;
  else
    sscanf( str, "%d", num );
  if( num < 1 || num> maxmsg )
    num = 0;  // force num to -1 on exit
  return--num;
} // validate_num


int
inccmsg() {
  if( !maxmsg )
    cmsg = 0;
  else if( ++cmsg >= maxmsg )
    cmsg = maxmsg - 1;
  return cmsg;
} // inccmsg


int *numlist( string str ) {
   mixed *ret, *tmp;
   int i, j, x, y;

   ret = ({ });
   str = replace_string( str, " ", "" );
   tmp = explode( str, "," );

   i = sizeof( tmp );
   while( i-- ) {
      if( sscanf( tmp[i], "%d-%d", x, y ) == 2 ) {
         while( x <= y ) {
             ret += ({ x });
             x++;
          }
      }
      else
         ret += ({ to_int( tmp[i] ) });
   }
 
   ret = uniq_array( ret );
   return ret;
}


static
varargs void
headers( int start, int newonly ) {
  int i, cnt, no_of_h;
  
  if( !maxmsg )  {
    printf( "No mail.\n" );
    cmsg = 0;
    return;
  }
  cnt = 0;

  no_of_h = this_player()->getenv( "mail_headers" );
  if (!no_of_h || !intp(no_of_h))
    no_of_h = PAGE;

  for ( i = start; (i < maxmsg) && (cnt < no_of_h); i++ )  {
    if( newonly && ( mailbox[i]["flags"]&MAIL_READ ) )
      continue;
    cnt++;

    if( !mailbox[i]["from"] )    {
	  mailbox[i]["flags"] |= OWNERDELETED;
      continue;
    }

     if( !mailbox[i]["idx"] ) mailbox[i]["idx"] = mailbox[i]["date"] + "";

    printf( "%s%s%2d %-30s (%s) %s\n",
      mailbox[i]["flags"]&MAIL_READ ? " " : "U",
      i == cmsg ?( mailbox[i]["flags"] & OWNERDELETED ? "<" : ">" )
      :( mailbox[i]["flags"] & OWNERDELETED ? "*" : " " ),
      i + 1,
      capitalize( mailbox[i]["from"] ),
      intp( mailbox[i]["date"] )?
      ctime( mailbox[i]["date"] )[0..9] : mailbox[i]["date"][0..9],
      mailbox[i]["subject"][0..25] );
  }
  if( newonly == 2 && !cnt ) // for 'from' command
    printf( "No new mail.\n" );
} // headers


static
string *
expand_to( string *raw_list ) {
  int i, j;
  string a, b, lcname, *zeta;
  mapping grps, rtn;
  
  rtn = ([   ]);
  grps = ( mapping ) GROUP_OB -> query_groups();
  
  raw_list -= ({ "" });
   raw_list -= ({ 0 });
  i = sizeof( raw_list );

  while( i-- )  {
    if( !raw_list[i] )
      continue;

    lcname = lower_case( raw_list[i] );

    if( !undefinedp( rtn[lcname] ) )
      continue;

    if( sscanf( lcname, "%s@%s", a, b ) == 2 )    {
        b = nntoh( b );
         if( !(int) DNS_MASTER -> dns_mudp( b ) || 
          !(int) DNS_MASTER -> query_named_service( b, "mail_q" ) ) {
        printf( "Cannot locate mud or mud doesn't support DNS mail: %s.\n",
                   b );
          printf( "Aborting send to %s@%s.\n", a, b );
          continue;
      }
      if( !a || a == "" )
        printf( "You must specify a username at the remote site.\n" );
      else
        rtn[lcname] = 1;
    }    else    {
      if( sscanf( raw_list[i], "(%s)", a ) )
        lcname = a;
      if( !user_exists( lcname ) )      {
        if( !undefinedp( grps[lcname] ) )        {
          lcname = "(" + lcname + ")";
          if( undefinedp( rtn[lcname] ) )
            rtn[lcname] = 1;
        }        else        {
          zeta = my_groups[lcname];
          if( undefinedp( zeta ) || !sizeof( zeta ) )
            printf( "No such user or group: %s.\n", lcname );
          else          {
            j = sizeof( zeta );
            while( j-- )
              rtn[zeta[j]] = 1;
          }
        }
      }
      else
        rtn[lcname] = 1;
    }
  }
  return( keys( rtn ) );
} // expand_to


void
prompt() {
  if( maxmsg )
    printf( "[%d of %d]& ", cmsg + 1, maxmsg );
  else
    printf( "& " );
} // prompt


static
varargs void
do_mail( string cmd ) {
  in_mailer = 1;

  if( cmd && cmd != "" )  {
    quitmailflag = 1;
    mail( cmd );
    return;
  }
  if( !in_edit( this_player() ) && !in_input( this_player() ) )
    prompt();
  input_to( "parse_mailcmd" );
} // do_mail


static
void
get_cc( string arg ) {
  int i, max;
  string *rtn;
  string tmpfile;
  
 arg = replace_string( arg, ",", " " );
  if( !pointerp( newmsg["cc"] ) ) newmsg["cc"] = ({ });

    newmsg["cc"] = expand_to( uniq_array( newmsg["cc"] + explode( arg, " " ) ) );
  
  rtn = ( string * ) MAILBOX_D -> send_mail( newmsg );
  
  max = sizeof( rtn );

  if( max )
    printf( "Mailing to: %s\n", implode_caps( rtn ) );
  else  {
    tmpfile = sprintf( "%s/%s.dead-letter", TMP_DIR, owner );
    printf( "No recipients found. Appending to: '%s'\n", tmpfile );
    write_file( tmpfile, newmsg["message"] + "\n" );
  }
  
  if( quitmailflag )
  {
    MAILBOX_D -> flush_files( my_groups );
    this_player() -> delete( "using_mail" );
    destruct( this_object() );
  }
  else
    do_mail();
} // get_cc


static
void
get_to( string arg ) {
  if( arg == "" )  {
    printf( "No users/groups specified.\n" );
    do_mail();
    return;
  }

  newmsg = ([   ]);
   arg = replace_string( arg, ",", " " );
  newmsg["from"] = capitalize( owner );
  newmsg["date"] = time();
  newmsg["to"] = expand_to( explode( arg, " " ) );

  if( !sizeof( newmsg["to"] ) )  {
    if( quitmailflag )    {
      this_player() -> delete( "using_mail" );
      destruct( this_object() );
    }
    else
      do_mail();
    return;
  }
  newmsg["cc"] = ({  });
  if( !mail_a_file ) {
      printf( "Subject: " );
      input_to( "get_subject" );
  }
  else this_object() -> get_subject();
} // get_to


static
void
mail( string arg ) {
  sscanf( arg, "%s<%s", arg, mail_a_file );
  if( stringp( mail_a_file ) && strlen( mail_a_file ) ) {
     mail_a_file = replace_string( mail_a_file, " ", "" );
     mail_a_file = resolv_path( "cwd", mail_a_file );
  
   if( !file_exists( mail_a_file ) || !master()->valid_read( mail_a_file,
		geteuid( this_player() ), "read_file" ) ) {
        write( "File: " + identify( mail_a_file ) + 
	       " does not exist.  Continuing.\n" );
	     mail_a_file = 0;
     }
  }
  else mail_a_file = 0;

  if( arg == "" )  {
    printf( "To: " );
    input_to( "get_to" );
    return;
  }
  get_to( arg );
} //  mail


int
do_from( string arg ) {
  if (file_name(previous_object()) != CMD_FROM)
    return 0;

  start_flag = 0;
  init();
  cmsg =  - 1;  // don't print cur message marker in 'frm'

  if( arg == "new" )
    headers( 0, 2 );
  else
// Changed by Dm 15 March 1996 because to_int was unrecognized.
// Changed back after a bit more checking.
    headers( to_int(arg) );
  in_mailer = 0;
  this_player() -> delete( "using_mail" );
  destruct( this_object() );
  return 1;
} // do_from


static
int
update_box() {
  int i, num;
  i = maxmsg;

  while( i-- )  {
    if( mailbox[i]["flags"] & OWNERDELETED )    {
      num++;
      MAILBOX_D -> remove_message( owner, i );
    }
    else
      MAILBOX_D -> set_flags( owner, i, mailbox[i]["flags"] );
  }
  return num;
} // update_box


static
void
do_quit( string cmd ) {
  int num, size;
  
  if( cmd == "Q" )
    printf( "No changes to mailbox.\n" );
  else  {
    num = update_box();
    if( !num )
      printf( "No items deleted.\n" );
    else
      printf( "%d item%s deleted.\n", num, ( num == 1 ? "" : "s" ) );
  }
   if( !adminp(geteuid( this_player() )) &&
    (size = sizeof( mailbox ) - num) > MAX_MESGS ) {
      printf( "You have %d messages, and our limit is %d.\n"
              "Please delete %d message(s) before you quit.\n",
              size, MAX_MESGS, size - MAX_MESGS );
      do_mail();
      return;
   }
  MAILBOX_D -> flush_files( my_groups );
  this_player() -> delete( "using_mail" );
  in_mailer = 0;
  destruct( this_object() );
  return;
} // do_quit


int
done_more() {
  do_mail();
  return 1;
} // done_more


static
void
get_subject( string sub ) {
  int i, max;
  
  if( mail_a_file )
    sub = "File: " + mail_a_file;

  if( sub == "" )  {
    printf( "Null subject line. Hope that's ok.\n" );
    sub = "<no subject>";
  }
  newmsg["subject"] = sub;
  printf( "\n\nFrom   : %s\nTo     : %s",
	 newmsg["from"], implode_caps( newmsg["to"] ) );
  printf( "\nDate   : %s\nSubject: %s\n",
    ctime( newmsg["date"] ), newmsg["subject"] );
  rm( tmpfilename );
  if( !mail_a_file )
  this_player() -> edit( tmpfilename, "get_text", this_object() );
  else {
	 write( "Inserting file " + identify( mail_a_file ) +
		" into message buffer.\n" );
     get_text();
  }
} // get_subject


void
abort() {
  if( previous_object() != find_player( owner ) ) return;
  rm( tmpfilename );

  if( quitmailflag )  {
    this_player() -> delete( "using_mail" );
    destruct( this_object() );
    return;
  }
  prompt();  // because abort() is still technically `in_edit()'
  do_mail();
} // abort


void
get_text() {
  if( previous_object() != find_player( owner ) ) return;
  if( mail_a_file )
    newmsg["message"] = read_file( mail_a_file );
  else
  newmsg["message"] = read_file( tmpfilename );
  if( !newmsg["message"] || newmsg["message"] == "" )
    newmsg["message"] = "Blank message!\n";
  rm( tmpfilename );

  if( !this_player() -> getenv( "mail_no_cc" ) )  {
    printf( "Cc: " );
    input_to( "get_cc" );
    return;
  }

  get_cc( "" );
}


static
void
do_header( string arg ) {
  int start;
  
  if( arg == "" )
    headers( 0 );
  else  {
    if( ( start = validate_num( arg ) ) ==  - 1 )
      printf( "Mail # out of range.\n" );
    else
      headers( start );
  }
} // do_header


static
void
do_setpos( string arg ) {
  int p;
  if( ( p = validate_num( arg ) ) ==  - 1 )
    printf( "No such item #%s\n", arg );
  else  {
    printf( "Current item set to %d\n", p + 1 );
    cmsg = p;
  }
} // do_setpos


static
void
read_mail( int num ) {
  string tmp_msg, to, cc;
  int i, max;
  
  to = cc = "";
  max = sizeof( mailbox[num]["to"] );

  for( i = 0; i <max; i++ )
    to += capitalize( mailbox[num]["to"][i] ) + " ";
  max = sizeof( mailbox[num]["cc"] );

  if( max )  {
    cc = "Cc     : ";

    for( i = 0; i < max; i++ )    {
      if( !mailbox[num]["cc"][i] )
        continue;
      cc += capitalize( mailbox[num]["cc"][i] ) + " ";
    }
    cc += "\n";
  }
  
  if( !mailbox[num]["from"] )
    mailbox[num]["from"] = "Unknown Sender (mailerob)!";
  
  tmp_msg = sprintf( 
    "Item #%d%s\nTo     : %s\n%sFrom   : %s\nDate   : %s\n"
    "Subject: %s\n===================================\n"
    "%s\n",
    ( num + 1 ),
    ( mailbox[num]["flags"] & OWNERDELETED )? " \t[DELETED]" : "",
    to,
    cc,
    capitalize( mailbox[num]["from"] ),
    intp( mailbox[num]["date"] )?
    ctime( mailbox[num]["date"] ): mailbox[num]["date"],
    mailbox[num]["subject"],
    MAILMESG_D -> get_mesg( mailbox[num]["idx"] )
    );
  
  this_player() -> more( explode( tmp_msg, "\n" ), 1, "Message #" + ( num + 1 ) );
  
  mailbox[num]["flags"] |= MAIL_READ;
} // read_mail


static
void
do_help( string arg ) {
  if( arg == "long" )
    this_player() -> more( MAILER_LONG, 1 );
  else
    this_player() -> more( MAILER_SHORT, 1 );
} // do_help


static
void
delete_mail( string str ) {
  int i, num;
  int *list;
  
  if( !str || str == "" )
     list = ({ "" });
  else
  list = numlist( str );
  i = sizeof( list );

  while(i--) {
    if( ( num = validate_num( list[i] + "" ) ) ==  - 1 )  {
      printf( "Illegal item value: %d\n", list[i]+1 );
      do_mail();
      continue;
    }
  
    if( mailbox[num]["flags"] & OWNERDELETED )  {
      printf( "Item #%d already deleted.\n", num + 1 );
      continue;
    }
  
    mailbox[num]["flags"] |= OWNERDELETED;
    printf( "Item #%d marked for deletion.\n", num + 1 );
  }
} // delete_mail


static
void undelete_mail( string str ) {
  int num;
  
  if( ( num = validate_num( str ) ) ==  - 1 )  {
    printf( "Illegal item value.\n" );
    return;
  }
  
  if( !( mailbox[num]["flags"] & OWNERDELETED ) )  {
    printf( "Item #%d is not deleted.\n", num + 1 );
    return;
  }
  mailbox[num]["flags"] &= ~OWNERDELETED;
  printf( "Item #%d has been restored.\n", num + 1 );
} // undelete_mail


static
void do_reply( string cmd, string numstr ) {
  int num, i, max;
  
  if( ( num = validate_num( numstr ) ) ==  - 1 )  {
    printf( "Illegal item number!\n" );
    return;
  }
  
  newmsg = ([   ]);
  if( cmd == "r" )  {
    newmsg["to"] = ({ mailbox[num]["from"] });
  }  else  {
    newmsg["to"] = uniq_array( ({ mailbox[num]["from"] })
      + mailbox[num]["to"] );
    newmsg["cc"] = mailbox[num]["cc"];
  }
  
  newmsg["from"] = capitalize( owner );
  newmsg["date"] = time();
  newmsg["subject"] = mailbox[num]["subject"];
  if( strlen( newmsg["subject"] ) < 4 || newmsg["subject"][0..3] != "RE: " )
    newmsg["subject"] = "RE: " + newmsg["subject"];
  
  printf( "\n\nFrom    : %s\nTo     : %s\n", newmsg["from"],
    implode_caps( newmsg["to"] ) );

  if( sizeof( newmsg["cc"] ) )
    printf( "Cc     : %s\n", implode_caps( newmsg["cc"] ) );
  printf( "Date   : %s\nSubject: %s\n",
	 ctime( newmsg["date"] ), newmsg["subject"] );
  rm( tmpfilename );

  if( this_player() -> getenv( "mail_use_reply_text" ) )  {
    newmsg["message"] = sprintf( "%s writes:\n> %s",
      capitalize( mailbox[num]["from"] ),
      replace_string( (string) MAILMESG_D -> get_mesg( mailbox[num]["idx"] ),
			"\n", "\n> " ) );
    write_file( tmpfilename, newmsg["message"] + "\n" );
  }
  this_player() -> edit( tmpfilename, "get_text", this_object() );
} // do_reply


static
void
add_group( string str ) {
  string *members, *res, grp, a, b;
  mapping grps;
  int i, n;
  
  if( !str || str == "" )  {
    printf( "No group specified.\n" );
    return;
  }
   str = replace_string( str, ",", " " );
  members = explode( str, " " );
  n = sizeof( members );

  if( n == 1 )  {
    printf( "No group members named.\n" );
    return;
  }
  grp = lower_case( members[0] );
  members = members[1..( --n )];
  i = n;

  while( i-- )
    members[i] = lower_case( members[i] );

  if( member_array( grp, members ) !=  - 1 )  {
    printf( "You cannot name a group as a member of itself.\n" );
    return;
  }
  grps = ( mapping ) GROUP_OB -> query_groups();

  if( !undefinedp( grps[grp] ) )  {
    printf( "A pre-defined mud group of that name already exists.\n" );
    return;
  }

  if( user_exists( grp ) )  {
    printf( "A player exists with the name you chose for your group.\n" );
    return;
  }

  if( !my_groups )
    my_groups = ([   ]);
  if( !my_groups[grp] )
    my_groups[grp] = ({  });
  
  res = allocate( n );
  i = 0;

  while( n-- )  {
    if( ( sscanf( members[n], "%s@%s", a, b ) == 2 )
      || ( user_exists( members[n] ) ) )
      res[i++] = members[n];
  }
  if( i )
    my_groups[grp] += res[0..--i];
  printf( "Personal group %s added to.\n", grp );
} // add_group


static
void
remove_group( string str ) {
  string *members;
  string grp;
  int i, n;
  
  if( !str || str == "" )  {
    printf( "No group specified.\n" );
    return;
  }
   str = replace_string( str, ",", " " );
  members = explode( str, " " );
  n = sizeof( members );

  if( n == 1 )  {
    printf( "No members named for removal.\n" );
    return;
  }
  grp = lower_case( members[0] );
  members = members[1..( --n )];
  i = n;

  while( i-- )  {
    members[i] = lower_case( members[i] );
    if( member_array( members[i], my_groups[grp] ) !=  - 1 )
      my_groups[grp] -= ({ members[i] });
    else
      printf( "%s: Not a member of group %s.\n", members[i], grp );
  }
  if( !sizeof( my_groups[grp] ) )
    map_delete( my_groups, grp );
  printf( "Removal complete.\n" );
} // remove_group


static
void
do_groups( string arg ) {
  mapping grps;
  string *groups;
  int i;
  
  grps = ( mapping ) GROUP_OB -> query_groups();
  groups = keys( grps );
  i = sizeof( groups );

  while( i-- )
    if( sizeof( grps[groups[i]] ) == 1 )
      map_delete( grps, groups[i] );

  if( arg != "" )  {
    if( !sizeof( grps[arg] ) && !sizeof( my_groups[arg] ) )    {
      printf( "That group does not exist!\n" );
      return;
    }
    printf( "Members of groups (%s):\n%-#75s\n", arg,
      my_groups[arg] ? implode( my_groups[arg], "\n" )
      : implode( grps[arg], "\n" ) );
    return;
  }
  groups = keys( grps );
  if( my_groups )
    groups += keys( my_groups );
  printf( "Current active groups:\n%-#75s\n", implode( groups, "\n" ) );
} // do_groups


static
void
get_forward_to( string arg, int num ) {
  string t;

  if( arg == "" )  {
    printf( "No users/groups specified.\n" );
    do_mail();
    return;
  }
  newmsg = ([   ]);
   arg = replace_string( arg, ",", " " );
  newmsg["from"] = owner;
  newmsg["date"] = time();
  newmsg["to"] = expand_to( explode( arg, " " ) );

  if( !sizeof( newmsg["to"] ) )  {
    do_mail();
    return;
  }
  newmsg["cc"] = ({  });
  t = mailbox[num]["subject"];
  if( strlen( t ) <  5 || t[0..4] != "FWD: " )
    t = "FWD: " + t;
  newmsg["subject"] = t;
  printf( "\n\nFrom    : %s\nTo     : %s\n", newmsg["from"],
    implode_caps( newmsg["to"] ) );

  if( sizeof( newmsg["cc"] ) )
    printf( "Cc     : %s\n", implode_caps( newmsg["cc"] ) );
  printf( "Date   : %s\nSubject: %s\n",
	 ctime( newmsg["date"] ), newmsg["subject"] );
  rm( tmpfilename );
  newmsg["message"] = sprintf( "BEGIN FORWARDED TEXT:\n%s writes:\n> %s",
    capitalize( mailbox[num]["from"] ),
    replace_string( (string) MAILMESG_D -> get_mesg( mailbox[num]["idx"] ),
		"\n", "\n> " ) );
  write_file( tmpfilename, newmsg["message"] + "\n" );
  this_player() -> edit( tmpfilename, "get_text", this_object() );
} // get_forward_to


static
void
do_forward( string arg ) {
  string target;
  int num;
  
  if( !arg || arg == "" )  {
    printf( "To: " );
    input_to( "get_forward_to", 0, cmsg );
    return;
  }
  num = 0;

  if(  sscanf( arg, "%d %s", num, target ) != 2
    && sscanf( arg, "%d", num ) != 1 )  {
    printf( "Usage: f <num> [<destination>]\n" );
    return;
  }
  if( sscanf( arg, "%d %s", num, target ) != 2 )
    num = cmsg;

  if( ( num = validate_num( num + "" ) ) ==  - 1 )  {
    printf( "Illegal item number!\n" );
    return;
  }

  if( !target )  {
    printf( "To: " );
    input_to( "get_forward_to", 0, num );
    return;
  }
  get_forward_to( target, num );
} // do_forward


static
varargs
void
do_save( string cmd, string arg, int flag )
{
  string file, dir, to, cc, errmsg;
  int i, num, max;
  
  if ( this_player()!=this_player(1) ||
    geteuid(this_player()) != owner ) {
      errmsg = sprintf("Mailer security: %s (interactive) %s (this_player()) %s (owner)\n", geteuid(this_player(1)), geteuid(this_player()), owner);
      message("error", errmsg, ({ this_player(), this_player(1), find_player("owner") }));
      log_file("mailer", errmsg);
  }

  dir = user_path( owner );
  if( !directory_exists( dir ) ) {
    printf( "Sorry, but only wizards with directories can save mail.\n" );
    return;
  }
  dir = this_player()->query("cwd");
  num = 0;
  if( arg == "" || ( sscanf( arg, "%s %d", file, num ) != 2 &&
    sscanf( arg, "%s", file ) != 1 ) )
  {
    printf( "Usage: %s <file> [<num>]\n", cmd );
    return;
  }
  if( sscanf( arg, "%s %d", file, num ) != 2 )
    num = cmsg + 1;
  
  if( ( num = validate_num( num + "" ) ) ==  - 1 )
  {
    printf( "Illegal item number!\n" );
    return;
  }
  file = resolv_path( dir, file );
  if (file_size(file) == -2) {
    printf("%s: Can't write to a directory.\n", file);
    return;
  }
  if (!master() -> valid_write( file, owner, "do_save" ) )
  {
    printf( "%s: Permission denied.\n", file );
    return;
  }
  if( sizeof( mailbox[num]["cc"] ) )
    cc = implode_caps( mailbox[num]["cc"] );
  else
    cc = "";
  
  if( !flag )
  write_file( file, sprintf( "\
From : %s\nTo : %s\n%s\nDate : %s\nSubject: %s\n\
==================================\n",
    capitalize( mailbox[num]["from"] ),
    implode_caps( mailbox[num]["to"] ),
    cc,
    intp( mailbox[num]["date"] )?
    ctime( mailbox[num]["date"] ): mailbox[num]["date"],
    mailbox[num]["subject"] ) );
  write_file( file,
	 sprintf( "%s\n",
		 (string) MAILMESG_D -> get_mesg( mailbox[num]["idx"] ) ) );
  
  printf( "Item '%s' written to: %s.\n", mailbox[num]["subject"], file );
  if( cmd == "s" || cmd == "w" )
    mailbox[num]["flags"] |= OWNERDELETED;
} // do_save


static
varargs
void
do_resync( int flag )
{
  if( !flag )
    printf( "Re-syncing folder.\n" );
  ( void ) update_box();
  MAILBOX_D -> flush_files( my_groups );
  in_mailer = 0;  // to stop init from remove()ing us..
  init();
  headers( 0, this_player() -> getenv( "mail_entry_unread" ) == 0 ? 0 : 1 );
  in_mailer++;
} // do_resync


static
void
parse_mailcmd( string cmd )
{
  string arg, ocmd, tmp;
  int num;
  
  ocmd = cmd;
  arg = "";
  
  if( cmd == "" || cmd == "n" || cmd == "+" )
  {
    if( start_flag && cmsg == inccmsg() )
      ocmd = "-1";
    else
      ocmd = "" + ( cmsg + 1 );
    cmd = "n";  // makes switch() below easier.
  }
  else
  {
    sscanf( cmd, "%s %s", cmd, arg );
    cmd = cmd[0..0];
  }
  
  switch( cmd )
  {
    case "d":
    delete_mail( arg );
    break;
    case "g":
    do_groups( arg );
    break;
    case "a":
    add_group( arg );
    break;
    case "A":
    remove_group( arg );
    break;
    case "m":
    mail( arg );
    return;
    case "f":
    do_forward( arg );
    break;
    case "#":
    do_setpos( arg );
    break;
    case "-":
    case "p":
    start_flag = 1;
    cmsg--;
    if (cmsg < 0) {
        printf( "At first item.\n" );
        cmsg = 0;
    } else {
        read_mail( cmsg );
        return;
    }
    break;
    case "R":
    case "r":
    do_reply( cmd, arg );
    return;
    case "Q":
    case "q":
    case "x":
    do_quit( cmd );
    return;
    case "u":
    undelete_mail( arg );
    break;
    case "?":
    do_help( arg );
    break;
    case "i":
    case "h":
    do_header( arg );
    break;
    case "c":
    case "s":
    do_save( cmd, arg );
    break;
    case "w":
    do_save( cmd, arg, 1 );
    break;
    case "$":
    do_resync();
    break;
    case "n":
    default:
    if( ocmd && sscanf( ocmd, "%d", num ) )
    {
      if( num> 0 && num  <= maxmsg )
      {
        cmsg = num - 1;
        start_flag = 1;
        read_mail( num - 1 );
        return;
      }
      else
        if( cmd == "n" )
          printf( "At last item.\n" );
      else
        printf( "Invalid note number.\n" );
    }
    else
      printf( "Invalid mail command.\n" );
    break;
  } // switch
  do_mail();
} // parse_mailcmd


int
start_mail( string arg )
{
  if( file_name( previous_object() ) != CMD_MAIL ) {
    printf( "You can't do that!\n" );
    destruct( this_object() );
    return 0;
  }

  start_flag = 0;
  if( !arg ) //  Don't show headers if just mailing
  {
    printf( "InterMUD Mailer v3.2  [? for help]\n" );
    do_resync( 1 );
  }
  do_mail( arg );
  return 1;
} // start_mail


int
valid_shadow()
{
  return 1;
}