]> code.citadel.org Git - citadel.git/blobdiff - citadel/msgbase.c
removed all references to sprintf from several files (not all files yet)
[citadel.git] / citadel / msgbase.c
index 11923c4f7e813a5b7919305d435363a7f80fcebe..0cd11884316994fa95a8731c3e023b80d8642d61 100644 (file)
@@ -26,6 +26,7 @@
 # endif
 #endif
 
+
 #include <ctype.h>
 #include <string.h>
 #include <syslog.h>
 extern struct config config;
 long config_msgnum;
 
+
+/* 
+ * This really belongs in serv_network.c, but I don't know how to export
+ * symbols between modules.
+ */
+struct FilterList *filterlist = NULL;
+
+
+/*
+ * These are the four-character field headers we use when outputting
+ * messages in Citadel format (as opposed to RFC822 format).
+ */
 char *msgkeys[] = {
-       "", "", "", "", "", "", "", ""
-       "", "", "", "", "", "", "", ""
-       "", "", "", "", "", "", "", ""
-       "", "", "", "", "", "", "", ""
-       "", "", "", "", "", "", "", ""
-       "", "", "", "", "", "", "", ""
-       "", "", "", "", "", "", "", ""
-       "", "", "", "", "", "", "", ""
-       ""
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+       NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+       NULL
        "from",
-       "", "", "",
+       NULL, NULL, NULL,
        "exti",
        "rfca",
-       ""
+       NULL
        "hnod",
        "msgn",
-       "", "", "",
+       NULL, NULL, NULL,
        "text",
        "node",
        "room",
        "path",
-       "",
+       NULL,
        "rcpt",
        "spec",
        "time",
        "subj",
-       "",
-       "",
-       "",
-       "",
-       ""
+       NULL,
+       NULL,
+       NULL,
+       NULL,
+       NULL
 };
 
 /*
@@ -130,6 +143,7 @@ int alias(char *name)
        char testnode[SIZ];
        char buf[SIZ];
 
+       striplt(name);
        remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
 
        fp = fopen("network/mail.aliases", "r");
@@ -156,6 +170,12 @@ int alias(char *name)
                        strcpy(name, bbb);
        }
        fclose(fp);
+
+       /* Hit the Global Address Book */
+       if (CtdlDirectoryLookup(aaa, name) == 0) {
+               strcpy(name, aaa);
+       }
+
        lprintf(7, "Mail is being forwarded to %s\n", name);
 
        /* Change "user @ xxx" to "user" if xxx is an alias for this host */
@@ -169,14 +189,12 @@ int alias(char *name)
        }
 
        /* determine local or remote type, see citadel.h */
-
        at = haschar(name, '@');
        if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
        if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
        remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
 
        /* figure out the delivery mode */
-
        extract_token(node, name, 1, '@');
 
        /* If there are one or more dots in the nodename, we assume that it
@@ -972,6 +990,7 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
        char display_name[SIZ];
        char *mptr;
        char *nl;       /* newline string */
+       int suppress_f = 0;
 
        /* buffers needed for RFC822 translation */
        char suser[SIZ];
@@ -1037,7 +1056,7 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
        }
 
        /* nhdr=yes means that we're only displaying headers, no body */
-       if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
+       if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
                if (do_proto) cprintf("nhdr=yes\n");
        }
 
@@ -1049,30 +1068,49 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
                if (TheMessage->cm_fields['A']) {
                        strcpy(buf, TheMessage->cm_fields['A']);
                        PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
-                       if (TheMessage->cm_anon_type == MES_ANON)
+                       if (TheMessage->cm_anon_type == MES_ANONONLY) {
                                strcpy(display_name, "****");
-                       else if (TheMessage->cm_anon_type == MES_AN2)
+                       }
+                       else if (TheMessage->cm_anon_type == MES_ANONOPT) {
                                strcpy(display_name, "anonymous");
-                       else
+                       }
+                       else {
                                strcpy(display_name, buf);
+                       }
                        if ((is_room_aide())
-                           && ((TheMessage->cm_anon_type == MES_ANON)
-                            || (TheMessage->cm_anon_type == MES_AN2))) {
+                           && ((TheMessage->cm_anon_type == MES_ANONONLY)
+                            || (TheMessage->cm_anon_type == MES_ANONOPT))) {
                                sprintf(&display_name[strlen(display_name)],
                                        " [%s]", buf);
                        }
                }
 
