/*
* History.
* 30/07/01, Shiannar: Removed the postal frogs from service.
*/
/**
* Folder handler for the mailer.
* Actually it does a lot more :-)
* <p>
* Thanks to Wodan and Pinkfish for ideas and help. <br>
* By Turrican@Discworld, May 1995.
* @author Turrican
* @started May 1995
*/
#pragma strict_types
#include <board.h>
#include <comms.h>
#include <mail.h>
#include <network.h>
#include <mime.h>
#define MAIL_PATH "/save/mail/"
/* #define CONVERTER "/handlers/converter" */
#undef CONVERTER
int *mesg_numbers;
class mail_header *info;
int new_thingy;
nosave int link;
class mail_header *get_messages(string pname, string fname);
string load_message(string pname, string fname, int number);
void delete_it(string pname, string fname, int *deleted);
string check_local(string arg);
private int load_counter();
private void save_counter(int counter);
void dest_me();
#ifdef CONVERTER
void convert_mail(string pname);
#endif
private void convert_links(int number);
void delete_account(string player);
void create() {
seteuid("Mailer");
} /* create() */
private string folder_filename(string name) {
if(file_size(MAIL_PATH + name) != -1) {
unguarded((: rename, MAIL_PATH + name,
MAIL_PATH + name[0..0] + "/" + name :));
} else if(file_size(MAIL_PATH + name + ".o") != -1) {
unguarded((: rename, MAIL_PATH + name + ".o",
MAIL_PATH + name[0..0] + "/" + name + ".o" :));
}
return MAIL_PATH+name[0..0] + "/" + name;
}
private string message_filename(int num, string prefix) {
if(file_size(MAIL_PATH+"new_mesg/" + (num % 50)) == -1) {
unguarded((: mkdir, MAIL_PATH+"new_mesg/" + (num % 50) :));
}
if(file_size(MAIL_PATH+"new_mesg/"+(num % 50)+"/"+((num / 50) % 50)) == -1) {
unguarded((: mkdir, MAIL_PATH+"new_mesg/" + (num %50) + "/" +
((num / 50) % 50) :));
}
#ifdef 0
if(file_size(MAIL_PATH + "mesg/" + prefix + num) != -1) {
unguarded((: rename, MAIL_PATH + "mesg/" + prefix + num,
MAIL_PATH+"new_mesg/"+ (num % 50) + "/" + ((num / 50) % 50) +
"/" + prefix + num :));
}
#endif
return MAIL_PATH+"new_mesg/"+ (num % 50) + "/" + ((num / 50) % 50) +
"/" + prefix + num;
}
private int check() {
string tmp;
tmp = base_name(PO);
if( tmp != "/handlers/mailer" &&
tmp != "/handlers/folder_handler" &&
tmp != "/handlers/converter" &&
tmp != "/net/daemon/pop3") {
write("Folder handler: Illegal access!\n");
return 1;
}
return 0;
} /* check() */
private void convert_class( string pname, string fname ) {
class mail_header hdr;
int i;
i = sizeof(info);
while( i-- ) {
hdr = new( class mail_header );
hdr->number = ((mapping)info[i])["number"];
hdr->status = ((mapping)info[i])["status"];
hdr->subject = ((mapping)info[i])["subject"];
hdr->from = ((mapping)info[i])["from"];
info[i] = hdr;
}
unguarded( (: save_object, folder_filename(lower_case(pname)+fname) :) );
} /* convert_class() */
/**
* This method returns the messages for the player in the specified
* folder. The return is an array of messages of type 'class mail_header'.
* @param pname the players name
* @param fname the folder name
* @return a list of messages
*/
class mail_header *get_messages(string pname, string fname) {
if( check() )
return ({ });
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded( (: restore_object, folder_filename(lower_case(pname)+fname) :) );
if( sizeof(info) && mapp(info[0]) )
convert_class( pname, fname );
return info;
} /* get_messages() */
/**
* This method loads a message for the specified player in the specified
* folder.
* @param pname the players name
* @param fname the folder name
* @param number the message number to open
* @return the text of the message
*/
string load_message(string pname, string fname, int number) {
string message;
if( check() )
return " ";
if( file_size( message_filename( number, "l") ) == -1 )
convert_links(number);
message = unguarded( (: read_file, message_filename( number, "") :) );
if( !message )
printf("* Failed to load message.\n", number );
return message;
} /* load_message() */
/**
* This method will attempt to the delete the specified messages from
* the folder. The array deleted must be an array of integers where each
* number is a message number to be deleted.
* @param pname the players name
* @param fname the folder name
* @param deleted the array of message numbers to delete from the folder
*/
void delete_it( string pname, string fname, int *deleted ) {
string tmp;
int i, idx;
if( check() )
return;
if( !PLAYER_H->test_user(pname) )
return;
if( sizeof(deleted) > 1 ) {
deleted = uniq_array( deleted );
deleted = sort_array( deleted, 1 );
}
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded( (: restore_object, folder_filename(lower_case(pname)+fname) :) );
if( sizeof(info) && mapp(info[0]) )
convert_class(pname, fname);
i = sizeof(deleted);
while (i--) {
reset_eval_cost();
if ((idx = member_array(deleted[i], mesg_numbers)) > -1) {
if (file_size(message_filename(mesg_numbers[idx], "l")) == -1) {
convert_links(mesg_numbers[idx]);
}
if (!(tmp = unguarded((: read_file,
message_filename(mesg_numbers[idx], "l")
:)))) {
mesg_numbers = delete(mesg_numbers, idx, 1);
if (((class mail_header)info[idx])->status == "N")
new_thingy--;
info = delete(info, idx, 1);
continue;
}
sscanf(tmp, "%d", link);
if (link == 1) {
unguarded((: rm, message_filename(mesg_numbers[idx], "") :));
unguarded((: rm, message_filename(mesg_numbers[idx], "l") :));
} else {
link--;
unguarded((: rm, message_filename(mesg_numbers[idx], "l") :));
unguarded((: write_file,
message_filename(mesg_numbers[idx], "l"),""+link :));
}
mesg_numbers = delete(mesg_numbers, idx, 1);
if (((class mail_header)info[idx])->status == "N") {
new_thingy--;
}
info = delete(info, idx, 1);
}
}
unguarded((: save_object, folder_filename(lower_case(pname)+fname) :));
} /* delete_it() */
private void send_notification( string to,
string from ) {
object ob;
if( ob = find_player(to) ) {
event( ob, "inform", "You receive mail from "+CAP(from), "mail");
}
} /* send_notification() */
/**
* This method adds a message into all the correct folders and stuff.
* This is the method that is called when a piece of mail is actually
* sent.
* @param mesg the message to send
* @param flag if this is set to a non-zero value then no delivery messages will
* occur
* @see delete_it()
*/
void add_it( class mail_message mesg, int flag ) {
object bing;
int i, counter, bong, ptpost;
string *local_to, *remote_to, *fail_to, str;
class mail_header hdr;
if( check() )
return;
if( sizeof(mesg->cc) )
mesg->to += mesg->cc;
mesg->to = MIME->get_email_addrs(implode(mesg->to, ","))[0];
local_to = ({ });
remote_to = ({ });
fail_to = ({ });
str = "";
bong = sizeof(mesg->to);
for (i = 0; i < bong; i++) {
if ((str = check_local(mesg->to[i]))) {
if (MAIL_TRACK->query_list(str)) {
local_to += MAIL_TRACK->query_members(str) - ({ mesg->from });
if ( str == "playtesters" ) {
ptpost = 1;
}
} else {
local_to += ({ str });
}
} else {
remote_to += ({ mesg->to[i] });
}
}
local_to = uniq_array(local_to);
remote_to = uniq_array(remote_to);
if ((i = sizeof(local_to))) {
if (!(counter = load_counter())) {
if (!flag) {
printf("Aborting send of message...\n");
}
return;
}
if (file_size(message_filename(counter, "")) != -1 ||
file_size(message_filename(counter, "l")) != -1) {
if (!flag) {
printf("Fatal error when sending message: File %d exists.\n"
"Please contact Turrican.\n", counter);
}
return;
}
for (i = 0; i < sizeof(local_to); i++) {
if (!flag) {
printf("Sending to: %s.\n", capitalize(local_to[i]));
}
if ((bing = (object)MAIL_TRACK->find_mailer(local_to[i]))) {
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
#ifdef CONVERTER
if (CONVERTER->query_busy(local_to[i])) {
write("Sorry, busy converting mail for "+local_to[i]+".\n");
mesg->to -= ({ local_to[i] });
mesg->cc -= ({ local_to[i] });
local_to = delete(local_to, i--, 1);
continue;
}
#endif
unguarded((: restore_object, folder_filename(local_to[i]+"inbox") :));
if (sizeof(info) && mapp(info[0]))
convert_class(local_to[i], "inbox");
mesg_numbers += ({ counter });
new_thingy++;
hdr = new(class mail_header);
hdr->number = counter;
hdr->status = "N";
hdr->subject = mesg->subject;
hdr->from = mesg->from;
info += ({ hdr });
if (!unguarded((: save_object, folder_filename(local_to[i]+"inbox") :))) {
if (!flag) {
printf("Couldn't save mailbox for %s...\n",
capitalize(local_to[i]));
}
mesg->to -= ({ local_to[i] });
mesg->cc -= ({ local_to[i] });
local_to = delete(local_to, i--, 1);
continue;
}
else if (this_player() && environment(this_player())) {
send_notification(local_to[i], mesg->from);
}
}
else if( PLAYER_H->test_user( local_to[i] ) ) {
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
#ifdef CONVERTER
if (!flag) {
if (file_size(folder_filename(local_to[i]+"inbox.o")) == -1 &&
file_size("/save/post/"+local_to[i]+".o") != -1) {
convert_mail(local_to[i]);
write("You cannot send mail to "+local_to[i]+" now.\n"+
"Try again later.\n");
mesg->to -= ({ local_to[i] });
mesg->cc -= ({ local_to[i] });
local_to = delete(local_to, i--, 1);
continue;
} else if (CONVERTER->query_busy(local_to[i])) {
write("Sorry, busy converting mail for "+local_to[i]+".\n");
mesg->to -= ({ local_to[i] });
mesg->cc -= ({ local_to[i] });
local_to = delete(local_to, i--, 1);
continue;
}
}
#endif
unguarded((: restore_object, folder_filename(local_to[i]+"inbox") :));
if (sizeof(info) && mapp(info[0]))
convert_class(local_to[i], "inbox");
mesg_numbers += ({ counter });
new_thingy++;
hdr = new(class mail_header);
hdr->number = counter;
hdr->status = "N";
hdr->subject = mesg->subject;
hdr->from = mesg->from;
info += ({ hdr });
if (!unguarded((: save_object, folder_filename(local_to[i]+"inbox") :))) {
if (!flag) {
printf("Couldn't save mailbox for %s...\n",
capitalize(local_to[i]));
}
mesg->to -= ({ local_to[i] });
mesg->cc -= ({ local_to[i] });
local_to = delete(local_to, i--, 1);
continue;
}
send_notification(local_to[i], mesg->from);
} else {
fail_to += ({ local_to[i] });
mesg->to -= ({ local_to[i] });
mesg->cc -= ({ local_to[i] });
local_to = delete(local_to, i--, 1);
}
}
if (i) {
++counter;
save_counter(counter);
if (!unguarded((: write_file, message_filename(counter-1, ""),
mesg->body :)) && !flag)
printf("*Failed to write message. Maybe the filesystem is full?\n");
if (!unguarded((: write_file, message_filename(counter-1, "l"), ""+i :)) &&
!flag)
printf("*Failed to write the message linkcount. Maybe the filesystem "
"is full?\n");
}
}
if ((i = sizeof(fail_to))) {
/* ohhhh...the recipient doesn't exist...make it bounce. */
while (i--)
MAILER->do_mail_message(mesg->from, "postmaster", "Error! User "+
fail_to[i]+" unknown", 0,
"Original message included:\n\n> "+
replace_string(mesg->body, "\n", "\n> "), 1, 0);
}
if (sizeof(remote_to) && !flag) {
for (i = 0; i < sizeof(remote_to); i++) {
sscanf(remote_to[i], "%*s@%s", str);
if (str && INTERMUD_H -> mud_exists(str)) {
printf("Sorry, Intermud mail is not currently supported. "
"Didn't send mail to %s.\n", remote_to[i]);
remote_to = delete(remote_to, i--, 1);
continue;
}
}
SMTP->eventSendMail(remote_to, mesg->from, mesg->body);
}
if (ptpost) {
BOARD_HAND->add_message("playtesters", capitalize(mesg->from),
"mailing to playtesters", mesg->body);
}
} /* add_it() */
/**
* This method marks the messages which are not in the unread array
* in the specified folder as being read.
* @param pname the player name
* @param fname the folder name
* @param unread the list of messages that are still unread
*/
void mark_read(string pname, string fname, int *unread) {
int i;
if( check() )
return;
if( !PLAYER_H->test_user(pname) )
return;
unread = uniq_array(unread);
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded((: restore_object, folder_filename(lower_case(pname)+fname) :));
if (sizeof(info) && mapp(info[0])) {
convert_class(pname, fname);
}
if (new_thingy == sizeof(unread)) {
return;
}
new_thingy = 0;
i = sizeof(mesg_numbers);
while (i--) {
if (member_array(i, unread) > -1) {
((class mail_header)info[i])->status = "N";
new_thingy++;
}
else if(i < sizeof(info))
((class mail_header)info[i])->status = " ";
else
write("\nErk, problems. The arrays don't quite match.\n");
reset_eval_cost();
}
unguarded((: save_object, folder_filename(lower_case(pname)+fname) :));
new_thingy = 0;
} /* mark_read() */
/**
* This method moves the specified message from one folder to another.
* @param pname the player name
* @param from the folder to move from
* @param to the folder to move to
* @param number the message number to move
*/
int move_it(string pname, string from, string to, int number) {
int link;
string tmp;
class mail_header tmphdr;
if (check())
return 1;
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded((: restore_object, folder_filename(lower_case(pname)+to) :));
if (sizeof(info) && mapp(info[0]))
convert_class(pname, to);
if (member_array(number, mesg_numbers) > -1)
return 1;
if (file_size(message_filename(number, "l")) == -1)
convert_links(number);
tmp = unguarded((: read_file, message_filename(number, "l") :));
if (!tmp) {
printf("Error moving message: File not found.\n");
return 1;
}
sscanf(tmp, "%d", link);
link++;
unguarded((: rm, message_filename(number, "l") :));
unguarded((: write_file, message_filename(number, "l"), ""+link :));
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded((: restore_object, folder_filename(lower_case(pname)+from) :));
if (sizeof(info) && mapp(info[0]))
convert_class(pname, from);
tmphdr = info[member_array(number, mesg_numbers)];
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded((: restore_object, folder_filename(lower_case(pname)+to) :));
mesg_numbers += ({ number });
info += ({ tmphdr });
if (tmphdr->status == "N")
new_thingy++;
unguarded((: save_object, folder_filename(lower_case(pname)+to) :));
return 0;
} /* move_it() */
/**
* This method checks to see if the specified players folder is empty.
* @param pname the player name to check
* @param fname the folder name to check
*/
int check_empty(string pname, string fname) {
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded((: restore_object, folder_filename(lower_case(pname)+fname) :));
if (!sizeof(mesg_numbers)) {
unguarded((: rm, folder_filename(lower_case(pname)+fname)+".o" :));
return 1;
}
return 0;
} /* check_empty() */
/**
* This method checks to see if the address is local.
* @param str the address to check
* @return local name for local messages, 0 for remote messages
*/
string check_local(string str) {
string name;
if (sscanf(lower_case(str),
"%*([\n \t]*([ \t!-;=?-~]+<)?)%([A-Za-z]+)"
"%*((.discworld(@|$))|$)",
name) == 3) {
return name;
}
return 0;
} /* check_local() */
/**
* This method returns the nice string used when fingering a player to
* determine their mail status.
* @param pname the player name to finger
* @return the string associated with the inbox of the player
*/
string finger_mail(string pname) {
string ret;
int num;
#ifdef CONVERTER
if (file_size(folder_filename(pname+"inbox.o")) == -1)
convert_mail(pname);
#endif
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded((: restore_object, folder_filename(pname+"inbox") :));
if( sizeof(info) && mapp(info[0]) )
convert_class(pname, "inbox");
num = sizeof( mesg_numbers );
if( !mesg_numbers || !num )
return "No mail.\n";
if( num == 1 )
ret = "1 mail message";
else
ret = num + " mail messages";
if( new_thingy <= 0 )
ret += ".\n";
else
ret += ", "+new_thingy+" of them unread.\n";
return ret;
} /* finger_mail() */
/**
* This method is called when the player logs on to determine if they have
* any new mail or not.
* @param pname the players name to check
*/
string check_mail( string pname ) {
#ifdef CONVERTER
if( file_size( folder_filename(pname+"inbox.o") ) == -1 )
convert_mail(pname);
#endif
mesg_numbers = ({ });
info = ({ });
new_thingy = 0;
unguarded( (: restore_object, folder_filename(pname+"inbox") :) );
if( sizeof(info) && mapp(info[0]) )
convert_class( pname, "inbox");
if( new_thingy > 0 )
return "\nYou have %^YELLOW%^NEW%^RESET%^ mail. Please use "
"the 'mail' command to read it.\n\n";
return "";
} /* check_mail() */
private void save_counter(int counter) {
if( check() )
return;
unguarded( (: rm, MAIL_PATH+"counter.save" :) );
unguarded( (: rename, MAIL_PATH+"counter", MAIL_PATH+"counter.save" :) );
unguarded( (: write_file, MAIL_PATH+"counter", ""+counter :) );
} /* save_counter() */
private int load_counter() {
string bing;
int ret;
if( check() )
return 0;
if( bing = unguarded( (: read_file, MAIL_PATH+"counter" :) ) ) {
sscanf( bing, "%d", ret );
} else {
printf("The mail counter was lost. Please contact a creator.\n");
return 0;
}
return ret;
} /* load_counter() */
/**
* This method sets the current message counter. This will return a
* message if the counter was not valid. This should be called in the
* event that a recovery is needed if the counter screws up.
* @param x the new value for the counter
*/
void set_counter(int x) {
if( file_size( message_filename( x, "") ) != -1 ||
file_size( message_filename( x, "l") ) != -1 ) {
printf("Invalid counter value: File exists.\n");
return;
}
save_counter(x);
} /* set_counter() */
/** @ignore yes */
void dest_me() { destruct(TO); }
#ifdef CONVERTER
/** @ignore yes */
void convert_mail(string pname) {
CONVERTER->convert_it(lower_case(pname));
} /* convert_mail() */
#endif
/**
* This method converts the links. Actually I have absolutely no idea
* what this one does at all.
* <p>
* So I think I should put a poem here instead...
* <p>
* <b>Thinking of you</b> (by Pinkfish)
* <p>
* Thinking of you always<br>
* Puts me a good mood<br>
* I dream of your voice<br>
* Your words and face
* <p>
* I long to be with you<br>
* At last to touch<br>
* To feel your warmth<br>
* And stare into your eyes
*/
private void convert_links(int number) {
string message;
int tmplink;
if( ! ( message = unguarded( (: read_file,
message_filename( number, ""), 1, 1 :) ) ) )
return;
sscanf( message, "%d", tmplink );
message = unguarded( (: read_file, message_filename( number, ""), 2 :) );
unguarded( (: rm, message_filename( number, "") :) );
unguarded( (: write_file, message_filename( number, ""), message :) );
unguarded( (: write_file, message_filename( number, "l"), ""+tmplink :) );
} /* convert_links() */
/**
* This method totaly removes the account for a particular player. This
* will be called when a player is deleted for whatever reason.
* @param pname the player name to delete
*/
void delete_account(string pname) {
string tmp, folder, *folders;
int message, lcount;
object mailer;
if( base_name(PO) != BULK_DELETE_H )
return;
mailer = clone_object(MAILER);
folders = (string *)mailer->query_folders(pname);
destruct(mailer);
if( !folders && file_size(folder_filename(pname+"inbox.o")) == -1 )
return;
if( !folders )
folders = ({"inbox"});
foreach( folder in folders ) {
mesg_numbers = ({ });
unguarded( (: restore_object, folder_filename(pname+folder) :) );
if( pointerp( mesg_numbers ) ) {
foreach( message in mesg_numbers ) {
reset_eval_cost();
if( file_size( message_filename( message, "l") ) == -1 )
convert_links(message);
tmp = unguarded( (: read_file, message_filename( message, "l") :) );
if( !tmp )
continue;
sscanf( tmp, "%d", lcount );
if( lcount != 1 ) {
lcount--;
unguarded( (: rm, message_filename( message, "l") :) );
unguarded( (: write_file, message_filename( message, "l"), ""+ lcount :) );
} else {
unguarded( (: rm, message_filename( message, "") :) );
unguarded( (: rm, message_filename( message, "") :) );
}
}
}
unguarded( (: rm, folder_filename( pname + folder + ".o") :) );
}
unguarded( (: rm, folder_filename( pname +".o") :) );
} /* delete_account() */