root/1.8.3/tags/p3rc1/src/extmail.c

Revision 911, 80.1 KB (checked in by shawnw, 19 months ago)

Removed nonstandard isascii(), and replaced some uses of isalpha()

isdigit() with isalnum()

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
101extern int do_convtime(const char *str, struct tm *ttm);        /* funtime.c */
102
103static void do_mail_flags
104  (dbref player, const char *msglist, mail_flag flag, bool negate);
105static char *mail_list_time(const char *the_time, bool flag);
106static MAIL *mail_fetch(dbref player, int num);
107static MAIL *real_mail_fetch(dbref player, int num, int folder);
108static MAIL *mailfun_fetch(dbref player, int nargs, char *arg1, char *arg2);
109static void count_mail(dbref player,
110                       int folder, int *rcount, int *ucount, int *ccount);
111static int real_send_mail(dbref player,
112                          dbref target, char *subject, char *message,
113                          mail_flag flags, int silent, int nosig);
114static void send_mail(dbref player,
115                      dbref target, char *subject, char *message,
116                      mail_flag flags, int silent, int nosig);
117static int send_mail_alias(dbref player,
118                           char *aname, char *subject,
119                           char *message, mail_flag flags, int silent,
120                           int nosig);
121static void filter_mail(dbref from, dbref player, char *subject,
122                        char *message, int mailnumber, mail_flag flags);
123static MAIL *find_insertion_point(dbref player);
124static int get_folder_number(dbref player, char *name);
125static char *get_folder_name(dbref player, int fld);
126static int player_folder(dbref player);
127static int parse_folder(dbref player, char *folder_string);
128static int mail_match(dbref player, MAIL *mp, struct mail_selector ms, int num);
129static int parse_msglist
130  (const char *msglist, struct mail_selector *ms, dbref player);
131static int parse_message_spec
132  (dbref player, const char *s, int *msglow, int *msghigh, int *folder);
133static char *status_chars(MAIL *mp);
134static char *status_string(MAIL *mp);
135static int sign(int x);
136static char *get_message(MAIL *mp);
137static unsigned char *get_compressed_message(MAIL *mp);
138static char *get_subject(MAIL *mp);
139static char *get_sender(MAIL *mp, int full);
140static int was_sender(dbref player, MAIL *mp);
141
142MAIL *maildb;            /**< The head of the mail list */
143MAIL *tail_ptr;          /**< The end of the mail list */
144
145#define HEAD  maildb     /**< The head of the mail list */
146#define TAIL  tail_ptr   /**< The end of the mail list */
147
148/** A line of...dashes! */
149#define DASH_LINE  \
150  "-----------------------------------------------------------------------------"
151
152int mdb_top = 0;                /**< total number of messages in mail db */
153
154/*-------------------------------------------------------------------------*
155 *   User mail functions (these are called from game.c)
156 *
157 * do_mail - cases without a /switch.
158 * do_mail_send - sending mail
159 * do_mail_read - read messages
160 * do_mail_list - list messages
161 * do_mail_flags - tagging, untagging, clearing, unclearing of messages
162 * do_mail_file - files messages into a new folder
163 * do_mail_fwd - forward messages to another player(s)
164 * do_mail_count - count messages
165 * do_mail_purge - purge cleared messages
166 * do_mail_change_folder - change current folder
167 * do_mail_unfolder - remove a folder name from MAILFOLDERS
168 * do_mail_subject - set the current mail subject
169 *-------------------------------------------------------------------------*/
170
171/* Return the uncompressed text of a @mail in a static buffer */
172static char *
173get_message(MAIL *mp)
174{
175  static char text[BUFFER_LEN * 2];
176  unsigned char tbuf[BUFFER_LEN * 2];
177
178  if (!mp)
179    return NULL;
180
181  chunk_fetch(mp->msgid, tbuf, sizeof tbuf);
182  strcpy(text, uncompress(tbuf));
183  return text;
184}
185
186/* Return the compressed text of a @mail in a static buffer */
187static unsigned char *
188get_compressed_message(MAIL *mp)
189{
190  static unsigned char text[BUFFER_LEN * 2];
191
192  if (!mp)
193    return NULL;
194
195  chunk_fetch(mp->msgid, (unsigned char *) text, sizeof text);
196  return text;
197}
198
199/* Return the subject of a mail message, or (no subject) */
200static char *
201get_subject(MAIL *mp)
202{
203  static char sbuf[SUBJECT_LEN + 1];
204  char *p;
205  if (mp->subject) {
206    strncpy(sbuf, uncompress(mp->subject), SUBJECT_LEN);
207    sbuf[SUBJECT_LEN] = '\0';
208    /* Stop at a return or a tab */
209    for (p = sbuf; *p; p++) {
210      if ((*p == '\r') || (*p == '\n') || (*p == '\t')) {
211        *p = '\0';
212        break;
213      }
214      if (!isprint((unsigned char) *p)) {
215        *p = ' ';
216      }
217    }
218  } else
219    strcpy(sbuf, T("(no subject)"));
220  return sbuf;
221}
222
223/* Return the name of the mail sender. */
224static char *
225get_sender(MAIL *mp, int full)
226{
227  static char tbuf1[BUFFER_LEN], *bp;
228  bp = tbuf1;
229  if (!GoodObject(mp->from))
230    safe_str("!Purged!", tbuf1, &bp);
231  else if (!was_sender(mp->from, mp))
232    safe_str("!Purged!", tbuf1, &bp);
233  else if (IsPlayer(mp->from) || !full)
234    safe_str(Name(mp->from), tbuf1, &bp);
235  else
236    safe_format(tbuf1, &bp, "%s (owner: %s)", Name(mp->from),
237                Name(Owner(mp->from)));
238  *bp = '\0';
239  return tbuf1;
240}
241
242/* Was this player the sender of this message? */
243static int
244was_sender(dbref player, MAIL *mp)
245{
246  /* If the dbrefs don't match, fail. */
247  if (mp->from != player)
248    return 0;
249  /* If we don't know the creation time of the sender, succeed. */
250  if (!mp->from_ctime)
251    return 1;
252  /* Succeed if and only if the creation times match. */
253  return (mp->from_ctime == CreTime(player));
254}
255
256/** Change folders or rename a folder.
257 * \verbatim
258 * This implements @mail/folder
259 * \endverbatim
260 * \param player the enactor.
261 * \param fld string containing folder number or name.
262 * \param newname string containing folder name, if renaming.
263 */
264void
265do_mail_change_folder(dbref player, char *fld, char *newname)
266{
267  int pfld;
268  char *p;
269
270  if (!fld || !*fld) {
271    /* Check mail in all folders */
272    for (pfld = MAX_FOLDERS; pfld >= 0; pfld--)
273      check_mail(player, pfld, 1);
274    pfld = player_folder(player);
275    notify_format(player,
276                  T("MAIL: Current folder is %d [%s]."), pfld,
277                  get_folder_name(player, pfld));
278    return;
279  }
280  pfld = parse_folder(player, fld);
281  if (pfld < 0) {
282    notify(player, T("MAIL: What folder is that?"));
283    return;
284  }
285  if (newname && *newname) {
286    /* We're changing a folder name here */
287    if (strlen(newname) > FOLDER_NAME_LEN) {
288      notify(player, T("MAIL: Folder name too long"));
289      return;
290    }
291    for (p = newname; p && *p; p++) {
292      if (!isalnum((unsigned char) *p)) {
293        notify(player, T("MAIL: Illegal folder name"));
294        return;
295      }
296    }
297    add_folder_name(player, pfld, newname);
298    notify_format(player, T("MAIL: Folder %d now named '%s'"), pfld, newname);
299  } else {
300    /* Set a new folder */
301    set_player_folder(player, pfld);
302    notify_format(player,
303                  T("MAIL: Current folder set to %d [%s]."), pfld,
304                  get_folder_name(player, pfld));
305  }
306}
307
308/** Remove a folder name.
309 * \verbatim
310 * This implements @mail/unfolder
311 * \endverbatim
312 * \param player the enactor.
313 * \param fld string containing folder number or name.
314 */
315void
316do_mail_unfolder(dbref player, char *fld)
317{
318  int pfld;
319
320  if (!fld || !*fld) {
321    notify(player, T("MAIL: You must specify a folder name or number"));
322    return;
323  }
324  pfld = parse_folder(player, fld);
325  if (pfld < 0) {
326    notify(player, T("MAIL: What folder is that?"));
327    return;
328  }
329  add_folder_name(player, pfld, NULL);
330  notify_format(player, T("MAIL: Folder %d now has no name"), pfld);
331}
332
333
334/** Tag a set of mail messages.
335 * \param player the enactor.
336 * \param msglist string specifying messages to tag.
337 */
338void
339do_mail_tag(dbref player, const char *msglist)
340{
341  do_mail_flags(player, msglist, M_TAG, 0);
342}
343
344/** Clear a set of mail messages.
345 * \param player the enactor.
346 * \param msglist string specifying messages to clear.
347 */
348void
349do_mail_clear(dbref player, const char *msglist)
350{
351  do_mail_flags(player, msglist, M_CLEARED, 0);
352}
353
354/** Untag a set of mail messages.
355 * \param player the enactor.
356 * \param msglist string specifying messages to untag.
357 */
358void
359do_mail_untag(dbref player, const char *msglist)
360{
361  do_mail_flags(player, msglist, M_TAG, 1);
362}
363
364/** Unclear a set of mail messages.
365 * \param player the enactor.
366 * \param msglist string specifying messages to unclear.
367 */
368void
369do_mail_unclear(dbref player, const char *msglist)
370{
371  do_mail_flags(player, msglist, M_CLEARED, 1);
372}
373
374
375/** Set or clear a flag on a set of messages.
376 * \param player the enactor.
377 * \param msglist string representing list of messages to operate on.
378 * \param flag flag to set or clear.
379 * \param negate if 1, clear the flag; if 0, set the flag.
380 */
381static void
382do_mail_flags(dbref player, const char *msglist, mail_flag flag, bool negate)
383{
384  MAIL *mp;
385  struct mail_selector ms;
386  int j;
387  mail_flag folder;
388  folder_array i;
389  int notified = 0;
390
391  if (!parse_msglist(msglist, &ms, player)) {
392    return;
393  }
394  FA_Init(i, j);
395  j = 0;
396  folder = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
397  for (mp = find_exact_starting_point(player);
398       mp && (mp->to == player); mp = mp->next) {
399    if ((mp->to == player) && (All(ms) || (Folder(mp) == folder))) {
400      i[Folder(mp)]++;
401      if (mail_match(player, mp, ms, i[Folder(mp)])) {
402        j++;
403        if (negate) {
404          mp->read &= ~flag;
405        } else {
406          mp->read |= flag;
407        }
408        switch (flag) {
409        case M_TAG:
410          if (All(ms)) {
411            if (!notified) {
412              notify_format(player,
413                            T("MAIL: All messages in all folders %s."),
414                            negate ? "untagged" : "tagged");
415              notified++;
416            }
417          } else
418            notify_format(player,
419                          "MAIL: Msg #%d:%d %s.", Folder(mp),
420                          i[Folder(mp)], negate ? "untagged" : "tagged");
421          break;
422        case M_CLEARED:
423          if (All(ms)) {
424            if (!notified) {
425              notify_format(player,
426                            T("MAIL: All messages in all folders %s."),
427                            negate ? "uncleared" : "cleared");
428              notified++;
429            }
430          } else {
431            if (Unread(mp) && !negate) {
432              notify_format(player,
433                            T
434                            ("MAIL: Unread Msg #%d:%d cleared! Use @mail/unclear %d:%d to recover."),
435                            Folder(mp), i[Folder(mp)], Folder(mp),
436                            i[Folder(mp)]);
437            } else {
438              notify_format(player,
439                            (negate ? T("MAIL: Msg #%d:%d uncleared.") :
440                             T("MAIL: Msg #%d:%d cleared.")), Folder(mp),
441                            i[Folder(mp)]);
442            }
443          }
444          break;
445        }
446      }
447    }
448  }
449  if (!j) {
450    /* ran off the end of the list without finding anything */
451    notify(player, T("MAIL: You don't have any matching messages!"));
452  }
453  return;
454}
455
456/** File messages into a folder.
457 * \verbatim
458 * This implements @mail/file.
459 * \endverbatim
460 * \param player the enactor.
461 * \param msglist list of messages to file.
462 * \param folder name or number of folder to put messages in.
463 */
464void
465do_mail_file(dbref player, char *msglist, char *folder)
466{
467  MAIL *mp;
468  struct mail_selector ms;
469  int j, foldernum;
470  mail_flag origfold;
471  folder_array i;
472  int notified = 0;
473
474  if (!parse_msglist(msglist, &ms, player)) {
475    return;
476  }
477  if ((foldernum = parse_folder(player, folder)) == -1) {
478    notify(player, T("MAIL: Invalid folder specification"));
479    return;
480  }
481  FA_Init(i, j);
482  j = 0;
483  origfold = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
484  for (mp = find_exact_starting_point(player);
485       mp && (mp->to == player); mp = mp->next) {
486    if ((mp->to == player) && (All(ms) || (Folder(mp) == origfold))) {
487      i[Folder(mp)]++;
488      if (mail_match(player, mp, ms, i[Folder(mp)])) {
489        j++;
490        mp->read &= M_FMASK;    /* Clear the folder */
491        mp->read &= ~M_CLEARED; /* Unclear it if it was marked cleared */
492        mp->read |= FolderBit(foldernum);
493        if (All(ms)) {
494          if (!notified) {
495            notify_format(player,
496                          T("MAIL: All messages filed in folder %d [%s]"),
497                          foldernum, get_folder_name(player, foldernum));
498            notified++;
499          }
500        } else
501          notify_format(player,
502                        T("MAIL: Msg %d:%d filed in folder %d [%s]"),
503                        origfold, i[origfold], foldernum,
504                        get_folder_name(player, foldernum));
505      }
506    }
507  }
508  if (!j) {
509    /* ran off the end of the list without finding anything */
510    notify(player, T("MAIL: You don't have any matching messages!"));
511  }
512  return;
513}
514
515/** Read mail messages.
516 * This displays the contents of a set of mail messages.
517 * \param player the enactor.
518 * \param msglist list of messages to read.
519 */
520void
521do_mail_read(dbref player, char *msglist)
522{
523  MAIL *mp;
524  char tbuf1[BUFFER_LEN];
525  char folderheader[BUFFER_LEN];
526  struct mail_selector ms;
527  int j;
528  mail_flag folder;
529  folder_array i;
530
531  if (!parse_msglist(msglist, &ms, player)) {
532    return;
533  }
534  folder = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
535  FA_Init(i, j);
536  j = 0;
537  for (mp = find_exact_starting_point(player);
538       mp && (mp->to == player); mp = mp->next) {
539    if ((mp->to == player) && (All(ms) || Folder(mp) == folder)) {
540      i[Folder(mp)]++;
541      if (mail_match(player, mp, ms, i[Folder(mp)])) {
542        /* Read it */
543        j++;
544        if (SUPPORT_PUEBLO) {
545          notify_noenter(player, open_tag("SAMP"));
546          snprintf(folderheader, BUFFER_LEN,
547                   "%c%cA XCH_HINT=\"List messages in this folder\" XCH_CMD=\"@mail/list %d:1-\"%c%s%c%c/A%c",
548                   TAG_START, MARKUP_HTML, Folder(mp), TAG_END, T("Folder:"),
549                   TAG_START, MARKUP_HTML, TAG_END);
550        } else
551          mush_strncpy(folderheader, T("Folder:"), BUFFER_LEN);
552        notify(player, DASH_LINE);
553        mush_strncpy(tbuf1, get_sender(mp, 1), BUFFER_LEN);
554        notify_format(player,
555                      T
556                      ("From: %-55s %s\nDate: %-25s   %s %2d   Message: %d\nStatus: %s"),
557                      tbuf1, ((*tbuf1 != '!') && IsPlayer(mp->from)
558                              && Connected(mp->from)
559                              && (!hidden(mp->from)
560                                  || Priv_Who(player))) ? " (Conn)" : "      ",
561                      show_time(mp->time, 0), folderheader, Folder(mp),
562                      i[Folder(mp)], status_string(mp));
563        notify_format(player, T("Subject: %s"), get_subject(mp));
564        notify(player, DASH_LINE);
565        if (SUPPORT_PUEBLO)
566          notify_noenter(player, close_tag("SAMP"));
567        strcpy(tbuf1, get_message(mp));
568        notify(player, tbuf1);
569        if (SUPPORT_PUEBLO)
570          notify(player, wrap_tag("SAMP", DASH_LINE));
571        else
572          notify(player, DASH_LINE);
573        if (Unread(mp))
574          mp->read |= M_MSGREAD;        /* mark message as read */
575      }
576    }
577  }
578  if (!j) {
579    /* ran off the end of the list without finding anything */
580    notify(player, T("MAIL: You don't have that many matching messages!"));
581  }
582  return;
583}
584
585
586/** List the flags, number, sender, subject, and date of messages in a
587 * concise format.
588 * \param player the enactor.
589 * \param msglist list of messages to list.
590 */
591void
592do_mail_list(dbref player, const char *msglist)
593{
594  char subj[30];
595  char sender[30];
596  MAIL *mp;
597  struct mail_selector ms;
598  int j;
599  mail_flag folder;
600  folder_array i;
601
602  if (!parse_msglist(msglist, &ms, player)) {
603    return;
604  }
605  FA_Init(i, j);
606  j = 0;
607  folder = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
608  if (SUPPORT_PUEBLO)
609    notify_noenter(player, open_tag("SAMP"));
610  notify_format(player,
611                T
612                ("---------------------------  MAIL (folder %2d)  ------------------------------"),
613                folder);
614  for (mp = find_exact_starting_point(player); mp && (mp->to == player);
615       mp = mp->next) {
616    if ((mp->to == player) && (All(ms) || Folder(mp) == folder)) {
617      i[Folder(mp)]++;
618      if (mail_match(player, mp, ms, i[Folder(mp)])) {
619        /* list it */
620        if (SUPPORT_PUEBLO)
621          notify_noenter(player,
622                         tprintf
623                         ("%c%cA XCH_CMD=\"@mail/read %d:%d\" XCH_HINT=\"Read message %d in folder %d\"%c",
624                          TAG_START, MARKUP_HTML, Folder(mp), i[Folder(mp)],
625                          i[Folder(mp)], Folder(mp), TAG_END));
626        strcpy(subj, chopstr(get_subject(mp), 28));
627        strcpy(sender, chopstr(get_sender(mp, 0), 12));
628        notify_format(player, "[%s] %2d:%-3d %c%-12s  %-*s %s",
629                      status_chars(mp), Folder(mp), i[Folder(mp)],
630                      ((*sender != '!') && (Connected(mp->from) &&
631                                            (!hidden(mp->from)
632                                             || Priv_Who(player)))
633                       ? '*' : ' '), sender, 30, subj,
634                      mail_list_time(show_time(mp->time, 0), 1));
635        if (SUPPORT_PUEBLO)
636          notify_noenter(player, tprintf("%c%c/A%c", TAG_START,
637                                         MARKUP_HTML, TAG_END));
638      }
639    }
640  }
641  notify(player, DASH_LINE);
642  if (SUPPORT_PUEBLO)
643    notify(player, close_tag("SAMP"));
644  return;
645}
646
647static char *
648mail_list_time(const char *the_time, bool flag /* 1 for no year */ )
649{
650  static char newtime[BUFFER_LEN];
651  const char *p;
652  char *q;
653  int i;
654  p = the_time;
655  q = newtime;
656  if (!p || !*p)
657    return NULL;
658  /* Format of the_time is: day mon dd hh:mm:ss yyyy */
659  /* Chop out :ss */
660  for (i = 0; i < 16; i++) {
661    if (*p)
662      *q++ = *p++;
663  }
664  if (!flag) {
665    for (i = 0; i < 3; i++) {
666      if (*p)
667        p++;
668    }
669    for (i = 0; i < 5; i++) {
670      if (*p)
671        *q++ = *p++;
672    }
673  }
674  *q = '\0';
675  return newtime;
676}
677
678
679/** Expunge mail that's marked for deletion.
680 * \verbatim
681 * This implements @mail/purge.
682 * \endverbatim
683 * \param player the enactor.
684 */
685void
686do_mail_purge(dbref player)
687{
688  MAIL *mp, *nextp;
689
690  /* Go through player's mail, and remove anything marked cleared */
691  for (mp = find_exact_starting_point(player);
692       mp && (mp->to == player); mp = nextp) {
693    if ((mp->to == player) && Cleared(mp)) {
694      /* Delete this one */
695      /* head and tail of the list are special */
696      if (mp == HEAD)
697        HEAD = mp->next;
698      else if (mp == TAIL)
699        TAIL = mp->prev;
700      /* relink the list */
701      if (mp->prev != NULL)
702        mp->prev->next = mp->next;
703      if (mp->next != NULL)
704        mp->next->prev = mp->prev;
705      /* save the pointer */
706      nextp = mp->next;
707      /* then wipe */
708      mdb_top--;
709      free(mp->subject);
710      chunk_delete(mp->msgid);
711      mush_free((Malloc_t) mp, "mail");
712    } else {
713      nextp = mp->next;
714    }
715  }
716  /* Clean up the player's mailp */
717  if (Connected(player)) {
718    desc_mail_set(player, NULL);
719    desc_mail_set(player, find_exact_starting_point(player));
720  }
721  if (command_check_byname(player, "@MAIL"))
722    notify(player, T("MAIL: Mailbox purged."));
723  return;
724}
725
726/** Forward mail messages to someone(s) else.
727 * \verbatim
728 * This implements @mail/forward.
729 * \endverbatim
730 * \param player the enactor.
731 * \param msglist list of messages to forward.
732 * \param tolist list of recipients to forwared to.
733 */
734void
735do_mail_fwd(dbref player, char *msglist, char *tolist)
736{
737  MAIL *mp;
738  MAIL *last;
739  struct mail_selector ms;
740  int j, num;
741  mail_flag folder;
742  folder_array i;
743  const char *head;
744  MAIL *temp;
745  dbref target;
746  int num_recpts = 0;
747  const char **start;
748  char *current;
749  start = &head;
750
751  if (!parse_msglist(msglist, &ms, player)) {
752    return;
753  }
754  if (!tolist || !*tolist) {
755    notify(player, T("MAIL: To whom should I forward?"));
756    return;
757  }
758  folder = AllInFolder(ms) ? player_folder(player) : MSFolder(ms);
759  /* Mark the player's last message. This prevents a loop if
760   * the forwarding command happens to forward a message back
761   * to the player itself
762   */
763  last = mp = find_exact_starting_point(player);
764  if (!last) {
765    notify(player, T("MAIL: You have no messages to forward."));
766    return;
767  }
768  while (last->next && (last->next->to == player))
769    last = last->next;
770