mux/src/mail.cpp

Go to the documentation of this file.
00001 // mail.cpp
00002 //
00003 // $Id: mail.cpp,v 1.55 2007/03/07 00:27:59 sdennis Exp $
00004 //
00005 // This code was taken from Kalkin's DarkZone code, which was
00006 // originally taken from PennMUSH 1.50 p10, and has been heavily modified
00007 // since being included in MUX.
00008 //
00009 
00010 #include "copyright.h"
00011 #include "autoconf.h"
00012 #include "config.h"
00013 #include "externs.h"
00014 
00015 #include <sys/types.h>
00016 
00017 #include "attrs.h"
00018 #include "command.h"
00019 #include "powers.h"
00020 #include "mail.h"
00021 
00022 #define SIZEOF_MALIAS 13
00023 #define WIDTHOF_MALIASDESC 40
00024 #define SIZEOF_MALIASDESC (WIDTHOF_MALIASDESC*2)
00025 
00026 #define MAX_MALIAS_MEMBERSHIP 100
00027 struct malias
00028 {
00029     int owner;
00030     char *name;
00031     char *desc;
00032     int desc_width; // The visual width of the Mail Alias Description.
00033     int numrecep;
00034     dbref list[MAX_MALIAS_MEMBERSHIP];
00035 };
00036 
00037 static int ma_size = 0;
00038 static int ma_top = 0;
00039 
00040 static struct malias **malias   = NULL;
00041 static MAILBODY      *mail_list = NULL;
00042 
00043 // Handling functions for the database of mail messages.
00044 //
00045 
00046 // mail_db_grow - We keep a database of mail text, so if we send a
00047 // message to more than one player, we won't have to duplicate the
00048 // text.
00049 //
00050 #define MAIL_FUDGE 1
00051 static void mail_db_grow(int newtop)
00052 {
00053     if (newtop <= mudstate.mail_db_top)
00054     {
00055         return;
00056     }
00057     if (mudstate.mail_db_size <= newtop)
00058     {
00059         // We need to make the mail bag bigger.
00060         //
00061         int newsize = mudstate.mail_db_size + 100;
00062         if (newtop > newsize)
00063         {
00064             newsize = newtop;
00065         }
00066 
00067         MAILBODY *newdb = (MAILBODY *)MEMALLOC((newsize + MAIL_FUDGE) * sizeof(MAILBODY));
00068         ISOUTOFMEMORY(newdb);
00069         if (mail_list)
00070         {
00071             mail_list -= MAIL_FUDGE;
00072             memcpy( newdb,
00073                     mail_list,
00074                     (mudstate.mail_db_top + MAIL_FUDGE) * sizeof(MAILBODY));
00075             MEMFREE(mail_list);
00076             mail_list = NULL;
00077         }
00078         mail_list = newdb + MAIL_FUDGE;
00079         newdb = NULL;
00080         mudstate.mail_db_size = newsize;
00081     }
00082 
00083     // Initialize new parts of the mail bag.
00084     //
00085     for (int i = mudstate.mail_db_top; i < newtop; i++)
00086     {
00087         mail_list[i].m_nRefs = 0;
00088         mail_list[i].m_pMessage = NULL;
00089     }
00090     mudstate.mail_db_top = newtop;
00091 }
00092 
00093 // MessageReferenceInc - Increments the reference count for any
00094 // particular message.
00095 //
00096 static DCL_INLINE void MessageReferenceInc(int number)
00097 {
00098     mail_list[number].m_nRefs++;
00099 }
00100 
00101 // MessageReferenceCheck - Checks whether the reference count for
00102 // any particular message indicates that the message body should be
00103 // freed. Also checks that if a message point is null, that the
00104 // reference count is zero.
00105 //
00106 static void MessageReferenceCheck(int number)
00107 {
00108     MAILBODY &m = mail_list[number];
00109     if (m.m_nRefs <= 0)
00110     {
00111         if (m.m_pMessage)
00112         {
00113             MEMFREE(m.m_pMessage);
00114             m.m_pMessage = NULL;
00115         }
00116     }
00117     if (m.m_pMessage == NULL)
00118     {
00119         m.m_nRefs = 0;
00120     }
00121 }
00122 
00123 // MessageReferenceDec - Decrements the reference count for a message, and
00124 // will also delete the message if the counter reaches 0.
00125 //
00126 static void MessageReferenceDec(int number)
00127 {
00128     mail_list[number].m_nRefs--;
00129     MessageReferenceCheck(number);
00130 }
00131 
00132 // MessageFetch - returns the text for a particular message number. This
00133 // text should not be modified.
00134 //
00135 static const char *MessageFetch(int number)
00136 {
00137     MessageReferenceCheck(number);
00138     if (mail_list[number].m_pMessage)
00139     {
00140         return mail_list[number].m_pMessage;
00141     }
00142     else
00143     {
00144         return "MAIL: This mail message does not exist in the database. Please alert your admin.";
00145     }
00146 }
00147 
00148 // This function returns a reference to the message and the the
00149 // reference count is increased to reflect that.
00150 //
00151 static int MessageAdd(char *pMessage)
00152 {
00153     if (!mail_list)
00154     {
00155         mail_db_grow(1);
00156     }
00157 
00158     int i;
00159     MAILBODY *pm;
00160     bool bFound = false;
00161     for (i = 0; i < mudstate.mail_db_top; i++)
00162     {
00163         pm = &mail_list[i];
00164         if (pm->m_pMessage == NULL)
00165         {
00166             pm->m_nRefs = 0;
00167             bFound = true;
00168             break;
00169         }
00170     }
00171     if (!bFound)
00172     {
00173         mail_db_grow(i + 1);
00174     }
00175 
00176     pm = &mail_list[i];
00177     pm->m_pMessage = StringClone(pMessage);
00178     MessageReferenceInc(i);
00179     return i;
00180 }
00181 
00182 // add_mail_message - adds a new text message to the mail database, and returns
00183 // a unique number for that message.
00184 //
00185 // IF return value is !NOTHING, you have a reference to the message,
00186 // and the reference count reflects that.
00187 //
00188 static int add_mail_message(dbref player, char *message)
00189 {
00190     if (!mux_stricmp(message, "clear"))
00191     {
00192         notify(player, "MAIL: You probably did not intend to send a @mail saying 'clear'.");
00193         return NOTHING;
00194     }
00195 
00196     // Evaluate signature.
00197     //
00198     int   aflags;
00199     dbref aowner;
00200     char *bp = alloc_lbuf("add_mail_message");
00201     char *atrstr = atr_get(player, A_SIGNATURE, &aowner, &aflags);
00202     char *execstr = bp;
00203     char *str = atrstr;
00204     mux_exec(execstr, &bp, player, player, player,
00205              EV_STRIP_CURLY | EV_FCHECK | EV_EVAL, &str, (char **)NULL, 0);
00206     *bp = '\0';
00207 
00208     // Save message body and return a reference to it.
00209     //
00210     int number = MessageAdd(tprintf("%s %s", message, execstr));
00211     free_lbuf(atrstr);
00212     free_lbuf(execstr);
00213     return number;
00214 }
00215 
00216 // This function is -only- used from reading from the disk, and so
00217 // it does -not- manage the reference counts.
00218 //
00219 static bool MessageAddWithNumber(int i, char *pMessage)
00220 {
00221     mail_db_grow(i+1);
00222 
00223     MAILBODY *pm = &mail_list[i];
00224     pm->m_pMessage = StringClone(pMessage);
00225     return true;
00226 }
00227 
00228 // new_mail_message - used for reading messages in from disk which
00229 // already have a number assigned to them.
00230 //
00231 // This function is -only- used from reading from the disk, and so
00232 // it does -not- manage the reference counts.
00233 //
00234 static void new_mail_message(char *message, int number)
00235 {
00236     bool bTruncated = false;
00237     if (strlen(message) > LBUF_SIZE-1)
00238     {
00239         bTruncated = true;
00240         message[LBUF_SIZE-1] = '\0';
00241     }
00242     MessageAddWithNumber(number, message);
00243     if (bTruncated)
00244     {
00245         STARTLOG(LOG_BUGS, "BUG", "MAIL");
00246         log_text(tprintf("new_mail_message: Mail message %d truncated.", number));
00247         ENDLOG;
00248     }
00249 }
00250 
00251 /*-------------------------------------------------------------------------*
00252  *   User mail functions (these are called from game.c)
00253  *
00254  * do_mail - cases
00255  * do_mail_read - read messages
00256  * do_mail_list - list messages
00257  * do_mail_flags - tagging, untagging, clearing, unclearing of messages
00258  * do_mail_file - files messages into a new folder
00259  * do_mail_fwd - forward messages to another player(s)
00260  * do_mail_reply - reply to a message
00261  * do_mail_count - count messages
00262  * do_mail_purge - purge cleared messages
00263  * do_mail_change_folder - change current folder
00264  *-------------------------------------------------------------------------*/
00265 
00266 static void set_player_folder(dbref player, int fnum)
00267 {
00268     // Set a player's folder to fnum.
00269     //
00270     char *tbuf1 = alloc_lbuf("set_player_folder");
00271     mux_ltoa(fnum, tbuf1);
00272     ATTR *a = atr_num(A_MAILCURF);
00273     if (a)
00274     {
00275         atr_add(player, A_MAILCURF, tbuf1, GOD, a->flags);
00276     }
00277     else
00278     {
00279         // Shouldn't happen, but...
00280         //
00281         atr_add(player, A_MAILCURF, tbuf1, GOD, AF_ODARK | AF_WIZARD | AF_NOPROG | AF_LOCK);
00282     }
00283     free_lbuf(tbuf1);
00284 }
00285 
00286 static void add_folder_name(dbref player, int fld, char *name)
00287 {
00288     // Fetch current list of folders
00289     //
00290     int aflags;
00291     size_t nFolders;
00292     dbref aowner;
00293     char *aFolders = alloc_lbuf("add_folder_name.str");
00294     atr_get_str_LEN(aFolders, player, A_MAILFOLDERS, &aowner, &aflags,
00295         &nFolders);
00296 
00297     // Build new record ("%d:%s:%d", fld, uppercase(name), fld);
00298     //
00299     char *aNew = alloc_lbuf("add_folder_name.new");
00300     char *q = aNew;
00301     q += mux_ltoa(fld, q);
00302     *q++ = ':';
00303     char *p = name;
00304     while (*p)
00305     {
00306         *q++ = mux_toupper(*p);
00307         p++;
00308     }
00309     *q++ = ':';
00310     q += mux_ltoa(fld, q);
00311     *q = '\0';
00312     size_t nNew = q - aNew;
00313 
00314     if (nFolders != 0)
00315     {
00316         // Build pattern ("%d:", fld)
00317         //
00318         char *aPattern = alloc_lbuf("add_folder_name.pat");
00319         q = aPattern;
00320         q += mux_ltoa(fld, q);
00321         *q++ = ':';
00322         *q = '\0';
00323         size_t nPattern = q - aPattern;
00324 
00325         BMH_State bmhs;
00326         BMH_Prepare(&bmhs, nPattern, aPattern);
00327         for (;;)
00328         {
00329             int i = BMH_Execute(&bmhs, nPattern, aPattern, nFolders, aFolders);
00330             if (i < 0)
00331             {
00332                 break;
00333             }
00334 
00335             // Remove old record.
00336             //
00337             q = aFolders + i;
00338             p = q + nPattern;
00339 
00340             // Eat leading spaces.
00341             //
00342             while (  aFolders < q
00343                   && mux_isspace(q[-1]))
00344             {
00345                 q--;
00346             }
00347 
00348             // Skip past old record and trailing spaces.
00349             //
00350             while (  *p
00351                   && *p != ':')
00352             {
00353                 p++;
00354             }
00355             while (  *p
00356                   && !mux_isspace(*p))
00357             {
00358                 p++;
00359             }
00360             while (mux_isspace(*p))
00361             {
00362                 p++;
00363             }
00364 
00365             if (q != aFolders)
00366             {
00367                 *q++ = ' ';
00368             }
00369             while (*p)
00370             {
00371                 *q++ = *p++;
00372             }
00373             *q = '\0';
00374             nFolders = q - aFolders;
00375         }
00376         free_lbuf(aPattern);
00377     }
00378     if (nFolders + 1 + nNew < LBUF_SIZE)
00379     {
00380         // It will fit. Append new record.
00381         //
00382         q = aFolders + nFolders;
00383         if (nFolders)
00384         {
00385             *q++ = ' ';
00386         }
00387         memcpy(q, aNew, nNew);
00388         q += nNew;
00389         *q = '\0';
00390 
00391         atr_add(player, A_MAILFOLDERS, aFolders, player,
00392             AF_MDARK | AF_WIZARD | AF_NOPROG | AF_LOCK);
00393     }
00394     free_lbuf(aFolders);
00395     free_lbuf(aNew);
00396 }
00397 
00398 static char *get_folder_name(dbref player, int fld)
00399 {
00400     // Get the name of the folder, or return "unnamed".
00401     //
00402     int aflags;
00403     size_t nFolders;
00404     dbref aowner;
00405     static char aFolders[LBUF_SIZE];
00406     atr_get_str_LEN(aFolders, player, A_MAILFOLDERS, &aowner, &aflags,
00407         &nFolders);
00408     char *p;
00409     if (nFolders != 0)
00410     {
00411         char *aPattern = alloc_lbuf("get_folder_name");
00412         p = aPattern;
00413         p += mux_ltoa(fld, p);
00414         *p++ = ':';
00415         *p = '\0';
00416         size_t nPattern = p - aPattern;
00417 
00418         int i = BMH_StringSearch(nPattern, aPattern, nFolders, aFolders);
00419         free_lbuf(aPattern);
00420 
00421         if (0 <= i)
00422         {
00423             p = aFolders + i + nPattern;
00424             char *q = p;
00425             while (  *q
00426                   && *q != ':')
00427             {
00428                 q++;
00429             }
00430             *q = '\0';
00431             return p;
00432         }
00433     }
00434     p = "unnamed";
00435     return p;
00436 }
00437 
00438 static int get_folder_number(dbref player, char *name)
00439 {
00440     // Look up a folder name and return the corresponding folder number.
00441     //
00442     int aflags;
00443     size_t nFolders;
00444     dbref aowner;
00445     char *aFolders = alloc_lbuf("get_folder_num_str");
00446     atr_get_str_LEN(aFolders, player, A_MAILFOLDERS, &aowner, &aflags,
00447         &nFolders);
00448     if (nFolders != 0)
00449     {
00450         char *aPattern = alloc_lbuf("get_folder_num_pat");
00451         char *q = aPattern;
00452         *q++ = ':';
00453         char *p = name;
00454         while (*p)
00455         {
00456             *q++ = mux_toupper(*p);
00457             p++;
00458         }
00459         *q++ = ':';
00460         *q = '\0';
00461         size_t nPattern = q - aPattern;
00462 
00463         int i = BMH_StringSearch(nPattern, aPattern, nFolders, aFolders);
00464         free_lbuf(aPattern);
00465         if (0 <= i)
00466         {
00467             p = aFolders + i + nPattern;
00468             q = p;
00469             while (  *q
00470                   && !mux_isspace(*q))
00471             {
00472                 q++;
00473             }
00474             *q = '\0';
00475             i = mux_atol(p);
00476             free_lbuf(aFolders);
00477             return i;
00478         }
00479     }
00480     free_lbuf(aFolders);
00481     return -1;
00482 }
00483 
00484 static int parse_folder(dbref player, char *folder_string)
00485 {
00486     // Given a string, return a folder #, or -1.
00487     //
00488     if (  !folder_string
00489        || !*folder_string)
00490     {
00491         return -1;
00492     }
00493     if (mux_isdigit(*folder_string))
00494     {
00495         int fnum = mux_atol(folder_string);
00496         if (  fnum < 0
00497            || fnum > MAX_FOLDERS)
00498         {
00499             return -1;
00500         }
00501         else
00502         {
00503             return fnum;
00504         }
00505     }
00506 
00507     // Handle named folders here
00508     //
00509     return get_folder_number(player, folder_string);
00510 }
00511 
00512 #define MAIL_INVALID_RANGE  0
00513 #define MAIL_INVALID_NUMBER 1
00514 #define MAIL_INVALID_AGE    2
00515 #define MAIL_INVALID_DBREF  3
00516 #define MAIL_INVALID_PLAYER 4
00517 #define MAIL_INVALID_SPEC   5
00518 #define MAIL_INVALID_PLAYER_OR_USING_MALIAS 6
00519 
00520 static char *mailmsg[] =
00521 {
00522     "MAIL: Invalid message range",
00523     "MAIL: Invalid message number",
00524     "MAIL: Invalid age",
00525     "MAIL: Invalid dbref #",
00526     "MAIL: Invalid player",
00527     "MAIL: Invalid message specification",
00528     "MAIL: Invalid player or trying to send @mail to a @malias without a subject",
00529 };
00530 
00531 static bool parse_msglist(char *msglist, struct mail_selector *ms, dbref player)
00532 {
00533     // Take a message list, and return the appropriate mail_selector setup.
00534     // For now, msglists are quite restricted. That'll change once all this
00535     // is working. Returns 0 if couldn't parse, and also notifies the player
00536     // why.
00537 
00538     // Initialize the mail selector - this matches all messages.
00539     //
00540     ms->low = 0;
00541     ms->high = 0;
00542     ms->flags = 0x0FFF | M_MSUNREAD;
00543     ms->player = 0;
00544     ms->days = -1;
00545     ms->day_comp = 0;
00546 
00547     // Now, parse the message list.
00548     //
00549     if (!msglist || !*msglist)
00550     {
00551         // All messages
00552         //
00553         return true;
00554     }
00555 
00556     char *p = msglist;
00557     while (mux_isspace(*p))
00558     {
00559         p++;
00560     }
00561 
00562     if (*p == '\0')
00563     {
00564         return true;
00565     }
00566 
00567     if (mux_isdigit(*p))
00568     {
00569         // Message or range.
00570         //
00571         char *q = strchr(p, '-');
00572         if (q)
00573         {
00574             // We have a subrange, split it up and test to see if it is valid.
00575             //
00576             q++;
00577             ms->low = mux_atol(p);
00578             if (ms->low <= 0)
00579             {
00580                 notify(player, mailmsg[MAIL_INVALID_RANGE]);
00581                 return false;
00582             }
00583             if (*q == '\0')
00584             {
00585                 // Unbounded range.
00586                 //
00587                 ms->high = 0;
00588             }
00589             else
00590             {
00591                 ms->high = mux_atol(q);
00592                 if (ms->low > ms->high)
00593                 {
00594                     notify(player, mailmsg[MAIL_INVALID_RANGE]);
00595                     return false;
00596                 }
00597             }
00598         }
00599         else
00600         {
00601             // A single message.
00602             //
00603             ms->low = ms->high = mux_atol(p);
00604             if (ms->low <= 0)
00605             {
00606                 notify(player, mailmsg[MAIL_INVALID_NUMBER]);
00607                 return false;
00608             }
00609         }
00610     }
00611     else
00612     {
00613         switch (mux_toupper(*p))
00614         {
00615         case '-':
00616 
00617             // Range with no start.
00618             //
00619             p++;
00620             if (*p == '\0')
00621             {
00622                 notify(player, mailmsg[MAIL_INVALID_RANGE]);
00623                 return false;
00624             }
00625             ms->high = mux_atol(p);
00626             if (ms->high <= 0)
00627             {
00628                 notify(player, mailmsg[MAIL_INVALID_RANGE]);
00629                 return false;
00630             }
00631             break;
00632 
00633         case '~':
00634 
00635             // Exact # of days old.
00636             //
00637             p++;
00638             if (*p == '\0')
00639             {
00640                 notify(player, mailmsg[MAIL_INVALID_AGE]);
00641                 return false;
00642             }
00643             ms->day_comp = 0;
00644             ms->days = mux_atol(p);
00645             if (ms->days < 0)
00646             {
00647                 notify(player, mailmsg[MAIL_INVALID_AGE]);
00648                 return false;
00649             }
00650             break;
00651 
00652         case '<':
00653 
00654             // Less than # of days old.
00655             //
00656             p++;
00657             if (*p == '\0')
00658             {
00659                 notify(player, mailmsg[MAIL_INVALID_AGE]);
00660                 return false;
00661             }
00662             ms->day_comp = -1;
00663             ms->days = mux_atol(p);
00664             if (ms->days < 0)
00665             {
00666                 notify(player, mailmsg[MAIL_INVALID_AGE]);
00667                 return false;
00668             }
00669             break;
00670 
00671         case '>':
00672 
00673             // Greater than # of days old.
00674             //
00675             p++;
00676             if (*p == '\0')
00677             {
00678                 notify(player, mailmsg[MAIL_INVALID_AGE]);
00679                 return false;
00680             }
00681             ms->day_comp = 1;
00682             ms->days = mux_atol(p);
00683             if (ms->days < 0)
00684             {
00685                 notify(player, mailmsg[MAIL_INVALID_AGE]);
00686                 return false;
00687             }
00688             break;
00689 
00690         case '#':
00691 
00692             // From db#.
00693             //
00694             p++;
00695             if (*p == '\0')
00696             {
00697                 notify(player, mailmsg[MAIL_INVALID_DBREF]);
00698                 return false;
00699             }
00700             ms->player = mux_atol(p);
00701             if (!Good_obj(ms->player) || !(ms->player))
00702             {
00703                 notify(player, mailmsg[MAIL_INVALID_DBREF]);
00704                 return false;
00705             }
00706             break;
00707 
00708         case '*':
00709 
00710             // From player name.
00711             //
00712             p++;
00713             if (*p == '\0')
00714             {
00715                 notify(player, mailmsg[MAIL_INVALID_PLAYER]);
00716                 return false;
00717             }
00718             ms->player = lookup_player(player, p, true);
00719             if (ms->player == NOTHING)
00720             {
00721                 notify(player, mailmsg[MAIL_INVALID_PLAYER_OR_USING_MALIAS]);
00722                 return false;
00723             }
00724             break;
00725 
00726         case 'A':
00727 
00728             // All messages, all folders
00729             //
00730             p++;
00731             switch (mux_toupper(*p))
00732             {
00733             case '\0':
00734                 notify(player, "MAIL: A isn't enough (all?)");
00735                 return false;
00736 
00737             case 'L':
00738 
00739                 // All messages, all folders
00740                 //
00741                 p++;
00742                 switch (mux_toupper(*p))
00743                 {
00744                 case '\0':
00745                     notify(player, "MAIL: AL isn't enough (all?)");
00746                     return false;
00747 
00748                 case 'L':
00749 
00750                     // All messages, all folders
00751                     //
00752                     p++;
00753                     if (*p == '\0')
00754                     {
00755                         ms->flags = M_ALL;
00756                     }
00757                     else
00758                     {
00759                         notify(player, mailmsg[MAIL_INVALID_SPEC]);
00760                         return false;
00761                     }
00762                     break;
00763 
00764                 default:
00765 
00766                     // Bad
00767                     //
00768                     notify(player, mailmsg[MAIL_INVALID_SPEC]);
00769                     return false;
00770                 }
00771                 break;
00772 
00773             default:
00774 
00775                 // Bad
00776                 //
00777                 notify(player, mailmsg[MAIL_INVALID_SPEC]);
00778                 return false;
00779             }
00780             break;
00781 
00782         case 'U':
00783 
00784             // Urgent, Unread
00785             //
00786             p++;
00787             if (*p == '\0')
00788             {
00789                 notify(player, "MAIL: U is ambiguous (urgent or unread?)");
00790                 return false;
00791             }
00792             switch (mux_toupper(*p))
00793             {
00794             case 'R':
00795 
00796                 // Urgent
00797                 //
00798                 ms->flags = M_URGENT;
00799                 break;
00800 
00801             case 'N':
00802 
00803                 // Unread
00804                 //
00805                 ms->flags = M_MSUNREAD;
00806                 break;
00807 
00808             default:
00809 
00810                 // Bad
00811                 //
00812                 notify(player, mailmsg[MAIL_INVALID_SPEC]);
00813                 return false;
00814             }
00815             break;
00816 
00817         case 'R':
00818 
00819             // Read
00820             //
00821             ms->flags = M_ISREAD;
00822             break;
00823 
00824         case 'C':
00825 
00826             // Cleared.
00827             //
00828             ms->flags = M_CLEARED;
00829             break;
00830 
00831         case 'T':
00832 
00833             // Tagged.
00834             //
00835             ms->flags = M_TAG;
00836             break;
00837 
00838         case 'M':
00839 
00840             // Mass, me.
00841             //
00842             p++;
00843             if (*p == '\0')
00844             {
00845                 notify(player, "MAIL: M is ambiguous (mass or me?)");
00846                 return false;
00847             }
00848             switch (mux_toupper(*p))
00849             {
00850             case 'A':
00851 
00852                 ms->flags = M_MASS;
00853                 break;
00854 
00855             case 'E':
00856 
00857                 ms->player = player;
00858                 break;
00859 
00860             default:
00861 
00862                 notify(player, mailmsg[MAIL_INVALID_SPEC]);
00863                 return false;
00864             }
00865             break;
00866 
00867         default:
00868 
00869             // Bad news.
00870             //
00871             notify(player, mailmsg[MAIL_INVALID_SPEC]);
00872             return false;
00873         }
00874     }
00875     return true;
00876 }
00877 
00878 static int player_folder(dbref player)
00879 {
00880     // Return the player's current folder number. If they don't have one, set
00881     // it to 0.
00882     //
00883     int flags;
00884     char *atrstr = atr_pget(player, A_MAILCURF, &player, &flags);
00885     if (!*atrstr)
00886     {
00887         free_lbuf(atrstr);
00888         set_player_folder(player, 0);
00889         return 0;
00890     }
00891     int number = mux_atol(atrstr);
00892     free_lbuf(atrstr);
00893     return number;
00894 }
00895 
00896 // Change or rename a folder
00897 //
00898 static void do_mail_change_folder(dbref player, char *fld, char *newname)
00899 {
00900     int pfld;
00901 
00902     if (!fld || !*fld)
00903     {
00904         // Check mail in all folders
00905         //
00906         for (pfld = 0; pfld <= MAX_FOLDERS; pfld++)
00907         {
00908             check_mail(player, pfld, true);
00909         }
00910         pfld = player_folder(player);
00911         notify(player, tprintf("MAIL: Current folder is %d [%s].",
00912                        pfld, get_folder_name(player, pfld)));
00913         return;
00914     }
00915     pfld = parse_folder(player, fld);
00916     if (pfld < 0)
00917     {
00918         notify(player, "MAIL: What folder is that?");
00919         return;
00920     }
00921     if (newname && *newname)
00922     {
00923         // We're changing a folder name here
00924         //
00925         if (strlen(newname) > FOLDER_NAME_LEN)
00926         {
00927             notify(player, "MAIL: Folder name too long");
00928             return;
00929         }
00930         char *p;
00931         for (p = newname; mux_isalnum(*p); p++) ;
00932         if (*p != '\0')
00933         {
00934             notify(player, "MAIL: Illegal folder name");
00935             return;
00936         }
00937 
00938         add_folder_name(player, pfld, newname);
00939         notify(player, tprintf("MAIL: Folder %d now named '%s'", pfld, newname));
00940     }
00941     else
00942     {
00943         // Set a new folder
00944         //
00945         set_player_folder(player, pfld);
00946         notify(player, tprintf("MAIL: Current folder set to %d [%s].",
00947                        pfld, get_folder_name(player, pfld)));
00948     }
00949 }
00950 
00951 static int sign(int x)
00952 {
00953     if (x == 0)
00954     {
00955         return 0;
00956     }
00957     else if (x < 0)
00958     {
00959         return -1;
00960     }
00961     else
00962     {
00963         return 1;
00964     }
00965 }
00966 
00967 static bool mail_match(struct mail *mp, struct mail_selector ms, int num)
00968 {
00969     // Does a piece of mail match the mail_selector?
00970     //
00971     if (ms.low && num < ms.low)
00972     {
00973         return false;
00974     }
00975     if (ms.high && ms.high < num)
00976     {
00977         return false;
00978     }
00979     if (ms.player && mp->from != ms.player)
00980     {
00981         return false;
00982     }
00983 
00984     mail_flag mpflag = Read(mp)
00985         ? (mp->read | M_ALL)
00986         : (mp->read | M_ALL | M_MSUNREAD);
00987 
00988     if ((ms.flags & mpflag) == 0)
00989     {
00990         return false;
00991     }
00992 
00993     if (ms.days == -1)
00994     {
00995         return true;
00996     }
00997 
00998     // Get the time now, subtract mp->time, and compare the results with
00999     // ms.days (in manner of ms.day_comp)
01000     //
01001     CLinearTimeAbsolute ltaNow;
01002     ltaNow.GetLocal();
01003 
01004     const char *pMailTimeStr = mp->time;
01005 
01006     CLinearTimeAbsolute ltaMail;
01007     if (ltaMail.SetString(pMailTimeStr))
01008     {
01009         CLinearTimeDelta ltd(ltaMail, ltaNow);
01010         int iDiffDays = ltd.ReturnDays();
01011         if (sign(iDiffDays - ms.days) == ms.day_comp)
01012         {
01013             return true;
01014         }
01015     }
01016     return false;
01017 }
01018 
01019 // Adjust the flags of a set of messages.
01020 // If negate is true, clear the flag.
01021 static void do_mail_flags(dbref player, char *msglist, mail_flag flag, bool negate)
01022 {
01023     struct mail_selector ms;
01024 
01025     if (!parse_msglist(msglist, &ms, player))
01026     {
01027         return;
01028     }
01029     int i = 0, j = 0;
01030     int folder = player_folder(player);
01031 
01032     MailList ml(player);
01033     struct mail *mp;
01034     for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
01035     {
01036         if (  All(ms)
01037            || Folder(mp) == folder)
01038         {
01039             i++;
01040             if (mail_match(mp, ms, i))
01041             {
01042                 j++;
01043                 if (negate)
01044                 {
01045                     mp->read &= ~flag;
01046                 }
01047                 else
01048                 {
01049                     mp->read |= flag;
01050                 }
01051 
01052                 switch (flag)
01053                 {
01054                 case M_TAG:
01055                     notify(player, tprintf("MAIL: Msg #%d %s.", i, negate ? "untagged" : "tagged"));
01056                     break;
01057 
01058                 case M_CLEARED:
01059                     if (Unread(mp) && !negate)
01060                     {
01061                         notify(player, tprintf("MAIL: Unread Msg #%d cleared! Use @mail/unclear %d to recover.", i, i));
01062                     }
01063                     else
01064                     {
01065                         notify(player, tprintf("MAIL: Msg #%d %s.", i, negate ? "uncleared" : "cleared"));
01066                     }
01067                     break;
01068 
01069                 case M_SAFE:
01070                     notify(player, tprintf("MAIL: Msg #%d marked safe.", i));
01071                     break;
01072                 }
01073             }
01074         }
01075     }
01076 
01077     if (!j)
01078     {
01079         // Ran off the end of the list without finding anything.
01080         //
01081         notify(player, "MAIL: You don't have any matching messages!");
01082     }
01083 }
01084 
01085 static void do_mail_tag(dbref player, char *msglist)
01086 {
01087     do_mail_flags(player, msglist, M_TAG, false);
01088 }
01089 
01090 static void do_mail_safe(dbref player, char *msglist)
01091 {
01092     do_mail_flags(player, msglist, M_SAFE, false);
01093 }
01094 
01095 void do_mail_clear(dbref player, char *msglist)
01096 {
01097     do_mail_flags(player, msglist, M_CLEARED, false);
01098 }
01099 
01100 static void do_mail_untag(dbref player, char *msglist)
01101 {
01102     do_mail_flags(player, msglist, M_TAG, true);
01103 }
01104 
01105 static void do_mail_unclear(dbref player, char *msglist)
01106 {
01107     do_mail_flags(player, msglist, M_CLEARED, true);
01108 }
01109 
01110 // Change a message's folder.
01111 //
01112 static void do_mail_file(dbref player, char *msglist, char *folder)
01113 {
01114     struct mail_selector ms;
01115     if (!parse_msglist(msglist, &ms, player))
01116     {
01117         return;
01118     }
01119     int foldernum;
01120     if ((foldernum = parse_folder(player, folder)) == -1)
01121     {
01122         notify(player, "MAIL: Invalid folder specification");
01123         return;
01124     }
01125     int i = 0, j = 0;
01126     int origfold = player_folder(player);
01127 
01128     MailList ml(player);
01129     struct mail *mp;
01130     for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
01131     {
01132         if (  All(ms)
01133            || (Folder(mp) == origfold))
01134         {
01135             i++;
01136             if (mail_match(mp, ms, i))
01137             {
01138                 j++;
01139 
01140                 // Clear the folder.
01141                 //
01142                 mp->read &= M_FMASK;
01143                 mp->read |= FolderBit(foldernum);
01144                 notify(player, tprintf("MAIL: Msg %d filed in folder %d", i, foldernum));
01145             }
01146         }
01147     }
01148 
01149     if (!j)
01150     {
01151         // Ran off the end of the list without finding anything.
01152         //
01153         notify(player, "MAIL: You don't have any matching messages!");
01154     }
01155 }
01156 
01157 // A mail alias can be any combination of upper-case letters, lower-case
01158 // letters, and digits. No leading digits. No symbols. No ANSI. Length is
01159 // limited to SIZEOF_MALIAS-1. Case is preserved.
01160 //
01161 static char *MakeCanonicalMailAlias
01162 (
01163     char *pMailAlias,
01164     int *pnValidMailAlias,
01165     bool *pbValidMailAlias
01166 )
01167 {
01168     static char Buffer[SIZEOF_MALIAS];
01169     size_t nLeft = sizeof(Buffer)-1;
01170     char *q = Buffer;
01171     char *p = pMailAlias;
01172 
01173     if (  !p
01174        || !mux_isalpha(*p))
01175     {
01176         *pnValidMailAlias = 0;
01177         *pbValidMailAlias = false;
01178         return NULL;
01179     }
01180     *q++ = *p++;
01181     nLeft--;
01182 
01183     while (  *p
01184           && nLeft)
01185     {
01186         if (  !mux_isalpha(*p)
01187            && !mux_isdigit(*p)
01188            && *p != '_')
01189         {
01190             break;
01191         }
01192         *q++ = *p++;
01193         nLeft--;
01194     }
01195     *q = '\0';
01196 
01197     *pnValidMailAlias = q - Buffer;
01198     *pbValidMailAlias = true;
01199     return Buffer;
01200 }
01201 
01202 #define GMA_NOTFOUND    1
01203 #define GMA_FOUND       2
01204 #define GMA_INVALIDFORM 3
01205 
01206 static struct malias *get_malias(dbref player, char *alias, int *pnResult)
01207 {
01208     *pnResult = GMA_INVALIDFORM;
01209     if (!alias)
01210     {
01211         return NULL;
01212     }
01213     if (alias[0] == '#')
01214     {
01215         if (ExpMail(player))
01216         {
01217             int x = mux_atol(alias + 1);
01218             if (x < 0 || x >= ma_top)
01219             {
01220                 *pnResult = GMA_NOTFOUND;
01221                 return NULL;
01222             }
01223             *pnResult = GMA_FOUND;
01224             return malias[x];
01225         }
01226     }
01227     else if (alias[0] == '*')
01228     {
01229         int  nValidMailAlias;
01230         bool bValidMailAlias;
01231         char *pValidMailAlias = MakeCanonicalMailAlias
01232                                 (   alias+1,
01233                                     &nValidMailAlias,
01234                                     &bValidMailAlias
01235                                 );
01236 
01237         if (bValidMailAlias)
01238         {
01239             for (int i = 0; i < ma_top; i++)
01240             {
01241                 struct malias *m = malias[i];
01242                 if (  m->owner == player
01243                    || m->owner == GOD
01244                    || ExpMail(player))
01245                 {
01246                     if (!strcmp(pValidMailAlias, m->name))
01247                     {
01248                         // Found it!
01249                         //
01250                         *pnResult = GMA_FOUND;
01251                         return m;
01252                     }
01253                 }
01254             }
01255             *pnResult = GMA_NOTFOUND;
01256         }
01257     }
01258     if (*pnResult == GMA_INVALIDFORM)
01259     {
01260