split dkim into signing and binding modules
[citadel.git] / citadel / server / msgbase.c
index 259a3e5ab2994febe129e78febbdca89f6b31015..aaf4446c604f07f6b434bffa7dc905fd26e84109 100644 (file)
@@ -1416,14 +1416,14 @@ int check_cached_msglist(long msgnum) {
 // Get a message off disk.  (returns om_* values found in msgbase.h)
 int CtdlOutputMsg(long msg_num,                // message number (local) to fetch
                int mode,               // how would you like that message?
-               int headers_only,       // eschew the message body?
+               int headers_only,       // If nonzero, skip the message body.  Also avoids loading it, if it's stored separately.
                int do_proto,           // do Citadel protocol responses?
-               int crlf,               // Use CRLF newlines instead of LF?
+               int crlf,               // If nonzero, terminate lines with CRLF instead of just LF
                char *section,          // NULL or a message/rfc822 section
                int flags,              // various flags; see msgbase.h
-               char **Author,
-               char **Address,
-               char **MessageID
+               char **Author,          // If non-NULL, allocate a string buffer and populate the display name (caller must free)
+               char **Address,         // If non-NULL, allocate a string buffer and populate the email address (caller must free)
+               char **MessageID        // If non-NULL, allocate a string buffer and populate the message ID (caller must free)
 ) {
        struct CtdlMessage *TheMessage = NULL;
        int retcode = CIT_OK;
@@ -1456,15 +1456,10 @@ int CtdlOutputMsg(long msg_num,         // message number (local) to fetch
                r = check_cached_msglist(msg_num);
        }
        if (r != om_ok) {
-               syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s",
-                          msg_num, CC->room.QRname
-               );
+               syslog(LOG_DEBUG, "msgbase: security check fail; message %ld is not in %s", msg_num, CC->room.QRname);
                if (do_proto) {
                        if (r == om_access_denied) {
-                               cprintf("%d message %ld was not found in this room\n",
-                                       ERROR + HIGHER_ACCESS_REQUIRED,
-                                       msg_num
-                               );
+                               cprintf("%d message %ld was not found in this room\n", ERROR + HIGHER_ACCESS_REQUIRED, msg_num);
                        }
                }
                return(r);
@@ -1479,8 +1474,7 @@ int CtdlOutputMsg(long msg_num,           // message number (local) to fetch
        }
 
        if (TheMessage == NULL) {
-               if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
-                       ERROR + MESSAGE_NOT_FOUND, msg_num);
+               if (do_proto) cprintf("%d Can't locate msg %ld on disk\n", ERROR + MESSAGE_NOT_FOUND, msg_num);
                return(om_no_such_msg);
        }
 
@@ -1494,16 +1488,13 @@ int CtdlOutputMsg(long msg_num,         // message number (local) to fetch
                        );
 
                if ((Author != NULL) && (*Author == NULL)) {
-                       long len;
-                       CM_GetAsField(TheMessage, eAuthor, Author, &len);
+                       *Author = strdup(TheMessage->cm_fields[eAuthor]);
                }
                if ((Address != NULL) && (*Address == NULL)) {  
-                       long len;
-                       CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
+                       *Address = strdup(TheMessage->cm_fields[erFc822Addr]);
                }
                if ((MessageID != NULL) && (*MessageID == NULL)) {      
-                       long len;
-                       CM_GetAsField(TheMessage, emessageId, MessageID, &len);
+                       *MessageID = strdup(TheMessage->cm_fields[emessageId]);
                }
                CM_Free(TheMessage);
                TheMessage = NULL;
@@ -1566,13 +1557,13 @@ void OutputCtdlMsgHeaders(struct CtdlMessage *TheMessage, int do_proto) {
                else {
                        safestrncpy(display_name, buf, sizeof display_name);
                }
-               if ((is_room_aide())
-                   && ((TheMessage->cm_anon_type == MES_ANONONLY)
-                       || (TheMessage->cm_anon_type == MES_ANONOPT))) {
+               if (    (is_room_aide())
+                       && (    (TheMessage->cm_anon_type == MES_ANONONLY)
+                               || (TheMessage->cm_anon_type == MES_ANONOPT)
+                       )
+               ) {
                        size_t tmp = strlen(display_name);
-                       snprintf(&display_name[tmp],
-                                sizeof display_name - tmp,
-                                " [%s]", buf);
+                       snprintf(&display_name[tmp], sizeof display_name - tmp, " [%s]", buf);
                }
        }
 
@@ -1756,14 +1747,12 @@ void Dump_RFC822HeadersBody(
                        // do nothing
                }
                else {
-                       if ((!eoh) &&
-                           (*mptr == '\n'))
-                       {
+                       if ((!eoh) && (*mptr == '\n')) {
                                eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
-                               if (!eoh)
+                               if (!eoh) {
                                        eoh = *(mptr+1) == '\n';
-                               if (eoh)
-                               {
+                               }
+                               if (eoh) {
                                        StartOfText = mptr;
                                        StartOfText = strchr(StartOfText, '\n');
                                        StartOfText = strchr(StartOfText, '\n');
@@ -1984,10 +1973,10 @@ int CtdlOutputPreLoadedMsg(
        }
 
        // nhdr=yes means that we're only displaying headers, no body
-       if ( (TheMessage->cm_anon_type == MES_ANONONLY)
-          && ((mode == MT_CITADEL) || (mode == MT_MIME))
-          && (do_proto)
-          ) {
+       if (    (TheMessage->cm_anon_type == MES_ANONONLY)
+               && ((mode == MT_CITADEL) || (mode == MT_MIME))
+               && (do_proto)
+       ) {
                cprintf("nhdr=yes\n");
        }
 
@@ -2019,12 +2008,13 @@ int CtdlOutputPreLoadedMsg(
        }
 
        if (mode == MT_RFC822) {
-               // Construct a fun message id
-               cprintf("Message-ID: <%s", mid);
-               if (strchr(mid, '@')==NULL) {
-                       cprintf("@%s", snode);
-               }
-               cprintf(">%s", nl);
+               // Make the message ID RFC2822 compliant
+               cprintf("Message-ID: <%s%s%s>%s",               // put it in angle brackets
+                       mid,
+                       (strchr(mid, '@') ? "" : "@"),          // if there is no domain part,
+                       (strchr(mid, '@') ? "" : snode),        // tack on ours.
+                       nl
+               );
 
                if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
                        cprintf("From: \"----\" <x@x.org>%s", nl);
@@ -2060,11 +2050,7 @@ START_TEXT:
                                (void *)&ma, 1);
                }
                else if (mode == MT_RFC822) {   // unparsed RFC822 dump
-                       Dump_RFC822HeadersBody(
-                               TheMessage,
-                               headers_only,
-                               flags,
-                               nl, nlen);
+                       Dump_RFC822HeadersBody(TheMessage, headers_only, flags, nl, nlen);
                        goto DONE;
                }
        }
@@ -2078,11 +2064,9 @@ START_TEXT:
                if (do_proto) cprintf("text\n");
        }
 
-       if (TheMessage->cm_format_type == FMT_FIXED) 
-               DumpFormatFixed(
-                       TheMessage,
-                       mode,           // how would you like that message?
-                       nl, nlen);
+       if (TheMessage->cm_format_type == FMT_FIXED) {
+               DumpFormatFixed( TheMessage, mode, nl, nlen);
+       }
 
        // If the message on disk is format 0 (Citadel vari-format), we
        // output using the formatter at 80 columns.  This is the final output
@@ -2131,18 +2115,19 @@ DONE:   // now we're done
        return(om_ok);
 }
 
+
 // Save one or more message pointers into a specified room
 // (Returns 0 for success, nonzero for failure)
 // roomname may be NULL to use the current room
 //
-// Note that the 'supplied_msg' field may be set to NULL, in which case
+// Note that the 'msg_in' field may be set to NULL, in which case
 // the message will be fetched from disk, by number, if we need to perform
 // replication checks.  This adds an additional database read, so if the
 // caller already has the message in memory then it should be supplied.  (Obviously
 // this mode of operation only works if we're saving a single message.)
 //
 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
-                       int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
+                       int do_repl_check, struct CtdlMessage *msg_in, int suppress_refcount_adj
 ) {
        int i, j, unique;
        char hold_rm[ROOMNAMELEN];
@@ -2167,7 +2152,7 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
        // Sanity checks
        if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
        if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
-       if (num_newmsgs > 1) supplied_msg = NULL;
+       if (num_newmsgs > 1) msg_in = NULL;
 
        // Now the regular stuff
        if (CtdlGetRoomLock(&CC->room, ((roomname != NULL) ? roomname : CC->room.QRname) ) != 0) {
@@ -2230,8 +2215,8 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
                for (i=0; i<num_msgs_to_be_merged; ++i) {
                        msgid = msgs_to_be_merged[i];
        
-                       if (supplied_msg != NULL) {
-                               msg = supplied_msg;
+                       if (msg_in != NULL) {
+                               msg = msg_in;
                        }
                        else {
                                msg = CtdlFetchMessage(msgid, 0);
@@ -2246,7 +2231,7 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
                                }
 
                                // Free up the memory we may have allocated
-                               if (msg != supplied_msg) {
+                               if (msg != msg_in) {
                                        CM_Free(msg);
                                }
                        }
@@ -2283,8 +2268,8 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
 
 
 // This is the same as CtdlSaveMsgPointersInRoom() but it only accepts a single message.
-int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *supplied_msg) {
-       return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
+int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *msg_in) {
+       return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, msg_in, 0);
 }
 
 
@@ -2293,57 +2278,52 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, stru
 //
 // This is the back end for CtdlSubmitMsg() and should not be directly
 // called by server-side modules.
-long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
-       long retval;
-       int is_bigmsg = 0;
-       char *holdM = NULL;
-       long holdMLen = 0;
-
-       // If the message is big, set its body aside for storage elsewhere and we hide the message body from the serializer
-       if (!CM_IsEmpty(msg, eMessageText) && msg->cm_lengths[eMessageText] > BIGMSG) {
-               is_bigmsg = 1;
-               holdM = msg->cm_fields[eMessageText];
-               msg->cm_fields[eMessageText] = NULL;
-               holdMLen = msg->cm_lengths[eMessageText];
-               msg->cm_lengths[eMessageText] = 0;
-       }
+long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid) {
+       long error_count = 0;
 
        // Serialize our data structure for storage in the database
        struct ser_ret smr = CtdlSerializeMessage(msg);
 
-       if (is_bigmsg) {
-               // put the message body back into the message
-               msg->cm_fields[eMessageText] = holdM;
-               msg->cm_lengths[eMessageText] = holdMLen;
+       if (smr.len == 0) {
+               syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
+               return (-1);
        }
 
-       if (smr.len == 0) {
-               if (Reply) {
-                       cprintf("%d Unable to serialize message\n", ERROR + INTERNAL_ERROR);
-               }
-               else {
-                       syslog(LOG_ERR, "msgbase: CtdlSaveMessage() unable to serialize message");
+       // STORAGE STRATEGY:
+       // * If headers+content fit are <= 4K, store them together.  It's likely to be one
+       //   memory page, one disk sector, etc. so we benefit from a single disk operation.
+       // * If headers+content exceed 4K, store them separately so we don't end up fetching
+       //   many gigamegs of data when we're just scanning the headers.
+       // * We are using a threshold of 4000 bytes so that there's some room for overhead
+       //   if the database or OS adds any metadata to that one disk page.
+
+       if (smr.len <= 4000) {                                  // all together less than one page, store them together
+
+               if (cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len)) {
+                       ++error_count;
                }
-               return (-1L);
-       }
 
-       // Write our little bundle of joy into the message base
-       retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, smr.len);
-       if (retval < 0) {
-               syslog(LOG_ERR, "msgbase: can't store message %ld: %ld", msgid, retval);
        }
-       else {
-               if (is_bigmsg) {
-                       retval = cdb_store(CDB_BIGMSGS, &msgid, (int)sizeof(long), holdM, (holdMLen + 1));
-                       if (retval < 0) {
-                               syslog(LOG_ERR, "msgbase: failed to store message body for msgid %ld: %ld", msgid, retval);
-                       }
+
+       else {                                                  // exceed one page, store headers in MSGMAIN, body in BIGMSGS
+
+               if (cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long), smr.ser, (smr.msgstart - smr.ser))) {
+                       ++error_count;
                }
+
+               if (cdb_store(CDB_BIGMSGS, &msgid, (int)sizeof(long), smr.msgstart+1, (smr.len - (smr.msgstart - smr.ser) - 1) )) {
+                       ++error_count;
+               }
+
+       }
+
+       if (error_count > 0) {
+               syslog(LOG_ERR, "msgbase: encountered %ld errors storing message %ld", error_count, msgid);
        }
 
        // Free the memory we used for the serialized message
        free(smr.ser);
-       return(retval);
+       return(error_count);
 }
 
 
@@ -2358,7 +2338,7 @@ long send_message(struct CtdlMessage *msg) {
 
        // Generate an ID if we don't have one already
        if (CM_IsEmpty(msg, emessageId)) {
-               msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
+               msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%lX-%lX@%s",
                       (long unsigned int) time(NULL),
                       (long unsigned int) newmsgid,
                       CtdlGetConfigStr("c_fqdn")
@@ -2366,7 +2346,7 @@ long send_message(struct CtdlMessage *msg) {
                CM_SetField(msg, emessageId, msgidbuf);
        }
 
-       retval = CtdlSaveThisMessage(msg, newmsgid, 1);
+       retval = CtdlSaveThisMessage(msg, newmsgid);
 
        if (retval == 0) {
                retval = newmsgid;
@@ -2387,11 +2367,13 @@ struct ser_ret CtdlSerializeMessage(struct CtdlMessage *msg) {
        size_t wlen;
        int i;
 
+       ret.len = 0;
+       ret.ser = NULL;
+       ret.msgstart = NULL;
+
        // Check for valid message format
        if (CM_IsValidMsg(msg) == 0) {
                syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() aborting due to invalid message");
-               ret.len = 0;
-               ret.ser = NULL;
                return(ret);
        }
 
@@ -2408,6 +2390,7 @@ struct ser_ret CtdlSerializeMessage(struct CtdlMessage *msg) {
                syslog(LOG_ERR, "msgbase: CtdlSerializeMessage() malloc(%ld) failed: %m", (long)ret.len);
                ret.len = 0;
                ret.ser = NULL;
+               ret.msgstart = NULL;
                return(ret);
        }
 
@@ -2418,7 +2401,9 @@ struct ser_ret CtdlSerializeMessage(struct CtdlMessage *msg) {
 
        for (i=0; i < NDiskFields; ++i) {
                if (msg->cm_fields[FieldOrder[i]] != NULL) {
-                                                                       // future improvement: do the bigmsg check right here
+                       if (FieldOrder[i] == eMessageText) {
+                               ret.msgstart = &ret.ser[wlen];          // Make a note where the message text begins
+                       }
                        ret.ser[wlen++] = (char)FieldOrder[i];
                        memcpy(&ret.ser[wlen], msg->cm_fields[FieldOrder[i]], msg->cm_lengths[FieldOrder[i]] + 1);
                        wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
@@ -2781,7 +2766,7 @@ long quickie_message(char *from,
        if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room);
        if (!IsEmptyStr(to)) {
                CM_SetField(msg, eRecipient, to);
-               recp = validate_recipients(to, NULL, 0);
+               recp = validate_recipients(to, 0);
        }
        if (!IsEmptyStr(subject)) {
                CM_SetField(msg, eMsgSubject, subject);
@@ -2896,7 +2881,7 @@ struct CtdlMessage *CtdlMakeMessage(
         char *fake_name,                // who we're masquerading as
        char *my_email,                 // which of my email addresses to use (empty is ok)
         char *subject,                  // Subject (optional)
-       char *supplied_euid,            // ...or NULL if this is irrelevant
+       char *euid_in,          // ...or NULL if this is irrelevant
         char *preformatted_text,        // ...or NULL to read text from client
        char *references                // Thread references
 ) {
@@ -2916,8 +2901,8 @@ struct CtdlMessage *CtdlMakeMessage(
                (my_email)?strlen(my_email): 0,
                subject,                                // Subject (optional)
                (subject)?strlen(subject): 0,
-               supplied_euid,                          // ...or NULL if this is irrelevant
-               (supplied_euid)?strlen(supplied_euid):0,
+               euid_in,                                // ...or NULL if this is irrelevant
+               (euid_in)?strlen(euid_in):0,
                preformatted_text,                      // ...or NULL to read text from client
                (preformatted_text)?strlen(preformatted_text) : 0,
                references,                             // Thread references
@@ -2947,7 +2932,7 @@ struct CtdlMessage *CtdlMakeMessageLen(
        long myelen,
        char *subject,                  // Subject (optional)
        long subjlen,
-       char *supplied_euid,            // ...or NULL if this is irrelevant
+       char *euid_in,          // ...or NULL if this is irrelevant
        long euidlen,
        char *preformatted_text,        // ...or NULL to read text from client
        long textlen,
@@ -3038,7 +3023,7 @@ struct CtdlMessage *CtdlMakeMessageLen(
        }
 
        if (euidlen > 0) {
-               CM_SetField(msg, eExclusiveID, supplied_euid);
+               CM_SetField(msg, eExclusiveID, euid_in);
        }
 
        if (reflen > 0) {
@@ -3081,9 +3066,9 @@ int CtdlDeleteMessages(const char *room_name,     // which room
        int need_to_free_re = 0;
 
        if (content_type) if (!IsEmptyStr(content_type)) {
-                       regcomp(&re, content_type, 0);
-                       need_to_free_re = 1;
-               }
+               regcomp(&re, content_type, 0);
+               need_to_free_re = 1;
+       }
        syslog(LOG_DEBUG, "msgbase: CtdlDeleteMessages(%s, %d msgs, %s)", room_name, num_dmsgnums, content_type);
 
        // get room record, obtaining a lock...