+               /* Don't show Internet address for users on the
+                * local Citadel network.
+                */
+               suppress_f = 0;
+               if (TheMessage->cm_fields['N'] != NULL)
+                  if (strlen(TheMessage->cm_fields['N']) > 0)
+                     if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
+                       suppress_f = 1;
+               }
+               
+               /* Now spew the header fields in the order we like them. */
                strcpy(allkeys, FORDER);
                for (i=0; i<strlen(allkeys); ++i) {
                        k = (int) allkeys[i];
                        if (k != 'M') {
-                               if (TheMessage->cm_fields[k] != NULL) {
+                               if ( (TheMessage->cm_fields[k] != NULL)
+                                  && (msgkeys[k] != NULL) ) {
                                        if (k == 'A') {
                                                if (do_proto) cprintf("%s=%s\n",
                                                        msgkeys[k],
                                                        display_name);
                                        }
+                                       else if ((k == 'F') && (suppress_f)) {
+                                               /* do nothing */
+                                       }
+                                       /* Masquerade display name if needed */
                                        else {
                                                if (do_proto) cprintf("%s=%s\n",
                                                        msgkeys[k],
@@ -1114,18 +1152,20 @@ int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
                                else if (i == 'U')
                                        cprintf("Subject: %s%s", mptr, nl);
                                else if (i == 'I')
-                                       strcpy(mid, mptr);
+                                       safestrncpy(mid, mptr, sizeof mid);
                                else if (i == 'H')
-                                       strcpy(lnode, mptr);
+                                       safestrncpy(lnode, mptr, sizeof lnode);
+                               else if (i == 'F')
+                                       safestrncpy(fuser, mptr, sizeof fuser);
                                else if (i == 'O')
                                        cprintf("X-Citadel-Room: %s%s",
                                                mptr, nl);
                                else if (i == 'N')
-                                       strcpy(snode, mptr);
+                                       safestrncpy(snode, mptr, sizeof snode);
                                else if (i == 'R')
                                        cprintf("To: %s%s", mptr, nl);
                                else if (i == 'T') {
-                                       datestring(datestamp, atol(mptr),
+                                       datestring(datestamp, sizeof datestamp, atol(mptr),
                                                DATESTRING_RFC822 );
                                        cprintf("Date: %s%s", datestamp, nl);
                                }
@@ -1487,7 +1527,7 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
  * Message base operation to send a message to the master file
  * (returns new message number)
  *
- * This is the back end for CtdlSaveMsg() and should not be directly
+ * This is the back end for CtdlSubmitMsg() and should not be directly
  * called by server-side modules.
  *
  */
@@ -1661,14 +1701,13 @@ int ReplicationChecks(struct CtdlMessage *msg) {
 
 
 /*
- * Save a message to disk
+ * Save a message to disk and submit it into the delivery system.
  */
-long CtdlSaveMsg(struct CtdlMessage *msg,      /* message to save */
-               char *rec,                      /* Recipient (mail) */
-               char *force,                    /* force a particular room? */
-               int supplied_mailtype)          /* local or remote type */
-{
-       char aaa[100];
+long CtdlSubmitMsg(struct CtdlMessage *msg,    /* message to save */
+               struct recptypes *recps,        /* recipients (if mail) */
+               char *force                     /* force a particular room? */
+) {
+       char aaa[SIZ];
        char hold_rm[ROOMNAMELEN];
        char actual_rm[ROOMNAMELEN];
        char force_room[ROOMNAMELEN];
@@ -1677,17 +1716,17 @@ long CtdlSaveMsg(struct CtdlMessage *msg,       /* message to save */
        long newmsgid;
        char *mptr = NULL;
        struct usersupp userbuf;
-       int a;
+       int a, i;
        struct MetaData smi;
        FILE *network_fp = NULL;
        static int seqnum = 1;
-       struct CtdlMessage *imsg;
+       struct CtdlMessage *imsg = NULL;
        char *instr;
-       int mailtype;
+       struct ser_ret smr;
+       char *hold_R, *hold_D;
 
-       lprintf(9, "CtdlSaveMsg() called\n");
+       lprintf(9, "CtdlSubmitMsg() called\n");
        if (is_valid_message(msg) == 0) return(-1);     /* self check */
-       mailtype = supplied_mailtype;
 
        /* If this message has no timestamp, we take the liberty of
         * giving it one, right now.
@@ -1717,27 +1756,6 @@ long CtdlSaveMsg(struct CtdlMessage *msg,        /* message to save */
 
        strcpy(force_room, force);
 
-       /* Strip non-printable characters out of the recipient name */
-       lprintf(9, "Checking recipient (if present)\n");
-       strcpy(recipient, rec);
-       for (a = 0; a < strlen(recipient); ++a)
-               if (!isprint(recipient[a]))
-                       strcpy(&recipient[a], &recipient[a + 1]);
-
-       /* Change "user @ xxx" to "user" if xxx is an alias for this host */
-       for (a=0; a<strlen(recipient); ++a) {
-               if (recipient[a] == '@') {
-                       if (CtdlHostAlias(&recipient[a+1]) 
-                          == hostalias_localhost) {
-                               recipient[a] = 0;
-                               lprintf(7, "Changed to <%s>\n", recipient);
-                               mailtype = MES_LOCAL;
-                       }
-               }
-       }
-
-       lprintf(9, "Recipient is <%s>, mailtype is %d\n", recipient, mailtype);
-
        /* Learn about what's inside, because it's what's inside that counts */
        lprintf(9, "Learning what's inside\n");
        if (msg->cm_fields['M'] == NULL) {
@@ -1777,7 +1795,7 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
        lprintf(9, "Switching rooms\n");
        strcpy(hold_rm, CC->quickroom.QRname);
        strcpy(actual_rm, CC->quickroom.QRname);
-       if (strlen(recipient) > 0) {
+       if (recps != NULL) {
                strcpy(actual_rm, SENTITEMS);
        }
 
@@ -1815,25 +1833,9 @@ long CtdlSaveMsg(struct CtdlMessage *msg,        /* message to save */
        lprintf(9, "Performing replication checks\n");
        if (ReplicationChecks(msg) > 0) return(-1);
 
-       /* Network mail - send a copy to the network program. */
-       if ((strlen(recipient) > 0) && (mailtype == MES_IGNET)) {
-               lprintf(9, "Sending network spool\n");
-               sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
-                       (long) getpid(), CC->cs_pid, ++seqnum);
-               lprintf(9, "Saving a copy to %s\n", aaa);
-               network_fp = fopen(aaa, "ab+");
-               if (network_fp == NULL)
-                       lprintf(2, "ERROR: %s\n", strerror(errno));
-       }
-
        /* Save it to disk */
        lprintf(9, "Saving to disk\n");
-       newmsgid = send_message(msg, network_fp);
-       if (network_fp != NULL) {
-               fclose(network_fp);
-               /* FIXME start a network run here */
-       }
-
+       newmsgid = send_message(msg, NULL);
        if (newmsgid <= 0L) return(-1);
 
        /* Write a supplemental message info record.  This doesn't have to
@@ -1854,7 +1856,7 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
         * message, we want to BYPASS saving the sender's copy (because there
         * is no local sender; it would otherwise go to the Trashcan).
         */
-       if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
+       if ((!CC->internal_pgm) || (recps == NULL)) {
                if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
                        lprintf(3, "ERROR saving message pointer!\n");
                        CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
@@ -1862,10 +1864,20 @@ long CtdlSaveMsg(struct CtdlMessage *msg,       /* message to save */
        }
 
        /* For internet mail, drop a copy in the outbound queue room */
-       if (mailtype == MES_INTERNET) {
+       if (recps != NULL)
+        if (recps->num_internet > 0) {
                CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
        }
 
+       /* If other rooms are specified, drop them there too. */
+       if (recps != NULL)
+        if (recps->num_room > 0)
+         for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
+               extract(recipient, recps->recp_room, i);
+               lprintf(9, "Delivering to local room <%s>\n", recipient);
+               CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
+       }
+
        /* Bump this user's messages posted counter. */
        lprintf(9, "Updating user\n");
        lgetuser(&CC->usersupp, CC->curr_user);
@@ -1875,15 +1887,18 @@ long CtdlSaveMsg(struct CtdlMessage *msg,       /* message to save */
        /* If this is private, local mail, make a copy in the
         * recipient's mailbox and bump the reference count.
         */
-       if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
+       if (recps != NULL)
+        if (recps->num_local > 0)
+         for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
+               extract(recipient, recps->recp_local, i);
+               lprintf(9, "Delivering private local mail to <%s>\n",
+                       recipient);
                if (getuser(&userbuf, recipient) == 0) {
-                       lprintf(9, "Delivering private mail\n");
                        MailboxName(actual_rm, &userbuf, MAILROOM);
                        CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
                }
                else {
-                       lprintf(9, "No user <%s>, saving in %s> instead\n",
-                               recipient, AIDEROOM);
+                       lprintf(9, "No user <%s>\n", recipient);
                        CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
                }
        }
@@ -1892,24 +1907,71 @@ long CtdlSaveMsg(struct CtdlMessage *msg,       /* message to save */
        lprintf(9, "Performing after-save hooks\n");
        PerformMessageHooks(msg, EVT_AFTERSAVE);
 
-       /* */
+       /* For IGnet mail, we have to save a new copy into the spooler for
+        * each recipient, with the R and D fields set to the recipient and
+        * destination-node.  This has two ugly side effects: all other
+        * recipients end up being unlisted in this recipient's copy of the
+        * message, and it has to deliver multiple messages to the same
+        * node.  We'll revisit this again in a year or so when everyone has
+        * a network spool receiver that can handle the new style messages.
+        */
+       if (recps != NULL)
+        if (recps->num_ignet > 0)
+         for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
+               extract(recipient, recps->recp_ignet, i);
+
+               hold_R = msg->cm_fields['R'];
+               hold_D = msg->cm_fields['D'];
+               msg->cm_fields['R'] = mallok(SIZ);
+               msg->cm_fields['D'] = mallok(SIZ);
+               extract_token(msg->cm_fields['R'], recipient, 0, '@');
+               extract_token(msg->cm_fields['D'], recipient, 1, '@');
+               
+               serialize_message(&smr, msg);
+               if (smr.len > 0) {
+                       sprintf(aaa,
+                               "./network/spoolin/netmail.%04lx.%04x.%04x",
+                               (long) getpid(), CC->cs_pid, ++seqnum);
+                       network_fp = fopen(aaa, "wb+");
+                       if (network_fp != NULL) {
+                               fwrite(smr.ser, smr.len, 1, network_fp);
+                               fclose(network_fp);
+                       }
+                       phree(smr.ser);
+               }
+
+               phree(msg->cm_fields['R']);
+               phree(msg->cm_fields['D']);
+               msg->cm_fields['R'] = hold_R;
+               msg->cm_fields['D'] = hold_D;
+       }
+
+       /* Go back to the room we started from */
        lprintf(9, "Returning to original room\n");
        if (strcasecmp(hold_rm, CC->quickroom.QRname))
                getroom(&CC->quickroom, hold_rm);
 
-       /* For internet mail, generate delivery instructions 
-        * (Yes, this is recursive!   Deal with it!)
+       /* For internet mail, generate delivery instructions.
+        * Yes, this is recursive.  Deal with it.  Infinite recursion does
+        * not happen because the delivery instructions message does not
+        * contain a recipient.
         */
-       if (mailtype == MES_INTERNET) {
+       if (recps != NULL)
+        if (recps->num_internet > 0) {
                lprintf(9, "Generating delivery instructions\n");
-               instr = mallok(2048);
+               instr = mallok(SIZ * 2);
                sprintf(instr,
                        "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
-                       "bounceto|%s@%s\n"
-                       "remote|%s|0||\n",
+                       "bounceto|%s@%s\n",
                        SPOOLMIME, newmsgid, (long)time(NULL),
-                       msg->cm_fields['A'], msg->cm_fields['N'],
-                       recipient );
+                       msg->cm_fields['A'], msg->cm_fields['N']
+               );
+
+               for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
+                       extract(recipient, recps->recp_internet, i);
+                       sprintf(&instr[strlen(instr)],
+                               "remote|%s|0||\n", recipient);
+               }
 
                imsg = mallok(sizeof(struct CtdlMessage));
                memset(imsg, 0, sizeof(struct CtdlMessage));
@@ -1918,7 +1980,7 @@ long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
                imsg->cm_format_type = FMT_RFC822;
                imsg->cm_fields['A'] = strdoop("Citadel");
                imsg->cm_fields['M'] = instr;
-               CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
+               CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
                CtdlFreeMessage(imsg);
        }
 
@@ -1946,7 +2008,7 @@ void quickie_message(char *from, char *to, char *room, char *text)
                msg->cm_fields['R'] = strdoop(to);
        msg->cm_fields['M'] = strdoop(text);
 
-       CtdlSaveMsg(msg, "", room, MES_LOCAL);
+       CtdlSubmitMsg(msg, NULL, room);
        CtdlFreeMessage(msg);
        syslog(LOG_NOTICE, text);
 }
@@ -1970,19 +2032,26 @@ char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
 
        if (exist == NULL) {
                m = mallok(4096);
+               m[0] = 0;
+               buffer_len = 4096;
+               message_len = 0;
        }
        else {
-               m = reallok(exist, strlen(exist) + 4096);
-               if (m == NULL) phree(exist);
+               message_len = strlen(exist);
+               buffer_len = message_len + 4096;
+               m = reallok(exist, buffer_len);
+               if (m == NULL) {
+                       phree(exist);
+                       return m;
+               }
        }
+
+       /* flush the input if we have nowhere to store it */
        if (m == NULL) {
                while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
                return(NULL);
-       } else {
-               buffer_len = 4096;
-               m[0] = 0;
-               message_len = 0;
        }
+
        /* read in the lines of message text one by one */
        while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
 
@@ -2007,9 +2076,10 @@ char *CtdlReadMessageBody(char *terminator,      /* token signalling EOT */
                        }
                }
 
-               /* Add the new line to the buffer.  We avoid using strcat()
-                * because that would involve traversing the entire message
-                * after each line, and this function needs to run fast.
+               /* Add the new line to the buffer.  NOTE: this loop must avoid
+                * using functions like strcat() and strlen() because they
+                * traverse the entire buffer upon every call, and doing that
+                * for a multi-megabyte message slows it down beyond usability.
                 */
                strcpy(&m[message_len], buf);
                m[message_len + linelen] = '\n';
@@ -2038,13 +2108,11 @@ static struct CtdlMessage *make_message(
        char *recipient,                /* NULL if it's not mail */
        char *room,                     /* room where it's going */
        int type,                       /* see MES_ types in header file */
-       int net_type,                   /* see MES_ types in header file */
-       int format_type,                /* local or remote (see citadel.h) */
-       char *fake_name)                /* who we're masquerading as */
-{
-
-       int a;
-       char dest_node[32];
+       int format_type,                /* variformat, plain text, MIME... */
+       char *fake_name,                /* who we're masquerading as */
+       char *subject                   /* Subject (optional) */
+) {
+       char dest_node[SIZ];
        char buf[SIZ];
        struct CtdlMessage *msg;
 
@@ -2057,24 +2125,7 @@ static struct CtdlMessage *make_message(
        /* Don't confuse the poor folks if it's not routed mail. */
        strcpy(dest_node, "");
 
-       /* If net_type is MES_IGNET, split out the destination node. */
-       if (net_type == MES_IGNET) {
-               strcpy(dest_node, NODENAME);
-               for (a = 0; a < strlen(recipient); ++a) {
-                       if (recipient[a] == '@') {
-                               recipient[a] = 0;
-                               strcpy(dest_node, &recipient[a + 1]);
-                       }
-               }
-       }
-
-       /* if net_type is MES_INTERNET, set the dest node to 'internet' */
-       if (net_type == MES_INTERNET) {
-               strcpy(dest_node, "internet");
-       }
-
-       while (isspace(recipient[strlen(recipient) - 1]))
-               recipient[strlen(recipient) - 1] = 0;
+       striplt(recipient);
 
        sprintf(buf, "cit%ld", author->usernum);                /* Path */
        msg->cm_fields['P'] = strdoop(buf);
@@ -2087,24 +2138,37 @@ static struct CtdlMessage *make_message(
        else
                msg->cm_fields['A'] = strdoop(author->fullname);
 
-       if (CC->quickroom.QRflags & QR_MAILBOX)                 /* room */
+       if (CC->quickroom.QRflags & QR_MAILBOX) {               /* room */
                msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
-       else
+       }
+       else {
                msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
+       }
 
        msg->cm_fields['N'] = strdoop(NODENAME);                /* nodename */
        msg->cm_fields['H'] = strdoop(HUMANNODE);               /* hnodename */
 
-       if (recipient[0] != 0)
+       if (recipient[0] != 0) {
                msg->cm_fields['R'] = strdoop(recipient);
-       if (dest_node[0] != 0)
+       }
+       if (dest_node[0] != 0) {
                msg->cm_fields['D'] = strdoop(dest_node);
+       }
 
+       if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
+               msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
+       }
+
+       if (subject != NULL) {
+               striplt(subject);
+               if (strlen(subject) > 0) {
+                       msg->cm_fields['U'] = strdoop(subject);
+               }
+       }
 
        msg->cm_fields['M'] = CtdlReadMessageBody("000",
                                                config.c_maxmsglen, NULL);
 
-
        return(msg);
 }
 
@@ -2145,17 +2209,21 @@ int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
 }
 
 
-#ifdef NOT_YET_FINISHED /* FIXME */
 /*
  * Validate recipients, count delivery types and errors, and handle aliasing
+ * FIXME check for dupes!!!!!
  */
 struct recptypes *validate_recipients(char *recipients) {
        struct recptypes *ret;
        char this_recp[SIZ];
+       char this_recp_cooked[SIZ];
+       char append[SIZ];
        int num_recps;
-       int i;
+       int i, j;
        int mailtype;
+       int invalid;
        struct usersupp tempUS;
+       struct quickroom tempQR;
 
        /* Initialize */
        ret = (struct recptypes *) malloc(sizeof(struct recptypes));
@@ -2166,49 +2234,142 @@ struct recptypes *validate_recipients(char *recipients) {
        ret->num_internet = 0;
        ret->num_ignet = 0;
        ret->num_error = 0;
+       ret->num_room = 0;
 
-       if (recipients == NULL) return(ret);
-       if (strlen(recipients) == NULL) return(ret);
-
-       /* Allow either , or ; separators by changing ; to , */
-       for (i=0; i<strlen(recipients); ++i) {
-               if (recipients[i] == ';') {
-                       recipients[i] = ',';
+       if (recipients == NULL) {
+               num_recps = 0;
+       }
+       else if (strlen(recipients) == 0) {
+               num_recps = 0;
+       }
+       else {
+               /* Change all valid separator characters to commas */
+               for (i=0; i<strlen(recipients); ++i) {
+                       if ((recipients[i] == ';') || (recipients[i] == '|')) {
+                               recipients[i] = ',';
+                       }
                }
+
+               /* Count 'em up */
+               num_recps = num_tokens(recipients, ',');
        }
 
-       /* Count 'em up */
-       num_recps = num_tokens(recipients, ',');
-       
-       for (i=0; i<num_recps; ++i) {
+       if (num_recps > 0) for (i=0; i<num_recps; ++i) {
                extract_token(this_recp, recipients, i, ',');
+               striplt(this_recp);
                lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
                mailtype = alias(this_recp);
+               mailtype = alias(this_recp);
+               mailtype = alias(this_recp);
+               for (j=0; j<=strlen(this_recp); ++j) {
+                       if (this_recp[j]=='_') {
+                               this_recp_cooked[j] = ' ';
+                       }
+                       else {
+                               this_recp_cooked[j] = this_recp[j];
+                       }
+               }
+               invalid = 0;
                switch(mailtype) {
                        case MES_LOCAL:
-                               if (getuser(&tempUS, buf) == 0) {
+                               if (!strcasecmp(this_recp, "sysop")) {
+                                       ++ret->num_room;
+                                       strcpy(this_recp, AIDEROOM);
+                                       if (strlen(ret->recp_room) > 0) {
+                                               strcat(ret->recp_room, "|");
+                                       }
+                                       strcat(ret->recp_room, this_recp);
+                               }
+                               else if (getuser(&tempUS, this_recp) == 0) {
+                                       ++ret->num_local;
+                                       strcpy(this_recp, tempUS.fullname);
+                                       if (strlen(ret->recp_local) > 0) {
+                                               strcat(ret->recp_local, "|");
+                                       }
+                                       strcat(ret->recp_local, this_recp);
+                               }
+                               else if (getuser(&tempUS, this_recp_cooked) == 0) {
                                        ++ret->num_local;
+                                       strcpy(this_recp, tempUS.fullname);
+                                       if (strlen(ret->recp_local) > 0) {
+                                               strcat(ret->recp_local, "|");
+                                       }
+                                       strcat(ret->recp_local, this_recp);
+                               }
+                               else if ( (!strncasecmp(this_recp, "room_", 5))
+                                     && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
+                                       ++ret->num_room;
+                                       if (strlen(ret->recp_room) > 0) {
+                                               strcat(ret->recp_room, "|");
+                                       }
+                                       strcat(ret->recp_room, &this_recp_cooked[5]);
                                }
                                else {
                                        ++ret->num_error;
+                                       invalid = 1;
                                }
                                break;
                        case MES_INTERNET:
                                ++ret->num_internet;
+                               if (strlen(ret->recp_internet) > 0) {
+                                       strcat(ret->recp_internet, "|");
+                               }
+                               strcat(ret->recp_internet, this_recp);
                                break;
                        case MES_IGNET:
                                ++ret->num_ignet;
+                               if (strlen(ret->recp_ignet) > 0) {
+                                       strcat(ret->recp_ignet, "|");
+                               }
+                               strcat(ret->recp_ignet, this_recp);
                                break;
                        case MES_ERROR:
                                ++ret->num_error;
+                               invalid = 1;
                                break;
                }
+               if (invalid) {
+                       if (strlen(ret->errormsg) == 0) {
+                               sprintf(append,
+                                       "Invalid recipient: %s",
+                                       this_recp);
+                       }
+                       else {
+                               sprintf(append, 
+                                       ", %s", this_recp);
+                       }
+                       if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
+                               strcat(ret->errormsg, append);
+                       }
+               }
+               else {
+                       if (strlen(ret->display_recp) == 0) {
+                               strcpy(append, this_recp);
+                       }
+                       else {
+                               sprintf(append, ", %s", this_recp);
+                       }
+                       if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
+                               strcat(ret->display_recp, append);
+                       }
+               }
+       }
 
+       if ((ret->num_local + ret->num_internet + ret->num_ignet +
+          ret->num_room + ret->num_error) == 0) {
+               ++ret->num_error;
+               strcpy(ret->errormsg, "No recipients specified.");
        }
 
+       lprintf(9, "validate_recipients()\n");
+       lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
+       lprintf(9, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
+       lprintf(9, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
+       lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
+       lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
+
        return(ret);
 }
-#endif /* FIXME */
 
 
 
@@ -2218,135 +2379,135 @@ struct recptypes *validate_recipients(char *recipients) {
 void cmd_ent0(char *entargs)
 {
        int post = 0;
-       char recipient[SIZ];
+       char recp[SIZ];
+       char masquerade_as[SIZ];
        int anon_flag = 0;
        int format_type = 0;
        char newusername[SIZ];
        struct CtdlMessage *msg;
-       int a, b;
-       int e = 0;
-       int mtsflag = 0;
-       struct usersupp tempUS;
-       char buf[SIZ];
+       int anonymous = 0;
+       char errmsg[SIZ];
        int err = 0;
+       struct recptypes *valid = NULL;
+       char subject[SIZ];
 
        post = extract_int(entargs, 0);
-       extract(recipient, entargs, 1);
+       extract(recp, entargs, 1);
        anon_flag = extract_int(entargs, 2);
        format_type = extract_int(entargs, 3);
+       extract(subject, entargs, 4);
 
        /* first check to make sure the request is valid. */
 
-       err = CtdlDoIHavePermissionToPostInThisRoom(buf);
+       err = CtdlDoIHavePermissionToPostInThisRoom(errmsg);
        if (err) {
-               cprintf("%d %s\n", err, buf);
+               cprintf("%d %s\n", err, errmsg);
                return;
        }
 
        /* Check some other permission type things. */
 
        if (post == 2) {
-               if (CC->usersupp.axlevel < 6) {
+               if (CC->usersupp.axlevel < 6) {
                        cprintf("%d You don't have permission to masquerade.\n",
                                ERROR + HIGHER_ACCESS_REQUIRED);
                        return;
                }
                extract(newusername, entargs, 4);
-               memset(CC->fake_postname, 0, 32);
-               strcpy(CC->fake_postname, newusername);
-               cprintf("%d Ok\n", OK);
+               memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
+               safestrncpy(CC->fake_postname, newusername,
+                       sizeof(CC->fake_postname) );
+               cprintf("%d ok\n", OK);
                return;
        }
        CC->cs_flags |= CS_POSTING;
 
-       buf[0] = 0;
-       if (CC->quickroom.QRflags & QR_MAILBOX) {
-               if (CC->usersupp.axlevel >= 2) {
-                       strcpy(buf, recipient);
-               }
-               else {
-                       strcpy(buf, "sysop");
+       /* In the Mail> room we have to behave a little differently --
+        * make sure the user has specified at least one recipient.  Then
+        * validate the recipient(s).
+        */
+       if ( (CC->quickroom.QRflags & QR_MAILBOX)
+          && (!strcasecmp(&CC->quickroom.QRname[11], MAILROOM)) ) {
+
+               if (CC->usersupp.axlevel < 2) {
+                       strcpy(recp, "sysop");
                }
-               e = alias(buf); /* alias and mail type */
-               if ((buf[0] == 0) || (e == MES_ERROR)) {
-                       cprintf("%d Unknown address - cannot send message.\n",
-                               ERROR + NO_SUCH_USER);
+
+               valid = validate_recipients(recp);
+               if (valid->num_error > 0) {
+                       cprintf("%d %s\n",
+                               ERROR + NO_SUCH_USER, valid->errormsg);
+                       phree(valid);
                        return;
                }
-               if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
-                       cprintf("%d Net privileges required for network mail.\n",
+
+               if ( ( (valid->num_internet + valid->num_ignet) > 0)
+                  && (CC->usersupp.axlevel < 4) ) {
+                       cprintf("%d Higher access required for network mail.\n",
                                ERROR + HIGHER_ACCESS_REQUIRED);
+                       phree(valid);
                        return;
                }
-               if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
+       
+               if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
                    && ((CC->usersupp.flags & US_INTERNET) == 0)
                    && (!CC->internal_pgm)) {
                        cprintf("%d You don't have access to Internet mail.\n",
                                ERROR + HIGHER_ACCESS_REQUIRED);
+                       phree(valid);
                        return;
                }
-               if (!strcasecmp(buf, "sysop")) {
-                       mtsflag = 1;
-               }
-               else if (e == MES_LOCAL) {      /* don't search local file */
-                       if (!strcasecmp(buf, CC->usersupp.fullname)) {
-                               cprintf("%d Can't send mail to yourself!\n",
-                                       ERROR + NO_SUCH_USER);
-                               return;
-                       }
-                       /* Check to make sure the user exists; also get the correct
-                        * upper/lower casing of the name.
-                        */
-                       a = getuser(&tempUS, buf);
-                       if (a != 0) {
-                               cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
-                               return;
-                       }
-                       strcpy(buf, tempUS.fullname);
-               }
+
        }
 
-       b = MES_NORMAL;
-       if (CC->quickroom.QRflags & QR_ANONONLY)
-               b = MES_ANON;
+       /* Is this a room which has anonymous-only or anonymous-option? */
+       anonymous = MES_NORMAL;
+       if (CC->quickroom.QRflags & QR_ANONONLY) {
+               anonymous = MES_ANONONLY;
+       }
        if (CC->quickroom.QRflags & QR_ANONOPT) {
-               if (anon_flag == 1)
-                       b = MES_AN2;
+               if (anon_flag == 1) {   /* only if the user requested it */
+                       anonymous = MES_ANONOPT;
+               }
+       }
+
+       if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
+               recp[0] = 0;
        }
-       if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
-               buf[0] = 0;
 
        /* If we're only checking the validity of the request, return
         * success without creating the message.
         */
        if (post == 0) {
-               cprintf("%d %s\n", OK, buf);
+               cprintf("%d %s\n", OK,
+                       ((valid != NULL) ? valid->display_recp : "") );
+               phree(valid);
                return;
        }
 
-       cprintf("%d send message\n", SEND_LISTING);
-
-       /* Read in the message from the client. */
+       /* Handle author masquerading */
        if (CC->fake_postname[0]) {
-               msg = make_message(&CC->usersupp, buf,
-                       CC->quickroom.QRname, b, e, format_type,
-                       CC->fake_postname);
+               strcpy(masquerade_as, CC->fake_postname);
        }
        else if (CC->fake_username[0]) {
-               msg = make_message(&CC->usersupp, buf,
-                       CC->quickroom.QRname, b, e, format_type,
-                       CC->fake_username);
+               strcpy(masquerade_as, CC->fake_username);
        }
        else {
-               msg = make_message(&CC->usersupp, buf,
-                       CC->quickroom.QRname, b, e, format_type, "");
+               strcpy(masquerade_as, "");
        }
 
+       /* Read in the message from the client. */
+       cprintf("%d send message\n", SEND_LISTING);
+       msg = make_message(&CC->usersupp, recp,
+               CC->quickroom.QRname, anonymous, format_type,
+               masquerade_as, subject);
+
        if (msg != NULL) {
-               CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
+               CtdlSubmitMsg(msg, valid, "");
                CtdlFreeMessage(msg);
        }
        CC->fake_postname[0] = '\0';
+       phree(valid);
        return;
 }
 
@@ -2365,6 +2526,7 @@ int CtdlDeleteMessages(char *room_name,           /* which room */
        struct quickroom qrbuf;
        struct cdbdata *cdbfr;
        long *msglist = NULL;
+       long *dellist = NULL;
        int num_msgs = 0;
        int i;
        int num_deleted = 0;
@@ -2384,6 +2546,7 @@ int CtdlDeleteMessages(char *room_name,           /* which room */
 
        if (cdbfr != NULL) {
                msglist = mallok(cdbfr->len);
+               dellist = mallok(cdbfr->len);
                memcpy(msglist, cdbfr->ptr, cdbfr->len);
                num_msgs = cdbfr->len / sizeof(long);
                cdb_free(cdbfr);
@@ -2409,9 +2572,8 @@ int CtdlDeleteMessages(char *room_name,           /* which room */
 
                        /* Delete message only if all bits are set */
                        if (delete_this == 0x03) {
-                               AdjRefCount(msglist[i], -1);
+                               dellist[num_deleted++] = msglist[i];
                                msglist[i] = 0L;
-                               ++num_deleted;
                        }
                }
 
@@ -2420,9 +2582,25 @@ int CtdlDeleteMessages(char *room_name,          /* which room */
                          msglist, (num_msgs * sizeof(long)));
 
                qrbuf.QRhighest = msglist[num_msgs - 1];
-               phree(msglist);
        }
        lputroom(&qrbuf);
+
+       /* Go through the messages we pulled out of the index, and decrement
+        * their reference counts by 1.  If this is the only room the message
+        * was in, the reference count will reach zero and the message will
+        * automatically be deleted from the database.  We do this in a
+        * separate pass because there might be plug-in hooks getting called,
+        * and we don't want that happening during an S_QUICKROOM critical
+        * section.
+        */
+       if (num_deleted) for (i=0; i<num_deleted; ++i) {
+               PerformDeleteHooks(qrbuf.QRname, dellist[i]);
+               AdjRefCount(dellist[i], -1);
+       }
+
+       /* Now free the memory we used, and go away. */
+       if (msglist != NULL) phree(msglist);
+       if (dellist != NULL) phree(dellist);
        lprintf(9, "%d message(s) deleted.\n", num_deleted);
        return (num_deleted);
 }
@@ -2503,19 +2681,27 @@ void cmd_move(char *args)
        targ[ROOMNAMELEN - 1] = 0;
        is_copy = extract_int(args, 2);
 
+       if (getroom(&qtemp, targ) != 0) {
+               cprintf("%d '%s' does not exist.\n", ERROR, targ);
+               return;
+       }
+
        getuser(&CC->usersupp, CC->curr_user);
+       /* Aides can move/copy */
        if ((CC->usersupp.axlevel < 6)
-           && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
+           /* Roomaides can move/copy */
+           && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
+           /* Permit move/copy to/from personal rooms */
+           && (!((CC->quickroom.QRflags & QR_MAILBOX)
+                           && (qtemp.QRflags & QR_MAILBOX)))
+           /* Permit only copy from public to personal room */
+           && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
+                           && (qtemp.QRflags & QR_MAILBOX)))) {
                cprintf("%d Higher access required.\n",
                        ERROR + HIGHER_ACCESS_REQUIRED);
                return;
        }
 
-       if (getroom(&qtemp, targ) != 0) {
-               cprintf("%d '%s' does not exist.\n", ERROR, targ);
-               return;
-       }
-
        err = CtdlCopyMsgToRoom(num, targ);
        if (err != 0) {
                cprintf("%d Cannot store message in %s: error %d\n",
@@ -2526,7 +2712,9 @@ void cmd_move(char *args)
        /* Now delete the message from the source room,
         * if this is a 'move' rather than a 'copy' operation.
         */
-       if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
+       if (is_copy == 0) {
+               CtdlDeleteMessages(CC->quickroom.QRname, num, "");
+       }
 
        cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
 }
@@ -2568,10 +2756,10 @@ void PutMetaData(struct MetaData *smibuf)
 {
        long TheIndex;
 
-       /* Use the negative of the message number for its supp record index */
+       /* Use the negative of the message number for the metadata db index */
        TheIndex = (0L - smibuf->meta_msgnum);
 
-       lprintf(9, "PuttMetaData(%ld) - ref count is %d\n",
+       lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
                smibuf->meta_msgnum, smibuf->meta_refcount);
 
        cdb_store(CDB_MSGMAIN,
@@ -2611,6 +2799,8 @@ void AdjRefCount(long msgnum, int incr)
                lprintf(9, "Deleting message <%ld>\n", msgnum);
                delnum = msgnum;
                cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
+
+               /* We have to delete the metadata record too! */
                delnum = (0L - msgnum);
                cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
        }
@@ -2714,7 +2904,7 @@ void CtdlWriteObject(char *req_room,              /* Room to stuff it in */
                        CtdlDeleteMessages(roomname, 0L, content_type));
        }
        /* Now write the data */
-       CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
+       CtdlSubmitMsg(msg, NULL, roomname);
        CtdlFreeMessage(msg);
 }