/**
* \file extmail.c
*
* \brief The PennMUSH built-in mail system.
*
* \verbatim
*---------------------------------------------------------------
* 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, aliases, or msg#'s
* to reply to. Players can be names or dbrefs. Aliases start with *
* 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.
*--------------------------------------------------------------------
* \endverbatim
*/
#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
#include <string.h>
#include "conf.h"
#include "externs.h"
#include "mushdb.h"
#include "dbdefs.h"
#include "match.h"
#include "extmail.h"
#include "function.h"
#include "malias.h"
#include "attrib.h"
#include "parse.h"
#include "mymalloc.h"
#include "pueblo.h"
#include "flags.h"
#include "log.h"
#include "lock.h"
#include "command.h"
#include "dbio.h"
#include "confmagic.h"
extern int do_convtime(const char *str, struct tm *ttm); /* funtime.c */
static void do_mail_flags
(dbref player, const char *msglist, mail_flag flag, int negate);
static char *mail_list_time(const char *the_time, int flag);
static MAIL *mail_fetch(dbref player, int num);
static MAIL *real_mail_fetch(dbref player, int num, int folder);
static 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);
static void send_mail(dbref player,
dbref target, char *subject, char *message,
mail_flag flags, int silent, int nosig);
static int send_mail_alias(dbref player,
char *aname, char *subject,
char *message, mail_flag flags, int silent,
int nosig);
static MAIL *find_insertion_point(dbref player);
static int get_folder_number(dbref player, char *name);
static char *get_folder_name(dbref player, int fld);
static int player_folder(dbref player);
static int parse_folder(dbref player, char *folder_string);
static int mail_match(dbref player, 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(MAIL *mp);
static char *status_string(MAIL *mp);
static int sign(int x);
static char *get_message(MAIL *mp);
static char *get_compressed_message(MAIL *mp);
static char *get_subject(MAIL *mp);
static char *get_sender(MAIL *mp, int full);
static int was_sender(dbref player, MAIL *mp);
MAIL *maildb; /**< The head of the mail list */
MAIL *tail_ptr; /**< The end of the mail list */
#define HEAD maildb /**< The head of the mail list */
#define TAIL tail_ptr /**< The end of the mail list */
/** A line of...dashes! */
#define DASH_LINE \
"-----------------------------------------------------------------------------"
int mdb_top = 0; /**< total number of messages in mail db */
/*-------------------------------------------------------------------------*
* 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
*-------------------------------------------------------------------------*/
/* Return the uncompressed text of a @mail in a static buffer */
static char *
get_message(MAIL *mp)
{
static char text[BUFFER_LEN * 2];
char tbuf[BUFFER_LEN * 2];
if (!mp)
return NULL;
chunk_fetch(mp->msgid, tbuf, sizeof tbuf);
strcpy(text, uncompress(tbuf));
return text;
}
/* Return the compressed text of a @mail in a static buffer */
static char *
get_compressed_message(MAIL *mp)
{
static char text[BUFFER_LEN * 2];
if (!mp)
return NULL;
chunk_fetch(mp->msgid, text, sizeof text);
return text;
}
/* Return the subject of a mail message, or (no subject) */
static char *
get_subject(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((unsigned char) *p)) {
*p = ' ';
}
}
} else
strcpy(sbuf, T("(no subject)"));
return sbuf;
}
/* Return the name of the mail sender. */
static char *
get_sender(MAIL *mp, int full)
{
static char tbuf1[BUFFER_LEN], *bp;
bp = tbuf1;
if (!GoodObject(mp->from))
safe_str("!Purged!", tbuf1, &bp);
else if (!was_sender(mp->from, mp))
safe_str("!Purged!", tbuf1, &bp);
else if (IsPlayer(mp->from) || !full)
safe_str(Name(mp->from), tbuf1, &bp);
else
safe_format(tbuf1, &bp, "%s (owner: %s)", Name(mp->from),
Name(Owner(mp->from)));
*bp = '\0';
return tbuf1;
}
/* Was this player the sender of this message? */
static int
was_sender(dbref player, MAIL *mp)
{
/* If the dbrefs don't match, fail. */
if (mp->from != player)
return 0;
/* If we don't know the creation time of the sender, succeed. */
if (!mp->from_ctime)
return 1;
/* Succeed if and only if the creation times match. */
return (mp->from_ctime == CreTime(player));
}
/** Change folders or rename a folder.
* \verbatim
* This implements @mail/folder
* \endverbatim
* \param player the enactor.
* \param fld string containing folder number or name.
* \param newname string containing folder name, if renaming.
*/
void
do_mail_change_folder(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_format(player,
T("MAIL: Current folder is %d [%s]."), pfld,
get_folder_name(player, pfld));
return;
}
pfld = parse_folder(player, fld);
if (pfld < 0) {
notify(player, T("MAIL: What folder is that?"));
return;
}
if (newname && *newname) {
/* We're changing a folder name here */
if (strlen(newname) > FOLDER_NAME_LEN) {
notify(player, T("MAIL: Folder name too long"));
return;
}
for (p = newname; p && *p; p++) {
if (!isdigit((unsigned char) *p) && !isalpha((unsigned char) *p)) {
notify(player, T("MAIL: Illegal folder name"));
return;
}
}
add_folder_name(player, pfld, newname);
notify_format(player, T("MAIL: Folder %d now named '%s'"), pfld, newname);
} else {
/* Set a new folder */
set_player_folder(player, pfld);
notify_format(player,
T("MAIL: Current folder set to %d [%s]."), pfld,
get_folder_name(player, pfld));
}
}
/** Remove a folder name.
* \verbatim
* This implements @mail/unfolder
* \endverbatim
* \param player the enactor.
* \param fld string containing folder number or name.
*/
void
do_mail_unfolder(dbref player, char *fld)
{
int pfld;
if (!fld || !*fld) {
notify(player, T("MAIL: You must specify a folder name or number"));
return;
}
pfld = parse_folder(player, fld);
if (pfld < 0) {
notify(player, T("MAIL: What folder is that?"));
return;
}
add_folder_name(player, pfld, NULL);
notify_format(player, T("MAIL: Folder %d now has no name"), pfld);
}
/** Tag a set of mail messages.
* \param player the enactor.
* \param msglist string specifying messages to tag.
*/
void
do_mail_tag(dbref player, const char *msglist)
{
do_mail_flags(player, msglist, M_TAG, 0);
}
/** Clear a set of mail messages.
* \param player the enactor.
* \param msglist string specifying messages to clear.
*/
void
do_mail_clear(dbref player, const char *msglist)
{
do_mail_flags(player, msglist, M_CLEARED, 0);
}
/** Untag a set of mail messages.
* \param player the enactor.
* \param msglist string specifying messages to untag.
*/
void
do_mail_untag(dbref player, const char *msglist)
{
do_mail_flags(player, msglist, M_TAG, 1);
}
/** Unclear a set of mail messages.
* \param player the enactor.
* \param msglist string specifying messages to unclear.
*/
void
do_mail_unclear(dbref player, const char *msglist)
{
do_mail_flags(player, msglist, M_CLEARED, 1);
}
/** Set or clear a flag on a set of messages.
* \param player the enactor.
* \param msglist string representing list of messages to operate on.
* \param flag flag to set or clear.
* \param negate if 1, clear the flag; if 0, set the flag.
*/
static void
do_mail_flags(dbref player, const char *msglist, mail_flag flag, int negate)
{
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 = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
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_format(player,
T("MAIL: All messages in all folders %s."),
negate ? "untagged" : "tagged");
notified++;
}
} else
notify_format(player,
"MAIL: Msg #%d:%d %s.", Folder(mp),
i[Folder(mp)], negate ? "untagged" : "tagged");
break;
case M_CLEARED:
if (All(ms)) {
if (!notified) {
notify_format(player,
T("MAIL: All messages in all folders %s."),
negate ? "uncleared" : "cleared");
notified++;
}
} else {
if (Unread(mp) && !negate) {
notify_format(player,
T
("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_format(player,
(negate ? T("MAIL: Msg #%d:%d uncleared.") :
T("MAIL: Msg #%d:%d cleared.")), Folder(mp),
i[Folder(mp)]);
}
}
break;
}
}
}
}
if (!j) {
/* ran off the end of the list without finding anything */
notify(player, T("MAIL: You don't have any matching messages!"));
}
return;
}
/** File messages into a folder.
* \verbatim
* This implements @mail/file.
* \endverbatim
* \param player the enactor.
* \param msglist list of messages to file.
* \param folder name or number of folder to put messages in.
*/
void
do_mail_file(dbref player, char *msglist, char *folder)
{
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, T("MAIL: Invalid folder specification"));
return;
}
FA_Init(i, j);
j = 0;
origfold = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
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 &= ~M_CLEARED; /* Unclear it if it was marked cleared */
mp->read |= FolderBit(foldernum);
if (All(ms)) {
if (!notified) {
notify_format(player,
T("MAIL: All messages filed in folder %d [%s]"),
foldernum, get_folder_name(player, foldernum));
notified++;
}
} else
notify_format(player,
T("MAIL: Msg %d:%d filed in folder %d [%s]"),
origfold, i[origfold], foldernum,
get_folder_name(player, foldernum));
}
}
}
if (!j) {
/* ran off the end of the list without finding anything */
notify(player, T("MAIL: You don't have any matching messages!"));
}
return;
}
/** Read mail messages.
* This displays the contents of a set of mail messages.
* \param player the enactor.
* \param msglist list of messages to read.
*/
void
do_mail_read(dbref player, char *msglist)
{
MAIL *mp;
char tbuf1[BUFFER_LEN];
char folderheader[BUFFER_LEN];
struct mail_selector ms;
int j, folder;
folder_array i;
if (!parse_msglist(msglist, &ms, player)) {
return;
}
folder = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
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("%cSAMP%c", TAG_START, TAG_END));
sprintf(folderheader,
"%cA XCH_HINT=\"List messages in this folder\" XCH_CMD=\"@mail/list %d:1-\"%c%s%c/A%c",
TAG_START, Folder(mp), TAG_END, T("Folder:"), TAG_START,
TAG_END);
} else
strcpy(folderheader, T("Folder:"));
notify(player, DASH_LINE);
strcpy(tbuf1, get_sender(mp, 1));
notify_format(player,
T
("From: %-55s %s\nDate: %-25s %s %2d Message: %d\nStatus: %s"),
tbuf1, ((*tbuf1 != '!') && IsPlayer(mp->from)
&& Connected(mp->from)
&& (!hidden(mp->from)
|| Priv_Who(player))) ? " (Conn)" : " ",
show_time(mp->time, 0), folderheader, Folder(mp),
i[Folder(mp)], status_string(mp));
notify_format(player, T("Subject: %s"), get_subject(mp));
notify(player, DASH_LINE);
if (SUPPORT_PUEBLO)
notify_noenter(player, tprintf("%c/SAMP%c", TAG_START, TAG_END));
strcpy(tbuf1, get_message(mp));
notify(player, tbuf1);
if (SUPPORT_PUEBLO)
notify_format(player, "%cSAMP%c%s%c/SAMP%c", TAG_START, TAG_END,
DASH_LINE, TAG_START, TAG_END);
else
notify(player, DASH_LINE);
if (Unread(mp))
mp->read |= M_MSGREAD; /* mark message as read */
}
}
}
if (!j) {
/* ran off the end of the list without finding anything */
notify(player, T("MAIL: You don't have that many matching messages!"));
}
return;
}
/** List the flags, number, sender, subject, and date of messages in a
* concise format.
* \param player the enactor.
* \param msglist list of messages to list.
*/
void
do_mail_list(dbref player, const char *msglist)
{
char subj[30];
char sender[30];
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 = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
if (SUPPORT_PUEBLO)
notify_noenter(player, tprintf("%cSAMP%c", TAG_START, TAG_END));
notify_format(player,
T
("--------------------------- MAIL (folder %2d) ------------------------------"),
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));
strcpy(subj, chopstr(get_subject(mp), 28));
strcpy(sender, chopstr(get_sender(mp, 0), 12));
notify_format(player, "[%s] %2d:%-3d %c%-12s %-*s %s",
status_chars(mp), Folder(mp), i[Folder(mp)],
((*sender != '!') && (Connected(mp->from) &&
(!hidden(mp->from)
|| Priv_Who(player)))
? '*' : ' '), sender, 30, subj,
mail_list_time(show_time(mp->time, 0), 1));
if (SUPPORT_PUEBLO)
notify_noenter(player, tprintf("%c/A%c", TAG_START, TAG_END));
}
}
}
notify(player, DASH_LINE);
if (SUPPORT_PUEBLO)
notify_format(player, "%c/SAMP%c", TAG_START, TAG_END);
return;
}
static char *
mail_list_time(const char *the_time, int flag /* 1 for no year */ )
{
static char newtime[BUFFER_LEN];
const char *p;
char *q;
int i;
p = the_time;
q = newtime;
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 newtime;
}
/** Expunge mail that's marked for deletion.
* \verbatim
* This implements @mail/purge.
* \endverbatim
* \param player the enactor.
*/
void
do_mail_purge(dbref player)
{
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--;
free(mp->subject);
chunk_delete(mp->msgid);
mush_free((Malloc_t) mp, "mail");
} 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));
}
if (command_check_byname(player, "@MAIL"))
notify(player, T("MAIL: Mailbox purged."));
return;
}
/** Forward mail messages to someone(s) else.
* \verbatim
* This implements @mail/forward.
* \endverbatim
* \param player the enactor.
* \param msglist list of messages to forward.
* \param tolist list of recipients to forwared to.
*/
void
do_mail_fwd(dbref player, char *msglist, char *tolist)
{
MAIL *mp;
MAIL *last;
struct mail_selector ms;
int j, num, folder;
folder_array i;
const char *head;
MAIL *temp;
dbref target;
int num_recpts = 0;
const char **start;
char *current;
start = &head;
if (!parse_msglist(msglist, &ms, player)) {
return;
}
if (!tolist || !*tolist) {
notify(player, T("MAIL: To whom should I forward?"));
return;
}
folder = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
/* 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, T("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 = tolist;
while (head && *head) {
current = next_in_list(start);
/* Now locate a target */
num = atoi(current);
if (num) {
/* reply to a mail message */
temp = mail_fetch(player, num);
if (!temp) {
notify(player, T("MAIL: You can't reply to nonexistant mail."));
} else {
char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];
strcpy(tbuf1, uncompress(mp->subject));
strcpy(tbuf2, get_compressed_message(mp));
send_mail(player, temp->from, tbuf1, tbuf2, M_FORWARD | M_REPLY,
1, 0);
num_recpts++;
}
} else {
/* forwarding to a player */
target =
match_result(player, current, TYPE_PLAYER,
MAT_ME | MAT_ABSOLUTE | MAT_PLAYER);
if (!GoodObject(target))
target = lookup_player(current);
if (!GoodObject(target))
target = short_page(current);
if (!GoodObject(target) || !IsPlayer(target)) {
notify_format(player, T("No such unique player: %s."), current);
} else {
char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];
strcpy(tbuf1, uncompress(mp->subject));
strcpy(tbuf2, get_compressed_message(mp));
send_mail(player, target, tbuf1, tbuf2, M_FORWARD, 1, 0);
num_recpts++;
}
}
}
}
}
mp = mp->next;
}
notify_format(player, T("MAIL: %d messages forwarded."), num_recpts);
}
/** Send a mail message.
* \param player the enactor.
* \param tolist list of recipients.
* \param message message text.
* \param flags flags to apply to the message.
* \param silent if 1, don't notify sender for each message sent.
* \param nosig if 1, don't apply sender's MAILSIGNATURE.
*/
void
do_mail_send(dbref player, char *tolist, char *message, mail_flag flags,
int silent, int nosig)
{
const char *head;
int num;
dbref target;
int mail_flags;
char sbuf[SUBJECT_LEN + 1], *sb, *mb;
int i = 0, subject_given = 0;
const char **start;
char *current;
start = &head;
if (!tolist || !*tolist) {
notify(player, T("MAIL: I can't figure out who you want to mail to."));
return;
}
if (!message || !*message) {
notify(player, T("MAIL: I can't figure out what you want to send."));
return;
}
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 */
/* Parse the player list */
head = tolist;
while (head && *head) {
mail_flags = flags;
current = next_in_list(start);
/* Now locate a target */
if (is_strict_integer(current)) {
/* reply to a mail message */
MAIL *temp;
num = parse_integer(current);
temp = mail_fetch(player, num);
if (!temp) {
notify(player, T("MAIL: You can't reply to nonexistent mail."));
return;
}
if (subject_given)
send_mail(player, temp->from, sbuf, message, mail_flags, silent, nosig);
else
send_mail(player, temp->from, uncompress(temp->subject), message,
mail_flags | M_REPLY, silent, nosig);
} else {
/* send a new mail message */
target =
match_result(player, current, TYPE_PLAYER,
MAT_ME | MAT_ABSOLUTE | MAT_PLAYER);
if (!GoodObject(target))
target = lookup_player(current);
if (!GoodObject(target))
target = short_page(current);
if (!GoodObject(target) || !IsPlayer(target)) {
if (!send_mail_alias
(player, current, sbuf, message, mail_flags, silent, nosig))
notify_format(player, T("No such unique player: %s."), current);
} else
send_mail(player, target, sbuf, message, mail_flags, silent, nosig);
}
}
}
/*-------------------------------------------------------------------------*
* 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 MAIL *
mail_fetch(dbref player, int num)
{
/* get an arbitrary mail message in the current folder */
return real_mail_fetch(player, num, player_folder(player));
}
static MAIL *
real_mail_fetch(dbref player, int num, int folder)
{
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(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 */
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;
}
/* ARGSUSED */
FUNCTION(fun_mailsend)
{
/* mailsend(<target>,[<subject>/]<message>) */
if ((fun->flags & FN_NOSIDEFX) || Gagged(executor) ||
!command_check_byname(executor, "@MAIL"))
safe_str(T(e_perm), buff, bp);
else if (FUNCTION_SIDE_EFFECTS)
do_mail_send(executor, args[0], args[1], 0, 1, 0);
else
safe_str(T(e_disabled), buff, bp);
}
static void
send_mail(dbref player, dbref target, char *subject, char *message,
mail_flag flags, int silent, int nosig)
{
/* send a mail message */
MAIL *newp, *mp;
int rc, uc, cc;
char *newmsg, *nm, *buff, *bp;
char const *ms;
char *mailsig;
char sbuf[BUFFER_LEN];
ATTR *a;
char *cp;
if (!IsPlayer(target)) {
notify(player, T("MAIL: You cannot send mail to non-existent people."));
return;
}
if (!strcasecmp(message, "clear")) {
notify(player,
T("MAIL: You probably don't wanna send mail saying 'clear'."));
return;
}
if (!(Hasprivs(player) || eval_lock(player, target, Mail_Lock))) {
notify_format(player,
T("MAIL: %s is not accepting mail from you right now."),
Name(target));
return;
}
count_mail(target, 0, &rc, &uc, &cc);
if ((rc + uc + cc) >= MAIL_LIMIT) {
notify_format(player, T("MAIL: %s's mailbox is full. Can't send."),
Name(target));
return;
}
/* initialize the appropriate fields */
newp = (MAIL *) mush_malloc(sizeof(struct mail), "mail");
newp->to = target;
newp->from = player;
newp->from_ctime = CreTime(player);
/* Deal with the subject */
cp = remove_markup(subject, NULL);
if (subject && cp && *cp)
strcpy(sbuf, cp);
else
strcpy(sbuf, T("(no subject)"));
if ((flags & M_FORWARD) && !string_prefix(sbuf, "Fwd:"))
newp->subject = compress(chopstr(tprintf("Fwd: %s", sbuf), SUBJECT_LEN));
else if ((flags & M_REPLY) && !string_prefix(sbuf, "Re:"))
newp->subject = 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(AL_STR(a));
else
newp->subject = compress(sbuf);
if (flags & M_FORWARD) {
/* Forwarding passes the message already compressed */
u_int_16 len = strlen(message) + 1;
newp->msgid = chunk_create(message, len, 1);
} else {
u_int_16 len;
unsigned char *text;
newmsg = (char *) mush_malloc(BUFFER_LEN, "string");
if (!newmsg)
mush_panic(T("Failed to allocate string in send_mail"));
nm = newmsg;
safe_str(message, newmsg, &nm);
if (!nosig && ((a = atr_get_noparent(player, "MAILSIGNATURE")) != NULL)) {
/* Append the MAILSIGNATURE to the mail - Cordin@Dune's idea */
buff = (char *) mush_malloc(BUFFER_LEN, "string");
if (!buff)
mush_panic(T("Failed to allocate string in send_mail"));
ms = mailsig = safe_atr_value(a);
bp = buff;
process_expression(buff, &bp, &ms, player, player, player,
PE_DEFAULT, PT_DEFAULT, NULL);
*bp = '\0';
free(mailsig);
safe_str(buff, newmsg, &nm);
mush_free((Malloc_t) buff, "string");
}
*nm = '\0';
text = compress(newmsg);
len = strlen(text) + 1;
newp->msgid = chunk_create(text, len, 1);
free(text);
mush_free((Malloc_t) newmsg, "string");
}
newp->time = mudtime;
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_format(player,
T("MAIL: You sent your message to %s."), Name(target));
notify_format(target,
T("MAIL: You have a new message (%d) from %s."),
rc + uc + cc + 1, 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;
}
/** Wipe the entire maildb.
* \param player the enactor.
*/
void
do_mail_nuke(dbref player)
{
MAIL *mp, *nextp;
if (!God(player)) {
notify(player, T("The postal service issues a warrant for your arrest."));
return;
}
/* walk the list */
for (mp = HEAD; mp != NULL; mp = nextp) {
nextp = mp->next;
if (mp->subject)
free(mp->subject);
chunk_delete(mp->msgid);
mush_free((Malloc_t) mp, "mail");
}
HEAD = TAIL = NULL;
mdb_top = 0;
desc_mail_clear();
do_log(LT_ERR, 0, 0, T("** MAIL PURGE ** done by %s(#%d)."),
Name(player), player);
notify(player, T("You annihilate the post office. All messages cleared."));
}
/** Low-level mail sanity checking or debugging.
* "how to fix mail with a sledgehammer".
* \param player the enactor.
* \param action action to take (sanity, clear, fix)
* \param victim name of player whose mail is to be checked (NULL to check all)
*/
void
do_mail_debug(dbref player, const char *action, const char *victim)
{
dbref target;
MAIL *mp, *nextp;
int i;
if (!Wizard(player)) {
notify(player, T("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_format(player, T("%s: No such player."), victim);
return;
}
do_mail_clear(target, "ALL");
do_mail_purge(target);
notify_format(player, T("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_format(player, T("Bad object #%d has mail."), mp->to);
else if (!IsPlayer(mp->to))
notify_format(player, T("%s(#%d) has mail but is not a player."),
Name(mp->to), mp->to);
}
if (i != mdb_top) {
notify_format(player,
T
("Mail database top is %d, actual message count is %d. Fixing."),
mdb_top, i);
mdb_top = i;
}
notify(player, T("Mail sanity check completed."));
} else if (string_prefix("fix", action)) {
for (i = 0, mp = HEAD; mp != NULL; i++, mp = nextp) {
if (!GoodObject(mp->to) || !IsPlayer(mp->to)) {
notify_format(player, T("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--;
if (mp->subject)
free(mp->subject);
chunk_delete(mp->msgid);
mush_free((Malloc_t) mp, "mail");
} else if (!GoodObject(mp->from)) {
/* Oops, it's from a player whose dbref is out of range!
* We'll make it appear to be from #0 instead because there's
* no really good choice
*/
mp->from = 0;
nextp = mp->next;
} else {
nextp = mp->next;
}
}
notify(player, T("Mail sanity fix completed."));
} else {
notify(player, T("That is not a debugging option."));
return;
}
}
/** Display mail database statistics.
* \verbatim
* This implements @mail/stat, @mail/dstat, @mail/fstat.
* \endverbatim
* \param player the enactor.
* \param name name of player whose mail stats are to be checked (NULL for all)
* \param full level of verbosity: MSTATS_COUNT = just count messages,
* MSTATS_READ = break down by read/unread, MSTATS_SIZE = include
* space usage.
*/
void
do_mail_stats(dbref player, char *name, enum mail_stats_type full)
{
dbref target;
int fc, fr, fu, tc, tr, tu, fchars, tchars, cchars;
char last[50];
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) || !IsPlayer(target))
target = NOTHING;
} else if (!strcasecmp(name, "me")) {
target = player;
} else {
target = lookup_player(name);
}
/* Don't use GoodObject here! */
if (target == NOTHING) {
target = match_result(player, name, NOTYPE, MAT_ABSOLUTE);
}
if ((target == NOTHING) || ((target == AMBIGUOUS) && !Wizard(player))) {
notify_format(player, T("%s: No such player."), name);
return;
}
if (!Wizard(player) && (target != player)) {
notify(player, T("The post office protects privacy!"));
return;
}
/* this comand is computationally expensive */
if (!payfor(player, FIND_COST)) {
notify_format(player, T("Finding mail stats costs %d %s."), FIND_COST,
(FIND_COST == 1) ? MONEY : MONIES);
return;
}
if (target == AMBIGUOUS) { /* stats for all */
if (full == MSTATS_COUNT) {
notify_format(player,
T("There are %d messages in the mail spool."), mdb_top);
return;
} else if (full == MSTATS_READ) {
for (mp = HEAD; mp != NULL; mp = mp->next) {
if (Cleared(mp))
fc++;
else if (Read(mp))
fr++;
else
fu++;
}
notify_format(player,
T
("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(get_message(mp));
} else if (Read(mp)) {
fr++;
fchars += strlen(get_message(mp));
} else {
fu++;
tchars += strlen(get_message(mp));
}
}
notify_format(player,
T
("MAIL: There are %d old msgs in the mail spool, totalling %d characters."),
fr, fchars);
notify_format(player,
T
("MAIL: There are %d new msgs in the mail spool, totalling %d characters."),
fu, tchars);
notify_format(player,
("MAIL: There are %d cleared msgs in the mail spool, totalling %d characters."),
fc, cchars);
return;
}
}
/* individual stats */
if (full == MSTATS_COUNT) {
/* just count number of messages */
for (mp = HEAD; mp != NULL; mp = mp->next) {
if (was_sender(target, mp))
fr++;
if (mp->to == target)
tr++;
}
notify_format(player, T("%s sent %d messages."), Name(target), fr);
notify_format(player, T("%s has %d messages."), Name(target), tr);
return;
}
/* more detailed message count */
for (mp = HEAD; mp != NULL; mp = mp->next) {
if (was_sender(target, mp)) {
if (Cleared(mp))
fc++;
else if (Read(mp))
fr++;
else
fu++;
if (full == MSTATS_SIZE)
fchars += strlen(get_message(mp));
}
if (mp->to == target) {
if (!tr && !tu)
strcpy(last, show_time(mp->time, 0));
if (Cleared(mp))
tc++;
else if (Read(mp))
tr++;
else
tu++;
if (full == MSTATS_SIZE)
tchars += strlen(get_message(mp));
}
}
notify_format(player, T("Mail statistics for %s:"), Name(target));
if (full == MSTATS_READ) {
notify_format(player, T("%d messages sent, %d unread, %d cleared."),
fc + fr + fu, fu, fc);
notify_format(player, T("%d messages received, %d unread, %d cleared."),
tc + tr + tu, tu, tc);
} else {
notify_format(player,
T
("%d messages sent, %d unread, %d cleared, totalling %d characters."),
fc + fr + fu, fu, fc, fchars);
notify_format(player,
T
("%d messages received, %d unread, %d cleared, totalling %d characters."),
tc + tr + tu, tu, tc, tchars);
}
if (tc + tr + tu > 0)
notify_format(player, T("Last is dated %s"), last);
return;
}
/** Main mail wrapper.
* \verbatim
* This implements the @mail command when called with no switches.
* \endverbatim
* \param player the enactor.
* \param arg1 left-hand argument (before the =)
* \param arg2 right-hand argument (after the =)
*/
void
do_mail(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, T("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 */
if (Gagged(sender))
notify(sender, T("You cannot do that while gagged."));
else
do_mail_send(sender, arg1, arg2, 0, 0, 0);
} else {
/* Must be reading or listing mail - no arg2 */
if (isdigit((unsigned char) *arg1) && !strchr(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;
switch (nargs) {
case 0:
count_mail(executor, player_folder(executor), &rc, &uc, &cc);
break;
case 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_ME | MAT_ABSOLUTE | MAT_PLAYER)) == NOTHING) {
safe_str(T("#-1 NO SUCH PLAYER"), buff, bp);
return;
} else if (!controls(executor, player)) {
safe_str(T(e_perm), buff, bp);
return;
} else {
count_mail(player, player_folder(player), &rc, &uc, &cc);
}
} else {
count_mail(executor, parse_integer(args[0]), &rc, &uc, &cc);
}
break;
case 2:
if ((player =
noisy_match_result(executor, args[0], TYPE_PLAYER,
MAT_ME | MAT_ABSOLUTE | MAT_PLAYER)) == NOTHING) {
safe_str(T("#-1 NO SUCH PLAYER"), buff, bp);
return;
} else if (!controls(executor, player)) {
safe_str(T(e_perm), buff, bp);
return;
}
if (!is_integer(args[1])) {
safe_str(T("#-1 FOLDER MUST BE INTEGER"), buff, bp);
return;
}
count_mail(player, parse_integer(args[1]), &rc, &uc, &cc);
break;
default:
/* This should never happen */
return;
}
safe_integer(rc, buff, bp);
safe_chr(' ', buff, bp);
safe_integer(uc, buff, bp);
safe_chr(' ', buff, bp);
safe_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
*/
MAIL *mp;
dbref player;
int rc, uc, cc;
if (nargs == 0) {
count_mail(executor, -1, &rc, &uc, &cc);
safe_integer(rc + uc + cc, buff, bp);
return;
}
/* Try mail(<player>) */
if (nargs == 1) {
player =
match_result(executor, args[0], TYPE_PLAYER,
MAT_ME | MAT_ABSOLUTE | MAT_PLAYER);
if (GoodObject(player)) {
if (!controls(executor, player)) {
safe_str(T(e_perm), buff, bp);
} else {
count_mail(player, -1, &rc, &uc, &cc);
safe_integer(rc, buff, bp);
safe_chr(' ', buff, bp);
safe_integer(uc, buff, bp);
safe_chr(' ', buff, bp);
safe_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(get_message(mp), buff, bp);
return;
}
safe_str(T("#-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 MAIL *
mailfun_fetch(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_ME | MAT_ABSOLUTE | MAT_PLAYER)) == NOTHING) {
return NULL;
} else if (!controls(player, target)) {
notify(player, T("Permission denied"));
return NULL;
}
if (parse_message_spec(target, arg2, &msg, NULL, &folder))
return real_mail_fetch(target, msg, folder);
else {
notify(player, T("Invalid message specification"));
return NULL;
}
}
/* NOTREACHED */
return NULL;
}
/* ARGSUSED */
FUNCTION(fun_mailfrom)
{
MAIL *mp;
mp = mailfun_fetch(executor, nargs, args[0], args[1]);
if (!mp)
safe_str("#-1", buff, bp);
else if (!was_sender(mp->from, mp))
safe_str("#-1", buff, bp);
else
safe_dbref(mp->from, buff, bp);
return;
}
/* ARGSUSED */
FUNCTION(fun_mailstats)
{
/* Copied from extmail.c: do_mail_stats with changes to refer to
* args[0] rather than name, etc
*
* Todo: Change it so it is just mailstats and depending on what
* it is called as, it will change if it is doing a fstats, dstats,
* or stats. Looks like stats -> full=0, dstats -> full=1, fstats ->
* full=2
*/
/* mail database statistics */
dbref target;
int fc, fr, fu, tc, tr, tu, fchars, tchars, cchars;
char last[50];
MAIL *mp;
int full;
/* Figure out how we were called */
if (string_prefix(called_as, "mailstats")) {
full = 0;
} else if (string_prefix(called_as, "maildstats")) {
full = 1;
} else if (string_prefix(called_as, "mailfstats")) {
full = 2;
} else {
safe_str(T("#-? fun_mailstats called with invalid called_as!"), buff, bp);
return;
}
fc = fr = fu = tc = tr = tu = cchars = fchars = tchars = 0;
/* find player */
if (*args[0] == '\0') {
if Wizard
(executor)
target = AMBIGUOUS;
else
target = executor;
} else if (*args[0] == NUMBER_TOKEN) {
target = atoi(&args[0][1]);
if (!GoodObject(target) || !IsPlayer(target))
target = NOTHING;
} else if (!strcasecmp(args[0], "me")) {
target = executor;
} else {
target = lookup_player(args[0]);
}
if (!GoodObject(target)) {
target = match_result(executor, args[0], NOTYPE, MAT_ABSOLUTE);
}
if (!GoodObject(target) || !IsPlayer(target)) {
notify_format(executor, T("%s: No such player."), args[0]);
return;
}
if (!controls(executor, target)) {
notify(executor, T("The post office protects privacy!"));
return;
}
/* this comand is computationally expensive */
if (!payfor(executor, FIND_COST)) {
notify_format(executor, T("Finding mail stats costs %d %s."), FIND_COST,
(FIND_COST == 1) ? MONEY : MONIES);
return;
}
if (target == AMBIGUOUS) { /* stats for all */
if (full == 0) {
/* FORMAT
* total mail
*/
safe_integer(mdb_top, buff, bp);
return;
} else if (full == 1) {
for (mp = HEAD; mp != NULL; mp = mp->next) {
if (Cleared(mp))
fc++;
else if (Read(mp))
fr++;
else
fu++;
}
/* FORMAT
* sent, sent_unread, sent_cleared
*/
safe_format(buff, bp, "%d %d %d", fc + fr + fu, fu, fc);
} else {
for (mp = HEAD; mp != NULL; mp = mp->next) {
if (Cleared(mp)) {
fc++;
cchars += strlen(get_message(mp));
} else if (Read(mp)) {
fr++;
fchars += strlen(get_message(mp));
} else {
fu++;
tchars += strlen(get_message(mp));
}
}
/* FORMAT
* sent_read, sent_read_characters,
* sent_unread, sent_unread_characters,
* sent_clear, sent_clear_characters
*/
safe_format(buff, bp, "%d %d %d %d %d %d",
fr, fchars, fu, tchars, fc, cchars);
return;
}
}
/* individual stats */
if (full == 0) {
/* just count number of messages */
for (mp = HEAD; mp != NULL; mp = mp->next) {
if (was_sender(target, mp))
fr++;
if (mp->to == target)
tr++;
}
/* FORMAT
* sent, received
*/
safe_format(buff, bp, "%d %d", fr, tr);
return;
}
/* more detailed message count */
for (mp = HEAD; mp != NULL; mp = mp->next) {
if (was_sender(target, mp)) {
if (Cleared(mp))
fc++;
else if (Read(mp))
fr++;
else
fu++;
if (full == 2)
fchars += strlen(get_message(mp));
}
if (mp->to == target) {
if (!tr && !tu)
strcpy(last, show_time(mp->time, 0));
if (Cleared(mp))
tc++;
else if (Read(mp))
tr++;
else
tu++;
if (full == 2)
tchars += strlen(get_message(mp));
}
}
if (full == 1) {
/* FORMAT
* sent, sent_unread, sent_cleared
* received, rec_unread, rec_cleared
*/
safe_format(buff, bp, "%d %d %d %d %d %d",
fc + fr + fu, fu, fc, tc + tr + tu, tu, tc);
} else {
/* FORMAT
* sent, sent_unread, sent_cleared, sent_bytes
* received, rec_unread, rec_cleared, rec_bytes
*/
safe_format(buff, bp, "%d %d %d %d %d %d %d %d",
fc + fr + fu, fu, fc, fchars, tc + tr + tu, tu, tc, tchars);
}
}
/* ARGSUSED */
FUNCTION(fun_mailtime)
{
MAIL *mp;
mp = mailfun_fetch(executor, nargs, args[0], args[1]);
if (!mp)
safe_str("#-1", buff, bp);
else
safe_str(show_time(mp->time, 0), buff, bp);
}
/* ARGSUSED */
FUNCTION(fun_mailstatus)
{
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;
}
/* ARGSUSED */
FUNCTION(fun_mailsubject)
{
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;
}
/** Save mail to disk.
* \param fp pointer to filehandle to save mail to.
* \return number of mail messages saved.
*/
int
dump_mail(FILE * fp)
{
MAIL *mp;
int count = 0;
int mail_flags = 0;
mail_flags += MDBF_SUBJECT;
mail_flags += MDBF_ALIASES;
mail_flags += MDBF_NEW_EOD;
mail_flags += MDBF_SENDERCTIME;
if (mail_flags)
fprintf(fp, "+%d\n", mail_flags);
save_malias(fp);
OUTPUT(fprintf(fp, "%d\n", mdb_top));
for (mp = HEAD; mp != NULL; mp = mp->next) {
putref(fp, mp->to);
putref(fp, mp->from);
putref(fp, mp->from_ctime);
putstring(fp, show_time(mp->time, 0));
if (mp->subject)
putstring(fp, uncompress(mp->subject));
else
putstring(fp, "");
putstring(fp, get_message(mp));
putref(fp, mp->read);
count++;
}
OUTPUT(fputs(EOD, fp));
fflush(fp);
if (count != mdb_top) {
do_log(LT_ERR, 0, 0, T("MAIL: Count of messages is %d, mdb_top is %d."),
count, mdb_top);
mdb_top = count; /* Doesn't help if we forked, but oh well */
}
return (count);
}
/** Find the first message in a player's mail chain, or NULL if none.
* We first try to find a good place to start by looking for a connected
* player with a dbref nearest to our target player. Then it's a linear
* search.
* \param player the player to search for.
* \return pointer to first message in their mail chain, or NULL.
*/
MAIL *
find_exact_starting_point(dbref player)
{
static 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 MAIL *
find_insertion_point(dbref player)
{
static 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;
}
/** Initialize the mail database pointers */
void
mail_init(void)
{
mdb_top = 0;
HEAD = NULL;
TAIL = NULL;
}
/** Load mail from disk.
* \param fp pointer to filehandle from which to load mail.
*/
int
load_mail(FILE * fp)
{
char nbuf1[8];
unsigned char *tbuf = NULL;
unsigned char *text;
u_int_16 len;
int mail_top = 0;
int mail_flags = 0;
int i = 0;
MAIL *mp, *tmpmp;
int done = 0;
char sbuf[BUFFER_LEN];
struct tm ttm;
/* 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 */
if (mail_flags & MDBF_ALIASES) {
load_malias(fp);
}
fgets(nbuf1, sizeof(nbuf1), fp);
}
mail_top = atoi(nbuf1);
if (!mail_top) {
/* mail_top could be 0 from an error or actually be 0. */
if (nbuf1[0] == '0' && nbuf1[1] == '\n') {
char buff[20];
fgets(buff, sizeof buff, fp);
if (!buff)
do_rawlog(LT_ERR,
T("MAIL: Missing end-of-dump marker in mail database."));
else if (strcmp(buff, (mail_flags & MDBF_NEW_EOD)
? "***END OF DUMP***\n" : "*** END OF DUMP ***\n") == 0)
return 1;
else
do_rawlog(LT_ERR, T("MAIL: Trailing garbage in the mail database."));
}
return 0;
}
/* first one is a special case */
mp = (MAIL *) mush_malloc(sizeof(struct mail), "mail");
mp->to = getref(fp);
mp->from = getref(fp);
if (mail_flags & MDBF_SENDERCTIME)
mp->from_ctime = getref(fp);
else
mp->from_ctime = 0; /* No one will have this creation time */
if (do_convtime(getstring_noalloc(fp), &ttm))
mp->time = mktime(&ttm);
else /* do_convtime failed. Odd. */
mp->time = mudtime;
if (mail_flags & MDBF_SUBJECT) {
tbuf = compress(getstring_noalloc(fp));
}
text = compress(getstring_noalloc(fp));
len = strlen(text) + 1;
mp->msgid = chunk_create(text, len, 1);
free(text);
if (mail_flags & MDBF_SUBJECT)
mp->subject = tbuf;
else {
strcpy(sbuf, get_message(mp));
mp->subject = compress(chopstr(sbuf, SUBJECT_LEN));
}
mp->read = getref(fp);
mp->next = NULL;
mp->prev = NULL;
HEAD = mp;
TAIL = mp;
i++;
/* now loop through */
for (; i < mail_top; i++) {
mp = (MAIL *) mush_malloc(sizeof(struct mail), "mail");
mp->to = getref(fp);
mp->from = getref(fp);
if (mail_flags & MDBF_SENDERCTIME)
mp->from_ctime = getref(fp);
else
mp->from_ctime = 0; /* No one will have this creation time */
if (do_convtime(getstring_noalloc(fp), &ttm))
mp->time = mktime(&ttm);
else /* do_convtime failed. Odd. */
mp->time = mudtime;
if (mail_flags & MDBF_SUBJECT)
tbuf = compress(getstring_noalloc(fp));
else
tbuf = NULL;
text = compress(getstring_noalloc(fp));
len = strlen(text) + 1;
mp->msgid = chunk_create(text, len, 1);
free(text);
if (tbuf)
mp->subject = tbuf;
else {
strcpy(sbuf, get_message(mp));
mp->subject = compress(chopstr(sbuf, SUBJECT_LEN));
}
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 */
do_rawlog(LT_ERR, T("MAIL: bad code."));
}
}
}
mdb_top = i;
if (i != mail_top) {
do_rawlog(LT_ERR, T("MAIL: mail_top is %d, only read in %d messages."),
mail_top, i);
}
{
char buff[20];
fgets(buff, sizeof buff, fp);
if (!buff)
do_rawlog(LT_ERR,
T("MAIL: Missing end-of-dump marker in mail database."));
else if (strcmp(buff, (mail_flags & MDBF_NEW_EOD)
? EOD : "*** END OF DUMP ***\n") != 0)
/* There's still stuff. Icky. */
do_rawlog(LT_ERR, T("MAIL: Trailing garbage in the mail database."));
}
do_mail_debug(GOD, "fix", "");
return (mdb_top);
}
static int
get_folder_number(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, atr_value(a));
sprintf(pat, ":%s:", strupper(name));
res = strstr(str, pat);
if (!res)
return -1;
res += 2 + strlen(name);
p = res;
while (isdigit((unsigned char) *p))
p++;
*p = '\0';
return atoi(res);
}
static char *
get_folder_name(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, atr_value(a));
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;
}
}
/** Assign a name to a folder.
* \param player dbref of player whose folder is to be named.
* \param fld folder number.
* \param name new name for folder.
*/
void
add_folder_name(dbref player, int fld, const char *name)
{
char *old, *res, *r;
char *new, *pat, *str, *tbuf;
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.
*/
new = (char *) mush_malloc(BUFFER_LEN, "string");
pat = (char *) mush_malloc(BUFFER_LEN, "string");
str = (char *) mush_malloc(BUFFER_LEN, "string");
tbuf = (char *) mush_malloc(BUFFER_LEN, "string");
if (!new || !pat || !str || !tbuf)
mush_panic(T("Failed to allocate strings in add_folder_name"));
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, atr_value(a));
old = (char *) string_match(str, pat);
}
if (old && *old) {
strcpy(tbuf, str);
r = old;
while (!isspace((unsigned char) *r))
r++;
*r = '\0';
res = replace_string(old, new, tbuf); /* mallocs mem! */
} else {
r = res = (char *) mush_malloc(BUFFER_LEN + 1, "replace_string.buff");
if (a)
safe_str(str, res, &r);
safe_str(new, res, &r);
*r = '\0';
}
/* put the attrib back */
(void) atr_add(player, "MAILFOLDERS", res, GOD,
AF_WIZARD | AF_NOPROG | AF_LOCKED);
mush_free((Malloc_t) res, "replace_string.buff");
mush_free((Malloc_t) new, "string");
mush_free((Malloc_t) pat, "string");
mush_free((Malloc_t) str, "string");
mush_free((Malloc_t) tbuf, "string");
}
static int
player_folder(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(atr_value(a));
}
/** Set a player's current mail folder to a given folder.
* \param player the player whose current folder is to be set.
* \param fnum folder number to make current.
*/
void
set_player_folder(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)
(void) atr_add(player, a->name, tbuf1, GOD, a->flags);
else /* Shouldn't happen, but... */
(void) atr_add(player, "MAILCURF", tbuf1, GOD,
AF_WIZARD | AF_NOPROG | AF_LOCKED);
}
static int
parse_folder(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((unsigned char) *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(dbref player, MAIL *mp, struct mail_selector ms, int num)
{
int diffdays;
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 && !was_sender(ms.player, mp))
return 0;
mpflag = Read(mp) ? mp->read : (mp->read | M_MSUNREAD);
if (!All(ms) && !(ms.flags & mpflag))
return 0;
if (AllInFolder(ms) && (Folder(mp) == player_folder(player)))
return 1;
if (ms.days != -1) {
/* Get the time now, subtract mp->time, and compare the results with
* ms.days (in manner of ms.day_comp) */
diffdays = (int) (difftime(mudtime, mp->time) / 86400);
if (sign(diffdays - ms.days) != ms.day_comp)
return 0;
else
return 1;
}
return 1;
}
static int
parse_msglist(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 in current folder */
ms->flags |= M_FOLDER;
return 1;
}
/* Don't mess with msglist itself */
strncpy(tbuf1, msglist, BUFFER_LEN - 1);
p = tbuf1;
while (p && *p && isspace((unsigned char) *p))
p++;
if (!p || !*p) {
ms->flags |= M_FOLDER;
return 1; /* all messages in current folder */
}
if (isdigit((unsigned char) *p) || *p == '-') {
if (!parse_message_spec(player, p, &ms->low, &ms->high, &folder)) {
notify(player, T("MAIL: Invalid message specification"));
return 0;
}
ms->flags |= FolderBit(folder);
} else if (*p == '~') {
/* exact # of days old */
p++;
if (!p || !*p) {
notify(player, T("MAIL: Invalid age"));
return 0;
}
if (!is_integer(p)) {
notify(player, T("MAIL: Message ages must be integers"));
return 0;
}
ms->day_comp = 0;
ms->days = atoi(p);
if (ms->days < 0) {
notify(player, T("MAIL: Invalid age"));
return 0;
}
} else if (*p == '<') {
/* less than # of days old */
p++;
if (!p || !*p) {
notify(player, T("MAIL: Invalid age"));
return 0;
}
if (!is_integer(p)) {
notify(player, T("MAIL: Message ages must be integers"));
return 0;
}
ms->day_comp = -1;
ms->days = atoi(p);
if (ms->days < 0) {
notify(player, T("MAIL: Invalid age"));
return 0;
}
} else if (*p == '>') {
/* greater than # of days old */
p++;
if (!p || !*p) {
notify(player, T("MAIL: Invalid age"));
return 0;
}
if (!is_integer(p)) {
notify(player, T("MAIL: Message ages must be integers"));
return 0;
}
ms->day_comp = 1;
ms->days = atoi(p);
if (ms->days < 0) {
notify(player, T("MAIL: Invalid age"));
return 0;
}
} else if (*p == '#') {
/* From db# */
if (!is_objid(p)) {
notify(player, T("MAIL: Invalid dbref #"));
return 0;
}
ms->player = parse_objid(p);
if (!GoodObject(ms->player) || !(ms->player)) {
notify(player, T("MAIL: Invalid dbref #"));
return 0;
}
} else if (*p == '*') {
/* From player name */
p++;
if (!p || !*p) {
notify(player, T("MAIL: Invalid player"));
return 0;
}
ms->player = lookup_player(p);
if (ms->player == NOTHING) {
notify(player, T("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 | M_FOLDER;
} else if (!strcasecmp(p, "unread")) {
ms->flags = M_MSUNREAD | M_FOLDER;
} else if (!strcasecmp(p, "read")) {
ms->flags = M_MSGREAD | M_FOLDER;
} else if (!strcasecmp(p, "clear") || !strcasecmp(p, "cleared")) {
ms->flags = M_CLEARED | M_FOLDER;
} 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, T("MAIL: Invalid message specification"));
return 0;
}
return 1;
}
static char *
status_chars(MAIL *mp)
{
/* Return a short description of message flags */
static char res[10];
char *p;
res[0] = '\0';
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(MAIL *mp)
{
/* Return a longer description of message flags */
static char tbuf1[BUFFER_LEN];
tbuf1[0] = '\0';
if (Read(mp))
strcat(tbuf1, T("Read "));
else
strcat(tbuf1, T("Unread "));
if (Cleared(mp))
strcat(tbuf1, T("Cleared "));
if (Urgent(mp))
strcat(tbuf1, T("Urgent "));
if (Mass(mp))
strcat(tbuf1, T("Mass "));
if (Forward(mp))
strcat(tbuf1, T("Fwd "));
if (Tagged(mp))
strcat(tbuf1, T("Tagged"));
return tbuf1;
}
/** Check for new mail.
* \param player player to check for new mail (and to notify).
* \param folder folder number to check.
* \param silent if 1, don't tell player if they have no mail.
*/
void
check_mail(dbref player, int folder, int silent)
{
int rc; /* read messages */
int uc; /* unread messages */
int cc; /* cleared messages */
int total;
/* just count messages */
count_mail(player, folder, &rc, &uc, &cc);
total = rc + uc + cc;
if (total > 0)
notify_format(player,
T
("MAIL: %d messages in folder %d [%s] (%d unread, %d cleared)."),
total, folder, get_folder_name(player, folder), uc, cc);
else if (!silent)
notify(player, T("\nMAIL: You have no mail.\n"));
if ((folder == 0) && (total + 5 > MAIL_LIMIT))
notify_format(player, T("MAIL: Warning! Limit on inbox messages is %d!"),
MAIL_LIMIT);
return;
}
static int
sign(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(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;
}
static int
send_mail_alias(dbref player, char *aname, char *subject, char *message,
mail_flag flags, int silent, int nosig)
{
struct mail_alias *m;
int i;
/* send a mail message to each player on an alias */
/* We return 0 if this wasn't an alias */
m = get_malias(player, aname);
if (!m)
return 0;
/* Is it an alias they can use? */
if (!((m->owner == player) || (m->nflags == 0) ||
(Hasprivs(player)) ||
((m->nflags & ALIAS_MEMBERS) && ismember(m, player))))
return 0;
/* If they are not allowed to see the people on the alias, then
* we must treat this as a case of silent mailing.
*/
if (!((m->owner == player) || (m->mflags == 0) ||
(Hasprivs(player)) ||
((m->mflags & ALIAS_MEMBERS) && ismember(m, player)))) {
silent = 1;
notify_format(player,
T("You sent your message to the '%s' alias"), m->name);
}
for (i = 0; i < m->size; i++) {
send_mail(player, m->members[i], subject, message, flags, silent, nosig);
}
return 1; /* Success */
}