/* iDIRT Message System Version 1.03/PE (Public Edition)
* by Illusion (1995)
*
* Derived from the DYRT mailer from DYRT 1.1c
* by Thrace [Valentin Popescu] (vpopesc@calstatela.edu)
* -------------------------------------------------------------------------
* *** NOTE ***
* DO NOT CHANGE THE VERSION INFORMATION IN THIS MAILER!!
* IF YOU CHANGE THE MAILER CONSIDERABLY, GIVE CREDIT TO
* BOTH ILLUSION AND THRACE. THRACE WORKED HARD ON THIS
* MAILER, AND ILLUSION WORKED HARD TO DEBUG IT, THEY
* DESERVE CREDIT FOR DOING SO.
*
* Basically what I did was take this source code from the Dyrt 1.1c
* source code and started rewriting it and trying to make it work with
* the iDIRT (or any other Dirt) code. I had to fix a few bugs that the
* mailer had which were:
*
* 1.00
* ----
* - A bug in the dump_headers() function that made the whole MUD crash
* when you scan your mail when you typed 'mail' to read it (read
* the headers). What was happening was this: the MUD was calling a
* bflush() that seemed to be crashing the MUD. Removing this bflush()
* still killed it. After trying out many different techniques I
* figured (basically guessed *grin*) that the error was coming from
* the bflush() calling fflush() when the mail file was at the EOF (or
* past it, that was my idea). So I had the dump_headers() check to see
* if the pointer that accesses the mail file is located past the EOF
* or not. Well, this fixed it and it no longer crashes.
*
* - A bug in the write_mail() function that didn't let you abort the
* message if you had a blank subject.
*
* - Fixed a bug in the mail menu that would cause it to stay in an
* infinite mail loop. This bug was caused by a user running the
* mailer from within the mailer again (*mail).
*
* - Players can now no longer alias from the mail menu.
*
* 1.01
* ----
* - Fixed the bug that allowed players to put the EOM text in the
* message. Change the EOM_MARKER #define to whatever you see fit
* and keep it a secret so that nobody can kill players mail files.
* You may even wish to use control characters for the EOM marker,
* your decision.
*
* - Added some color to the mailer.
*
* 1.02
* ----
* - Fixed a bug in which if users entered any format character into
* the subject, it crashed the MUD.
*
* 1.03
* ----
* - Fixed a bug in which if users entered any format character into
* the text, it crashed the MUD.
*
* - Now the mailer saves the player's prompt before entering the mailer.
*
* - Released a Public Version removing the iDIRT specific code.
*
* 1.04
* ----
* - Added multiple recipients
* - Added forwarding
*/
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include "kernel.h"
#include "objects.h"
#include "mobiles.h"
#include "mobile.h"
#include "locations.h"
#include "sflags.h"
#include "pflags.h"
#include "mflags.h"
#include "cflags.h"
#include "quests.h"
#include "sendsys.h"
#include "levels.h"
#include "parse.h"
#include "exec.h"
#include "rooms.h"
#include "mail.h"
#include "bprintf.h"
#include "stdinc.h"
/* Function Prototypes
Boolean concat_files (char *to, char *from);
*/
void cc_mail( char *u );
/* Mail system defines */
#define EOM_MARKER "\001\002\003"
#define MAX_LETTERS 150
#define READ 1
#define WRITE 0
#define SUBJECT "&+CSubject: &+w"
#define CC "&+CCC: &+w"
#define SENT "\n&+CMessage sent.\n"
#define MAILP "&+c:&+C> &+w"
#define PROMPT "&+C[&+wMail&+C] &+b:&+w "
#define NOTICE1 "\
&+wEnter your message. To save your message type &+W'=s' &+wor &+W'**' &+won \
a blank line.\nTo abort your message type &+W'=a' &+wor &+W'*abort' &+won \
a blank line.\n"
int count;
/* mailcom(): Main loop for the mail program. This function checks to see if
* the person trying to access the mailer is aliased, and then checks if a
* player's name was passed through the command line and will startup the
* mail sender if they are found.
*/
void mailcom (void)
{
PERSONA p;
/* Do not let aliased players use the mailer */
if (cur_player->aliased || cur_player->polymorphed != -1)
{
bprintf ("You cannot use the mailer while you are aliased.\n");
return;
}
/* no mailing while in jail. */
if (ltstflg(ploc(mynum), LFL_JAIL) && plev(mynum) < LVL_ARCHWIZARD)
{
bprintf ("Sorry... we jailed the postman also...\n");
return;
}
/* Save the mail header */
cur_player->mails.old_handler = cur_player->inp_handler->inp_handler;
/* Save the prompt */
strcpy(cur_player->mails.prompt, cur_player->prompt);
if (brkword () != -1) {
if (!getuaf (wordbuf, &p)) {
bprintf ("&+WPlayer not found in database.\n");
return;
}
strcpy (cur_player->mails.w_name, p.p_name);
bprintf ("&+CTo: &+w%s\n", cur_player->mails.w_name);
bprintf (SUBJECT);
cur_player->mails.old_handler = cur_player->inp_handler->inp_handler;
strcpy (cur_player->cprompt, SUBJECT);
replace_input_handler(write_mail);
return;
}
read_mail (pname(mynum));
return;
}
/* write_mail(): Gets the message subject and checks to see if to abort
* the message (if subject is a blank) and creates the header in the
* temporary mail file. Also, now you can abort the message here (before
* it just asked you for the subject again).
*/
void write_mail (char *u)
{
if (EMPTY (u) || u[0] == '\n') {
bprintf ("&+RMessage Aborted.\n");
replace_input_handler (cur_player->mails.old_handler);
strcpy(cur_player->prompt, cur_player->mails.prompt);
strcpy (cur_player->cprompt, build_prompt (mynum));
bprintf("\r%s", cur_player->cprompt);
setup_globals (mynum);
return;
} else {
bprintf (CC);
open_mail (WRITE, True);
if (cur_player->mails.write == (FILE *) 0)
return;
fprintf (cur_player->mails.write, "%s\n%s\n%d\n", pname (mynum), u, time (0));
fflush (cur_player->mails.write);
strcpy (cur_player->cprompt, CC);
replace_input_handler (cc_mail);
}
return;
}
void cc_mail( char *u ) {
if( EMPTY( u ) || u[0] == '\n' ) {
cur_player->mails.cc_list[ 0 ] = '\0';
} else {
strncpy( cur_player->mails.cc_list, u, MAX_COM_LEN );
}
bprintf(NOTICE1);
bprintf(MAILP);
strcpy( cur_player->cprompt, MAILP );
replace_input_handler( input_mail );
}
/* input_mail(): Handles the input of the message text */
void input_mail (char *x)
{
int p;
char to[256];
char from[256];
Boolean abort = False;
if (!strcasecmp(x,"=s") || !strcasecmp(x,"=a") ||
!strcasecmp(x,"**") || !strcasecmp(x,"*abort")) {
fprintf( cur_player->mails.write, "\n**CC:%s", cur_player->mails.cc_list );
fprintf (cur_player->mails.write, "\n%s\n", EOM_MARKER);
close_mail (WRITE);
strcpy(cur_player->prompt, cur_player->mails.prompt);
strcpy (cur_player->cprompt, (char *) build_prompt (mynum));
sprintf (to, "%s/%s", MAIL_DIR, cur_player->mails.w_name);
sprintf (from, "%s/%s.from-%s", MAIL_DIR, cur_player->mails.w_name, pname (mynum));
if (!strcasecmp(x,"=a") || !strcasecmp(x, "*abort")) {
bprintf("&+RMail aborted.\n");
abort = True;
} else {
PERSONA pe;
char *pCCName;
bprintf( "Sending mail to %s\n", cur_player->mails.w_name );
concat_files (to, from);
/** Send mail to the cc list as well... */
for( pCCName = strtok( cur_player->mails.cc_list, " ," );
pCCName != NULL; pCCName = strtok( NULL, " ," ) ) {
if (!getuaf (pCCName, &pe)) {
bprintf ("&+WPlayer %s not found in database.\n", pCCName );
} else {
sprintf( to, "%s/%s", MAIL_DIR, pe.p_name );
bprintf( "Sending mail to %s\n", pe.p_name );
concat_files( to, from );
if( ( p = fpbns( pe.p_name ) ) >= 0 && ( p < max_players ) ) {
sendf (p,
"&+C* &+WYou have new mail from %s &+C*\n", pname (mynum));
}
}
}
}
unlink (from);
if ((p = fpbns (cur_player->mails.w_name)) >= 0) {
if (p < max_players && !abort)
sendf (p, "&+C* &+WYou have new mail from %s &+C*\n", pname (mynum));
}
if (cur_player->mails.level)
replace_input_handler (mail_menu);
else {
replace_input_handler (cur_player->mails.old_handler);
strcpy(cur_player->prompt, cur_player->mails.prompt);
strcpy (cur_player->cprompt, build_prompt (mynum));
bprintf("\r%s", cur_player->cprompt);
}
return;
}
fprintf (cur_player->mails.write, "%s\n", x);
bprintf (MAILP);
fflush (cur_player->mails.write);
return;
}
/* read_mail(): Lets the player read a message */
void read_mail (char *u)
{
strcpy (cur_player->mails.r_name, pname (mynum));
open_mail (READ, False);
if (!(cur_player->mails.read)) {
bprintf ("&+CSorry, you have no mail.\n");
return;
}
strcpy (cur_player->cprompt, PROMPT);
dump_headers (cur_player->mails.read);
bprintf (PROMPT);
replace_input_handler (mail_menu);
return;
}
/* dump_headers(): Displays the message headers for your message file.
* There was a bug with the original code that caused the MUD to crash when
* the bflush() was called and if the EOF of the mail file had been reached.
* This was fixed by Illusion for iDIRT.
*/
void dump_headers (FILE * m)
{
char b[MAX_COM_LEN];
char from[MAX_COM_LEN];
time_t time_sent;
char s[MAX_COM_LEN];
count = 1;
rewind (cur_player->mails.read);
bprintf ("&+C No. From Date Sent Subject\n");
bprintf ("&+b----- ------------ ------------------------ ---------------------------\n");
while (!feof (cur_player->mails.read)) {
fgets (b, MAX_COM_LEN, cur_player->mails.read); /* From Field */
if (feof(cur_player->mails.read))
return;
b[strlen (b) - 1] = '\0';
strncpy (from, b, 12);
fgets (s, MAX_COM_LEN, cur_player->mails.read); /* Subject Field */
fgets (b, 40, cur_player->mails.read); /* Date Send Field */
time_sent = atol (b);
for (;;) { /* Find the EOF text */
if (feof (cur_player->mails.read))
break;
fgets (b, MAX_COM_LEN, cur_player->mails.read);
if (strncmp (b, EOM_MARKER, 3) == 0)
break;
}
if (strlen (s) > 27) { /* Dump Header */
s[24] = ' ';
s[25] = s[26] = s[27] = '.';
s[28] = '\n';
s[29] = '\0';
}
bprintf("&+w[%3.3d] %-12.12s %24.24s %s",
count, from, ctime (&time_sent),
(cur_player->mails.data)[count - 1] ? "&+R[ To Be Deleted ]\n" : s);
count++;
if (!feof(cur_player->mails.read)) /* [iDIRT] Bug fix. The MUD used */
bflush(); /* to crash here until the mail */
} /* file was checked for EOF. */
return;
}
/* mail_menu(): The menuing system for the mailer */
void mail_menu (char *u)
{
time_t tim;
char buffer[MAX_COM_LEN + 5];
int quitting_mail = False;
int do_cc = False;
cur_player->inmailer = True;
if (cur_player->mails.level) {
bprintf (PROMPT);
cur_player->mails.level = 0;
}
strcpy (cur_player->cprompt, PROMPT);
if (isupper (u[0]))
u[0] = tolower (u[0]);
switch (u[0]) {
case 'R': do_cc = True;
case 'r': /* Reply */
tim = time (0);
if (EMPTY (cur_player->mails.last))
bprintf ("&+WYou haven't read any messages yet.\n");
else {
if (strncasecmp (cur_player->mails.subject, "Re: ", 4) != 0)
sprintf (buffer, "&+CRe: &+w%s", cur_player->mails.subject);
else
strcpy (buffer, cur_player->mails.subject);
strcpy (cur_player->mails.w_name, cur_player->mails.last);
open_mail (WRITE, True);
if (!cur_player->mails.write) {
close_mail (READ);
return;
}
bprintf ("&+CTo: &+w%s\n&+CSubject: &+w%s\n",
cur_player->mails.last, buffer);
bprintf (CC);
fprintf (cur_player->mails.write, "%s\n%s%d\n",
pname (mynum), buffer, tim);
cur_player->mails.level = 1;
replace_input_handler (cc_mail);
return;
}
break;
case 'd': /* Delete */
if (u[1] != ' ' || !isdigit (u[2]))
bprintf ("\nUsage: d [message number]\n");
else
/*or statement added by jwn*/
if ((atoi(&u[2]) - 1) > MAX_LETTERS || atoi(&u[2]) > (count-1) )
{
bprintf("Message number cannot be greater than %d\n", (count-1));
mudlog("Bad &+rMAIL&* by %s, Number was %d",
pname(mynum), (atoi(&u[2]) - 1));
bprintf (PROMPT);
return;
}
if((atoi(&u[2]) - 1) < 0) {
bprintf("Message number must be at least 1.\n");
mudlog("Bad &+rMAIL&* by %s, Number was %d",
pname(mynum), (atoi(&u[2]) - 1));
bprintf(PROMPT);
return;
}
(cur_player->mails.data)[atoi (&u[2]) - 1] = True;
break;
case 'u': /* Undelete */
if (u[1] != ' ' || !isdigit (u[2]))
bprintf ("\nUsage: u [message number]\n");
else
if ((atoi(&u[2]) - 1) > MAX_LETTERS || atoi(&u[2]) > (count-1) )
{
bprintf("Message number cannot be greater than %d\n", MAX_LETTERS);
mudlog("Bad &+rMAIL&* by %s, Number was %d",
pname(mynum), (atoi(&u[2]) - 1));
bprintf (PROMPT);
return;
}
if((atoi(&u[2]) - 1) < 0) {
bprintf("Message number must be at least 1.\n");
mudlog("Bad &+rMAIL&* by %s, Number was %d",
pname(mynum), (atoi(&u[2]) - 1));
bprintf(PROMPT);
return;
}
(cur_player->mails.data)[atoi (&u[2]) - 1] = False;
break;
case '*': /* Game command */
gamecom (&u[1], False);
break;
case 'i': /* Info */
bprintf ("\001f%s\003\n", "mail.info");
break;
case '?':
bprintf ("\001f%s\003\n", "mail.help");
break;
case 'h':
bprintf ("\n");
dump_headers (cur_player->mails.read);
break;
case 'v':
bprintf("iDIRT Mailer Version 1.04/PE (Public Edition)\n");
bprintf("1994, 95 by Illusion\n\n");
bprintf("Derived from the DYRT mailer from DYRT 1.1c\n");
bprintf("by Valentin Popescu (vpopesc@calstatela.edu)\n");
bprintf("Multiple recepient & forwarding capabilities added 1998\n" );
bprintf(" by DuRandir\n" );
break;
case '\0':
case '\n':
break;
case 'x':
case 'q': /* quit the mailer */
replace_input_handler (cur_player->mails.old_handler);
close_mail (WRITE);
delete_mail ();
close_mail (READ);
strcpy(cur_player->prompt, cur_player->mails.prompt);
strcpy (cur_player->cprompt, build_prompt (mynum));
setup_globals (mynum);
cur_player->inmailer = False;
quitting_mail = True;
break;
default:
/* Check to see if a number was inputed */
{
int number;
number = atoi (u);
if (number < 1 || number > MAX_LETTERS) {
bprintf ("\nUnknown command. Type '?' for help.\n");
break;
}
read_msg (number);
}
break;
}
if (!quitting_mail) {
bprintf (PROMPT);
} else {
strcpy(cur_player->prompt, cur_player->mails.prompt);
bprintf ("%s", (char *) build_prompt (mynum));
}
return;
}
/* read_msg(): Reads in a message number, taking the number as a parameter */
void read_msg (int num)
{
/* to get to a particular message num, we must skip exactly num - 1 EOFs */
char buffer[MAX_COM_LEN];
time_t time_sent;
if (num <= 0 || num > MAX_LETTERS)
return;
rewind (cur_player->mails.read);
for (num = num - 1; num != 0; num--) {
for (;;) {
fgets (buffer, MAX_COM_LEN, cur_player->mails.read);
if (feof (cur_player->mails.read))
break;
if (strncmp (buffer, EOM_MARKER, 3) == 0)
break;
}
}
/* Now we should be at the right place, and we dump the letter */
fgets (buffer, MAX_COM_LEN, cur_player->mails.read);
if (feof (cur_player->mails.read)) {
bprintf ("\n&+WMessage out of range.\n", num);
return;
}
buffer[strlen (buffer) - 1] = '\0';
strncpy (cur_player->mails.last, buffer, 12);
if ((cur_player->mails.data)[num] == True)
bprintf ("\n&+R* Note: This letter is marked for deletion *\n");
/* dump the header */
bprintf ("\n&+CFrom: &+w%s\n", buffer);
fgets (buffer, MAX_COM_LEN, cur_player->mails.read);
strncpy (cur_player->mails.subject, buffer, MAX_COM_LEN);
bprintf ("&+CSubject: &+w%s", buffer);
fgets (buffer, MAX_COM_LEN, cur_player->mails.read);
time_sent = atoi (buffer);
bprintf ("&+CSent: &+w%s", ctime (&time_sent));
bprintf ("\n");
/* Dump the message body */
for (;;) {
fgets (buffer, MAX_COM_LEN, cur_player->mails.read);
if (strncmp (buffer, EOM_MARKER, 3) == 0)
break;
if( buffer[0] == '*' && buffer[1] == '*' ) {
bprintf( "%s", buffer + 2 );
/* strncpy (cur_player->mails.cc_list, buffer, MAX_COM_LEN); */
} else {
bprintf("%s", buffer);
}
if (feof (cur_player->mails.read))
break;
}
bflush ();
return;
}
/* open_mail(): Handles the opening of a mail file. */
void open_mail (char mode, Boolean complain)
{
char buf[1024];
FILE *tf;
Boolean fail = False;
switch (mode) {
case READ:
sprintf (buf, "%s/%s", MAIL_DIR, cur_player->mails.r_name);
tf = fopen (buf, "r");
if (!tf)
fail = True;
else
cur_player->mails.read = tf;
break;
case WRITE:
sprintf (buf, "%s/%s.from-%s", MAIL_DIR, cur_player->mails.w_name, pname (mynum));
tf = fopen (buf, "w");
if (!tf) {
progerror ("mail-write");
fail = True;
}
cur_player->mails.write = tf;
break;
}
if (complain && fail) {
bprintf ("We are experiencing technical difficulties. Try again later.\n");
progerror ("open_mail");
replace_input_handler (cur_player->mails.old_handler);
}
return;
}
/* close_mail(): Handles the closing of the mail files */
void close_mail (char mode)
{
switch (mode) {
case READ:
if (cur_player->mails.read)
fclose (cur_player->mails.read);
cur_player->mails.read = 0;
break;
case WRITE:
if (cur_player->mails.write)
fclose (cur_player->mails.write);
cur_player->mails.write = 0;
break;
}
}
/* delete_mail(): Deletes messages that have been marked for removal */
void delete_mail (void)
{
char tempfile[128];
char tempfile2[128];
char b[MAX_COM_LEN];
FILE *fout;
FILE *fin;
struct stat s;
register int t;
int pid;
extern int errno;
pid = getpid ();
sprintf (tempfile, "%s/TEMP.%s.%d", MAIL_DIR, pname (mynum), pid);
sprintf (tempfile2, "%s/%s", MAIL_DIR, pname (mynum));
(void) unlink (tempfile);
/* copy the file, filtering out the offending message */
fout = fopen (tempfile, "w");
if (!fout) {
bprintf ("System error detected, mail NOT deleted.\n");
progerror ("delete_file write");
return;
}
fin = fopen (tempfile2, "r");
if (!fin) {
bprintf ("System error detected, mail NOT deleted.\n");
progerror ("delete_file read");
fclose (fout);
unlink (tempfile);
errno = 0; /* unlink may cause an errorr */
return;
}
/* now we have both files open */
for (t = 0; t != MAX_LETTERS; t++) {
if (feof (fin) != 0)
break;
/* Should we delete messages? */
if ((cur_player->mails.data)[t] != 0) { /* Yes, delete messages */
while (1) { /* Search for 'EOF' */
bzero (b, MAX_COM_LEN);
fgets (b, MAX_COM_LEN, fin);
if (feof (fin) != 0)
break;
if (strncmp (b, EOM_MARKER, 3) == 0)
break;
}
} else { /* No, do not delete */
while (1) {
bzero (b, MAX_COM_LEN);
fgets (b, MAX_COM_LEN, fin);
fprintf (fout, "%s", b);
if (feof (fin) != 0)
break;
if (strncmp (b, EOM_MARKER, 3) == 0)
break;
}
}
/* unset the marker */
(cur_player->mails.data)[t] = 0;
}
rewind (fin);
fclose (fin);
fclose (fout);
/* copy the file over */
unlink (tempfile2);
rename (tempfile, tempfile2);
if (stat (tempfile2, &s) != -1)
if (s.st_size < 4)
unlink (tempfile2);
return;
}
/* check_files(): Checks if any files have been updated since login. A
* problem with iDIRT (and any DIRT for that matter) has been fixed here and
* it uses all of the correct DIRT user variables now.
*/
void check_files (void)
{
PERSONA me;
struct stat s;
time_t last;
char buff[512];
getuaf(pname(mynum), &me);
last = me.p_last_on;
sprintf (buff, "%s/%s", MAIL_DIR, pname (mynum));
#if 0 /* If you do have bulletins, this code is helpful */
if (stat(BULLETIN_DIR"/"MORTALBULL, &s) != -1) {
if (s.st_mtime > last)
bprintf ("New bulletin as of %19.19s.\nType 'bulletin' to read it.\n", ctime (&s.st_mtime));
}
if ((stat (BULLETIN_DIR"/"WIZARDBULL, &s) != -1) && plev (mynum) >= LVL_WIZARD) {
if (s.st_mtime > last)
bprintf ("New Wizard bulletin as of %19.19s.\nType 'wbull' to read it.\n", ctime (&s.st_mtime));
}
if ((stat (BULLETIN_DIR"/"ARCHWIZBULL, &s) != -1) && plev (mynum) >= LVL_ARCHWIZARD) {
if (s.st_mtime > last)
bprintf ("New Arch-Wizard+ bulletin as of %19.19s.\nType 'abull' to read it.\n", ctime (&s.st_mtime));
}
if ((stat (BULLETIN_DIR"/"DEMIGODBULL, &s) != -1) && plev (mynum) >= LVL_DEMI) {
if (s.st_mtime > last)
bprintf ("New DemiGod+ bulletin as of %19.19s.\nType 'gbull' to read it.\n", ctime (&s.st_mtime));
}
#endif
if (stat (buff, &s) != -1) {
if (s.st_size > 3) {
if (s.st_mtime > last)
bprintf ("&+WYou have new mail.\n");
else
bprintf ("&+WYou have old mail in your mailbox.\n");
}
}
bprintf("\n");
}
/* concat_files(): Puts two files together (like the 'cat' shell command)
Boolean concat_files (char *to, char *from)
{
FILE *fin;
FILE *fout;
char x[1024];
fin = fopen (from, "r");
if (!fin) {
fprintf (stderr, "error on opening source file for concat_files.\n");
return (False);
}
fout = fopen (to, "a+");
if (!fout) {
fprintf (stderr, "error on opening destination file for concat_files.\n");
fclose (fin);
return (False);
}
while (!feof (fin)) {
fgets (x, 1024, fin);
fprintf (fout, "%s", x);
x[0] = '\0';
}
fclose (fin);
fclose (fout);
return (True);
}
*/