PennMUSH Community

root/1.8.3/trunk/src/extmail.c

Revision 1167, 79.8 kB (checked in by shawnw, 8 months ago)

Merge devel into trunk for p6 release

Line 
1 /**
2  * \file extmail.c
3  *
4  * \brief The PennMUSH built-in mail system.
5  *
6  * \verbatim
7  *---------------------------------------------------------------
8  * extmail.c - Javelin's improved @mail system
9  * Based on Amberyl's linked list mailer
10  *
11  * Summary of mail command syntax:
12  * Sending:
13  *   @mail[/sendswitch] player-list = message
14  *     sendswitches: /silent, /urgent
15  *     player-list is a space-separated list of players, aliases, or msg#'s
16  *     to reply to. Players can be names or dbrefs. Aliases start with *
17  * Reading/Handling:
18  *   @mail[/readswitch] [msg-list [= target]]
19  *     With no readswitch, @mail reads msg-list (same as /read)
20  *     With no readswitch and no msg-list, @mail lists all messages (/list)
21  *     readswitches: /list, /read, /fwd (requires target list of players
22  *      to forward messages to), /file (requires target folder to file
23  *      to), /tag, /untag, /clear, /unclear, /purge (no msg-list),
24  *      /count
25  *     Assumes messages in current folder, set by @folder or @mail/folder
26  *     msg-list can be one of: a message number, a message range,
27  *     (2-3, 4-, -6), sender references (*player), date comparisons
28  *     (~0, >2, <5), or the strings "urgent", "tagged", "cleared",
29  *     "read", "unread", "all", or "folder"
30  *     You can also use 1:2 (folder 1, message 2) and 1:2-3 for ranges.
31  * Admin stuff:
32  *   @mail[/switch] [player]
33  *     Switches include: nuke (used to be "purge"), [efd]stats, debug
34  *
35  * THEORY OF OPERATION:
36  *  Prior to pl11, mail was an unsorted linked list. When mail was sent,
37  * it was added onto the end. To read mail, you scanned the whole list.
38  * This is still how origmail.c works.
39  *  As of pl11, extmail.c maintains mail as a sorted linked list, sorted
40  * by recipient and order of receipt. This makes sending mail less
41  * efficient (because you have to scan the list to figure out where to
42  * insert), but reading/checking/deleting more efficient,
43  * because once you've found where the player's mail starts, you just
44  * read from there.
45  *  That wouldn't be so exciting unless there was a fast way to find
46  * where a player's mail chain started. Fortunately, there is. We
47  * record that information for connected players when they connect,
48  * on their descriptor. So, when connected players do reading/etc,
49  * it's O(1). Sending to a connected player is O(1). Sending to an
50  * unconnected player still requires scanning (O(n)), but you send once,
51  * and read/list/delete etc, multiple times.
52  *  And just to make the sending to disconnected players faster,
53  * instead of scanning the whole maildb to find the insertion point,
54  * we start the scan from the chain of the connected player with the
55  * closest db# to the target player. This scales up very well.
56  *--------------------------------------------------------------------
57  * \endverbatim
58  */
59
60 #include "config.h"
61 #include "copyrite.h"
62
63 #ifdef I_SYS_TIME
64 #include <sys/time.h>
65 #ifdef TIME_WITH_SYS_TIME
66 #include <time.h>
67 #endif
68 #else
69 #include <time.h>
70 #endif
71 #include <ctype.h>
72 #ifdef I_SYS_TYPES
73 #include <sys/types.h>
74 #endif
75 #include <string.h>
76 #ifdef HAVE_STDINT_H
77 #include <stdint.h>
78 #endif
79
80 #include "conf.h"
81 #include "externs.h"
82 #include "mushdb.h"
83 #include "dbdefs.h"
84 #include "match.h"
85 #include "extmail.h"
86 #include "function.h"
87 #include "malias.h"
88 #include "attrib.h"
89 #include "parse.h"
90 #include "mymalloc.h"
91 #include "ansi.h"
92 #include "pueblo.h"
93 #include "flags.h"
94 #include "log.h"
95 #include "lock.h"
96 #include "command.h"
97 #include "dbio.h"
98 #include "confmagic.h"
99
100
101 extern int do_convtime(const char *str, struct tm *ttm);        /* funtime.c */
102
103 static void do_mail_flags
104   (dbref player, const char *msglist, mail_flag flag, bool negate);
105 static char *mail_list_time(const char *the_time, bool flag);
106 static MAIL *mail_fetch(dbref player, int num);
107 static MAIL *real_mail_fetch(dbref player, int num, int folder);
108 static MAIL *mailfun_fetch(dbref player, int nargs, char *arg1, char *arg2);
109 static void count_mail(dbref player,
110                        int folder, int *rcount, int *ucount, int *ccount);
111 static int real_send_mail(dbref player,
112                           dbref target, char *subject, char *message,
113                           mail_flag flags, int silent, int nosig);
114 static void send_mail(dbref player,
115                       dbref target, char *subject, char *message,
116                       mail_flag flags, int silent, int nosig);
117 static int send_mail_alias(dbref player,
118                            char *aname, char *subject,
119                            char *message, mail_flag flags, int silent,
120                            int nosig);
121 static void filter_mail(dbref from, dbref player, char *subject,
122                         char *message, int mailnumber, mail_flag flags);
123 static MAIL *find_insertion_point(dbref player);
124 static int get_folder_number(dbref player, char *name);
125 static char *get_folder_name(dbref player, int fld);
126 static int player_folder(dbref player);
127 static int parse_folder(dbref player, char *folder_string);
128 static int mail_match(dbref player, MAIL *mp, struct mail_selector ms, int num);
129 static int parse_msglist
130   (const char *msglist, struct mail_selector *ms, dbref player);
131 static int parse_message_spec
132   (dbref player, const char *s, int *msglow, int *msghigh, int *folder);
133 static char *status_chars(MAIL *mp);
134 static char *status_string(MAIL *mp);
135 static int sign(int x);
136 static char *get_message(MAIL *mp);
137 static unsigned char *get_compressed_message(MAIL *mp);
138 static char *get_subject(MAIL *mp);
139 static char *get_sender(MAIL *mp, int full);
140 static int was_sender(dbref player, MAIL *mp);
141
142 MAIL *maildb;            /**< The head of the mail list */
143 MAIL *tail_ptr;          /**< The end of the mail list */
144
145 slab *mail_slab; /**< slab for 'struct mail' allocations */
146
147 #define HEAD  maildb     /**< The head of the mail list */
148 #define TAIL  tail_ptr   /**< The end of the mail list */
149
150 /** A line of...dashes! */
151 #define DASH_LINE  \
152   "-----------------------------------------------------------------------------"
153
154 int mdb_top = 0;                /**< total number of messages in mail db */
155
156 /*-------------------------------------------------------------------------*
157  *   User mail functions (these are called from game.c)
158  *
159  * do_mail - cases without a /switch.
160  * do_mail_send - sending mail
161  * do_mail_read - read messages
162  * do_mail_list - list messages
163  * do_mail_flags - tagging, untagging, clearing, unclearing of messages
164  * do_mail_file - files messages into a new folder
165  * do_mail_fwd - forward messages to another player(s)
166  * do_mail_count - count messages
167  * do_mail_purge - purge cleared messages
168  * do_mail_change_folder - change current folder
169  * do_mail_unfolder - remove a folder name from MAILFOLDERS
170  * do_mail_subject - set the current mail subject
171  *-------------------------------------------------------------------------*/
172
173 /* Return the uncompressed text of a @mail in a static buffer */
174 static char *
175 get_message(MAIL *mp)
176 {
177   static char text[BUFFER_LEN * 2];
178   unsigned char tbuf[BUFFER_LEN * 2];
179
180   if (!mp)
181     return NULL;
182
183   chunk_fetch(mp->msgid, tbuf, sizeof tbuf);
184   strcpy(text, uncompress(tbuf));
185   return text;
186 }
187
188 /* Return the compressed text of a @mail in a static buffer */
189 static unsigned char *
190 get_compressed_message(MAIL *mp)
191 {
192   static unsigned char text[BUFFER_LEN * 2];
193
194   if (!mp)
195     return NULL;
196
197   chunk_fetch(mp->msgid, (unsigned char *) text, sizeof text);
198   return text;
199 }
200
201 /* Return the subject of a mail message, or (no subject) */
202 static char *
203 get_subject(MAIL *mp)
204 {
205   static char sbuf[SUBJECT_LEN + 1];
206   char *p;
207   if (mp->subject) {
208     strncpy(sbuf, uncompress(mp->subject), SUBJECT_LEN);
209     sbuf[SUBJECT_LEN] = '\0';
210     /* Stop at a return or a tab */
211     for (p = sbuf; *p; p++) {
212       if ((*p == '\r') || (*p == '\n') || (*p == '\t')) {
213         *p = '\0';
214         break;
215       }
216       if (!isprint((unsigned char) *p)) {
217         *p = ' ';
218       }
219     }
220   } else
221     strcpy(sbuf, T("(no subject)"));
222   return sbuf;
223 }
224
225 /* Return the name of the mail sender. */
226 static char *
227 get_sender(MAIL *mp, int full)
228 {
229   static char tbuf1[BUFFER_LEN], *bp;
230   bp = tbuf1;
231   if (!GoodObject(mp->from))
232     safe_str("!Purged!", tbuf1, &bp);
233   else if (!was_sender(mp->from, mp))
234     safe_str("!Purged!", tbuf1, &bp);
235   else if (IsPlayer(mp->from) || !full)
236     safe_str(Name(mp->from), tbuf1, &bp);
237   else
238     safe_format(tbuf1, &bp, "%s (owner: %s)", Name(mp->from),
239                 Name(Owner(mp->from)));
240   *bp = '\0';
241   return tbuf1;
242 }
243
244 /* Was this player the sender of this message? */
245 static int
246 was_sender(dbref player, MAIL *mp)
247 {
248   /* If the dbrefs don't match, fail. */
249   if (mp->from != player)
250     return 0;
251   /* If we don't know the creation time of the sender, succeed. */
252   if (!mp->from_ctime)
253     return 1;
254   /* Succeed if and only if the creation times match. */
255   return (mp->from_ctime == CreTime(player));
256 }
257
258 /** Change folders or rename a folder.
259  * \verbatim
260  * This implements @mail/folder
261  * \endverbatim
262  * \param player the enactor.
263  * \param fld string containing folder number or name.
264  * \param newname string containing folder name, if renaming.
265  */
266 void
267 do_mail_change_folder(dbref player, char *fld, char *newname)
268 {
269   int pfld;
270   char *p;
271
272   if (!fld || !*fld) {
273     /* Check mail in all folders */
274     for (pfld = MAX_FOLDERS; pfld >= 0; pfld--)
275       check_mail(player, pfld, 1);
276     pfld = player_folder(player);
277     notify_format(player,
278                   T("MAIL: Current folder is %d [%s]."), pfld,
279                   get_folder_name(player, pfld));
280     return;
281   }
282   pfld = parse_folder(player, fld);
283   if (pfld < 0) {
284     notify(player, T("MAIL: What folder is that?"));
285     return;
286   }
287   if (newname && *newname) {
288     /* We're changing a folder name here */
289     if (strlen(newname) > FOLDER_NAME_LEN) {
290       notify(player, T("MAIL: Folder name too long"));
291       return;
292     }
293     for (p = newname; p && *p; p++) {
294       if (!isalnum((unsigned char) *p)) {
295         notify(player, T("MAIL: Illegal folder name"));
296         return;
297       }
298     }
299     add_folder_name(player, pfld, newname);
300     notify_format(player, T("MAIL: Folder %d now named '%s'"), pfld, newname);
301   } else {
302     /* Set a new folder */
303     set_player_folder(player, pfld);
304     notify_format(player,
305                   T("MAIL: Current folder set to %d [%s]."), pfld,
306                   get_folder_name(player, pfld));
307   }
308 }
309
310 /** Remove a folder name.
311  * \verbatim
312  * This implements @mail/unfolder
313  * \endverbatim
314  * \param player the enactor.
315  * \param fld string containing folder number or name.
316  */
317 void
318 do_mail_unfolder(dbref player, char *fld)
319 {
320   int pfld;
321
322   if (!fld || !*fld) {
323     notify(player, T("MAIL: You must specify a folder name or number"));
324     return;
325   }
326   pfld = parse_folder(player, fld);
327   if (pfld < 0) {
328     notify(player, T("MAIL: What folder is that?"));
329     return;
330   }
331   add_folder_name(player, pfld, NULL);
332   notify_format(player, T("MAIL: Folder %d now has no name"), pfld);
333 }
334
335
336 /** Tag a set of mail messages.
337  * \param player the enactor.
338  * \param msglist string specifying messages to tag.
339  */
340 void
341 do_mail_tag(dbref player, const char *msglist)
342 {
343   do_mail_flags(player, msglist, M_TAG, 0);
344 }
345
346 /** Clear a set of mail messages.
347  * \param player the enactor.
348  * \param msglist string specifying messages to clear.
349  */
350 void
351 do_mail_clear(dbref player, const char *msglist)
352 {
353   do_mail_flags(player, msglist, M_CLEARED, 0);
354 }
355
356 /** Untag a set of mail messages.
357  * \param player the enactor.
358  * \param msglist string specifying messages to untag.
359  */
360 void
361 do_mail_untag(dbref player, const char *msglist)
362 {
363   do_mail_flags(player, msglist, M_TAG, 1);
364 }
365
366 /** Unclear a set of mail messages.
367  * \param player the enactor.
368  * \param msglist string specifying messages to unclear.
369  */
370 void
371 do_mail_unclear(dbref player, const char *msglist)
372 {
373   do_mail_flags(player, msglist, M_CLEARED, 1);
374 }
375
376
377 /** Set or clear a flag on a set of messages.
378  * \param player the enactor.
379  * \param msglist string representing list of messages to operate on.
380  * \param flag flag to set or clear.
381  * \param negate if 1, clear the flag; if 0, set the flag.
382  */
383 static void
384 do_mail_flags(dbref player, const char *msglist, mail_flag flag, bool negate)
385 {
386   MAIL *mp;
387   struct mail_selector ms;
388   int j;
389   mail_flag folder;
390   folder_array i;
391   int notified = 0;
392
393   if (!parse_msglist(msglist, &ms, player)) {
394     return;
395   }
396   FA_Init(i, j);
397   j = 0;
398   folder = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
399   for (mp = find_exact_starting_point(player);
400        mp && (mp->to == player); mp = mp->next) {
401     if ((mp->to == player) && (All(ms) || (Folder(mp) == folder))) {
402       i[Folder(mp)]++;
403       if (mail_match(player, mp, ms, i[Folder(mp)])) {
404         j++;
405         if (negate) {
406           mp->read &= ~flag;
407         } else {
408           mp->read |= flag;
409         }
410         switch (flag) {
411         case M_TAG:
412           if (All(ms)) {
413             if (!notified) {
414               notify_format(player,
415                             T("MAIL: All messages in all folders %s."),
416                             negate ? "untagged" : "tagged");
417               notified++;
418             }
419           } else
420             notify_format(player,
421                           "MAIL: Msg #%d:%d %s.", Folder(mp),
422                           i[Folder(mp)], negate ? "untagged" : "tagged");
423           break;
424         case M_CLEARED:
425           if (All(ms)) {
426             if (!notified) {
427               notify_format(player,
428                             T("MAIL: All messages in all folders %s."),
429                             negate ? "uncleared" : "cleared");
430               notified++;
431             }
432           } else {
433             if (Unread(mp) && !negate) {
434               notify_format(player,
435                             T
436                             ("MAIL: Unread Msg #%d:%d cleared! Use @mail/unclear %d:%d to recover."),
437                             Folder(mp), i[Folder(mp)], Folder(mp),
438                             i[Folder(mp)]);
439             } else {
440               notify_format(player,
441                             (negate ? T("MAIL: Msg #%d:%d uncleared.") :
442                              T("MAIL: Msg #%d:%d cleared.")), Folder(mp),
443                             i[Folder(mp)]);
444             }
445           }
446           break;
447         }
448       }
449     }
450   }
451   if (!j) {
452     /* ran off the end of the list without finding anything */
453     notify(player, T("MAIL: You don't have any matching messages!"));
454   }
455   return;
456 }
457
458 /** File messages into a folder.
459  * \verbatim
460  * This implements @mail/file.
461  * \endverbatim
462  * \param player the enactor.
463  * \param msglist list of messages to file.
464  * \param folder name or number of folder to put messages in.
465  */
466 void
467 do_mail_file(dbref player, char *msglist, char *folder)
468 {
469   MAIL *mp;
470   struct mail_selector ms;
471   int j, foldernum;
472   mail_flag origfold;
473   folder_array i;
474   int notified = 0;
475
476   if (!parse_msglist(msglist, &ms, player)) {
477     return;
478   }
479   if ((foldernum = parse_folder(player, folder)) == -1) {
480     notify(player, T("MAIL: Invalid folder specification"));
481     return;
482   }
483   FA_Init(i, j);
484   j = 0;
485   origfold = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
486   for (mp = find_exact_starting_point(player);
487        mp && (mp->to == player); mp = mp->next) {
488     if ((mp->to == player) && (All(ms) || (Folder(mp) == origfold))) {
489       i[Folder(mp)]++;
490       if (mail_match(player, mp, ms, i[Folder(mp)])) {
491         j++;
492         mp->read &= M_FMASK;    /* Clear the folder */
493         mp->read &= ~M_CLEARED; /* Unclear it if it was marked cleared */
494         mp->read |= FolderBit(foldernum);
495         if (All(ms)) {
496           if (!notified) {
497             notify_format(player,
498                           T("MAIL: All messages filed in folder %d [%s]"),
499                           foldernum, get_folder_name(player, foldernum));
500             notified++;
501           }
502         } else
503           notify_format(player,
504                         T("MAIL: Msg %d:%d filed in folder %d [%s]"),
505                         origfold, i[origfold], foldernum,
506                         get_folder_name(player, foldernum));
507       }
508     }
509   }
510   if (!j) {
511     /* ran off the end of the list without finding anything */
512     notify(player, T("MAIL: You don't have any matching messages!"));
513   }
514   return;
515 }
516
517 /** Read mail messages.
518  * This displays the contents of a set of mail messages.
519  * \param player the enactor.
520  * \param msglist list of messages to read.
521  */
522 void
523 do_mail_read(dbref player, char *msglist)
524 {
525   MAIL *mp;
526   char tbuf1[BUFFER_LEN];
527   char folderheader[BUFFER_LEN];
528   struct mail_selector ms;
529   int j;
530   mail_flag folder;
531   folder_array i;
532
533   if (!parse_msglist(msglist, &ms, player)) {
534     return;
535   }
536   folder = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
537   FA_Init(i, j);
538   j = 0;
539   for (mp = find_exact_s