* rework imap tokenizer, we no longer copy the stuff around, we keep a reference...
[citadel.git] / citadel / msgbase.c
index 066e1382bebb73c0d031f686de42d2d141efd9d2..87641e3eef8a96e690bc72cd7982525b6f652b1c 100644 (file)
@@ -2,7 +2,22 @@
  * $Id$
  *
  * Implements the message store.
+ * 
+ * Copyright (c) 1987-2010 by the citadel.org team
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
  *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "sysdep.h"
@@ -281,6 +296,25 @@ void headers_listing(long msgnum, void *userdata)
        CtdlFreeMessage(msg);
 }
 
+/*
+ * Back end for the MSGS command: output EUID header.
+ */
+void headers_euid(long msgnum, void *userdata)
+{
+       struct CtdlMessage *msg;
+
+       msg = CtdlFetchMessage(msgnum, 0);
+       if (msg == NULL) {
+               cprintf("%ld||\n", msgnum);
+               return;
+       }
+
+       cprintf("%ld|%s|\n", msgnum, (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""));
+       CtdlFreeMessage(msg);
+}
+
+
+
 
 
 /* Determine if a given message matches the fields in a message template.
@@ -353,7 +387,6 @@ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
        StrBuf *histr;
        const char *pvset;
        char *is_set;   /* actually an array of booleans */
-       int w = 0;
 
        /* Don't bother doing *anything* if we were passed a list of zero messages */
        if (num_target_msgnums < 1) {
@@ -469,14 +502,11 @@ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
                        }
                }
 
-               w = 0;  /* set to 1 if we write something to the string */
-
                if ((was_seen == 0) && (is_seen == 1)) {
                        lo = msglist[i];
                }
                else if ((was_seen == 1) && (is_seen == 0)) {
                        hi = msglist[i-1];
-                       w = 1;
 
                        if (StrLength(vset) > 0) {
                                StrBufAppendBufPlain(vset, HKEY(","), 0);
@@ -488,8 +518,8 @@ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
                                StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
                        }
                }
-               else if ((is_seen) && (i == num_msgs - 1)) {
-                       w = 1;
+
+               if ((is_seen) && (i == num_msgs - 1)) {
                        if (StrLength(vset) > 0) {
                                StrBufAppendBufPlain(vset, HKEY(","), 0);
                        }
@@ -504,34 +534,43 @@ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
                was_seen = is_seen;
        }
 
-       while (StrLength(vset) > SIZ) {
-               /*
-                * If we're truncating the sequence set of messages marked with the 'seen' flag,
-                * we want the earliest messages (the truncated ones) to be marked, not unmarked.
-                * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
-                */
-               if (which_set == ctdlsetseen_seen) {
-                       StrBuf *first_tok;
-                       first_tok = NewStrBuf();
-                       StrBufRemove_token(vset, 0, ',');
-                       StrBufExtract_token(first_tok, vset, 0, ',');
-                       StrBufRemove_token(vset, 0, ',');
-
-                       if (StrBufNum_tokens(first_tok, ':') > 1) {
-                               StrBufRemove_token(first_tok, 0, ':');
-                       }
-                       
-                       StrBuf *new_set;
-                       new_set = NewStrBuf();
-                       StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
-                       StrBufAppendBuf(new_set, first_tok, 0);
-                       StrBufAppendBufPlain(new_set, HKEY(":"), 0);
-                       StrBufAppendBuf(new_set, vset, 0);
-
-                       FreeStrBuf(&vset);
-                       FreeStrBuf(&first_tok);
-                       vset = new_set;
+       /*
+        * We will have to stuff this string back into a 4096 byte buffer, so if it's
+        * larger than that now, truncate it by removing tokens from the beginning.
+        * The limit of 100 iterations is there to prevent an infinite loop in case
+        * something unexpected happens.
+        */
+       int number_of_truncations = 0;
+       while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
+               StrBufRemove_token(vset, 0, ',');
+               ++number_of_truncations;
+       }
+
+       /*
+        * If we're truncating the sequence set of messages marked with the 'seen' flag,
+        * we want the earliest messages (the truncated ones) to be marked, not unmarked.
+        * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
+        */
+       if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
+               StrBuf *first_tok;
+               first_tok = NewStrBuf();
+               StrBufExtract_token(first_tok, vset, 0, ',');
+               StrBufRemove_token(vset, 0, ',');
+
+               if (StrBufNum_tokens(first_tok, ':') > 1) {
+                       StrBufRemove_token(first_tok, 0, ':');
                }
+               
+               StrBuf *new_set;
+               new_set = NewStrBuf();
+               StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
+               StrBufAppendBuf(new_set, first_tok, 0);
+               StrBufAppendBufPlain(new_set, HKEY(":"), 0);
+               StrBufAppendBuf(new_set, vset, 0);
+
+               FreeStrBuf(&vset);
+               FreeStrBuf(&first_tok);
+               vset = new_set;
        }
 
        CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
@@ -560,7 +599,7 @@ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
 int CtdlForEachMessage(int mode, long ref, char *search_string,
                        char *content_type,
                        struct CtdlMessage *compare,
-                       void (*CallBack) (long, void *),
+                        ForEachMsgCallback CallBack,
                        void *userdata)
 {
 
@@ -588,7 +627,7 @@ int CtdlForEachMessage(int mode, long ref, char *search_string,
        }
 
        /* Learn about the user and room in question */
-       getuser(&CC->user, CC->curr_user);
+       CtdlGetUser(&CC->user, CC->curr_user);
        CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
 
        /* Load the message list */
@@ -718,6 +757,7 @@ int CtdlForEachMessage(int mode, long ref, char *search_string,
                                       || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
                                   || ((mode == MSGS_FIRST) && (a < ref))
                                || ((mode == MSGS_GT) && (thismsg > ref))
+                               || ((mode == MSGS_LT) && (thismsg < ref))
                                || ((mode == MSGS_EQ) && (thismsg == ref))
                            )
                            ) {
@@ -753,14 +793,26 @@ void cmd_msgs(char *cmdbuf)
        int i;
        int with_template = 0;
        struct CtdlMessage *template = NULL;
-       int with_headers = 0;
        char search_string[1024];
+       ForEachMsgCallback CallBack;
 
        extract_token(which, cmdbuf, 0, '|', sizeof which);
        cm_ref = extract_int(cmdbuf, 1);
        extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
        with_template = extract_int(cmdbuf, 2);
-       with_headers = extract_int(cmdbuf, 3);
+       switch (extract_int(cmdbuf, 3))
+       {
+       default:
+       case MSG_HDRS_BRIEF:
+               CallBack = simple_listing;
+               break;
+       case MSG_HDRS_ALL:
+               CallBack = headers_listing;
+               break;
+       case MSG_HDRS_EUID:
+               CallBack = headers_euid;
+               break;
+       }
 
        strcat(which, "   ");
        if (!strncasecmp(which, "OLD", 3))
@@ -773,6 +825,8 @@ void cmd_msgs(char *cmdbuf)
                mode = MSGS_LAST;
        else if (!strncasecmp(which, "GT", 2))
                mode = MSGS_GT;
+       else if (!strncasecmp(which, "LT", 2))
+               mode = MSGS_LT;
        else if (!strncasecmp(which, "SEARCH", 6))
                mode = MSGS_SEARCH;
        else
@@ -816,13 +870,12 @@ void cmd_msgs(char *cmdbuf)
        }
 
        CtdlForEachMessage(mode,
-                       ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
-                       ( (mode == MSGS_SEARCH) ? search_string : NULL ),
-                       NULL,
-                       template,
-                       (with_headers ? headers_listing : simple_listing),
-                       NULL
-       );
+                          ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
+                          ( (mode == MSGS_SEARCH) ? search_string : NULL ),
+                          NULL,
+                          template,
+                          CallBack,
+                          NULL);
        if (template != NULL) CtdlFreeMessage(template);
        cprintf("000\n");
 }
@@ -874,79 +927,92 @@ void do_help_subst(char *buffer)
  */
 void memfmout(
        char *mptr,             /* where are we going to get our text from? */
-       char subst,             /* nonzero if we should do substitutions */
-       char *nl)               /* string to terminate lines with */
+       const char *nl)         /* string to terminate lines with */
 {
-       int a, b, c;
-       int real = 0;
-       int old = 0;
-       cit_uint8_t ch;
-       char aaa[140];
-       char buffer[SIZ];
-       static int width = 80;
-
-       strcpy(aaa, "");
-       old = 255;
-       strcpy(buffer, "");
-       c = 1;                  /* c is the current pos */
-
+       StrBuf *OutBuf;
+       char *LineStart;
+       char *LastBlank;
+       size_t len;
+       size_t NLLen;
+       char *eptr;
+       int NLFound, NLFoundLastTime;
+       int Found;
+
+       len = strlen (mptr);
+       NLLen = strlen (nl);
+       eptr = mptr + len;
+
+       OutBuf = NewStrBufPlain(NULL, 200);
+       
+       NLFound = NLFoundLastTime = 0;
        do {
-               if (subst) {
-                       while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
-                               ch = *mptr++;
-                               buffer[strlen(buffer) + 1] = 0;
-                               buffer[strlen(buffer)] = ch;
-                       }
-
-                       if (buffer[0] == '^')
-                               do_help_subst(buffer);
-
-                       buffer[strlen(buffer) + 1] = 0;
-                       a = buffer[0];
-                       strcpy(buffer, &buffer[1]);
-               } else {
-                       ch = *mptr++;
-               }
-
-               old = real;
-               real = ch;
-
-               if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
-                       ch = 32;
-               }
-               if (((old == 13) || (old == 10)) && (isspace(real))) {
-                       cprintf("%s", nl);
-                       c = 1;
-               }
-               if (ch > 32) {
-                       if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
-                               cprintf("%s%s", nl, aaa);
-                               c = strlen(aaa);
-                               aaa[0] = 0;
-                       }
-                       b = strlen(aaa);
-                       aaa[b] = ch;
-                       aaa[b + 1] = 0;
-               }
-               if (ch == 32) {
-                       if ((strlen(aaa) + c) > (width - 5)) {
-                               cprintf("%s", nl);
-                               c = 1;
+               size_t i;
+
+               LineStart = LastBlank = mptr;
+               Found = 'x';
+               i = 0;
+               while (Found == 'x')
+               {
+                       if (LineStart[i] == '\n')
+                               Found = '\n';
+                       else if (LineStart[i] == '\r')
+                               Found = '\r';
+                       else if (LineStart[i] == ' ') 
+                       {
+                               LastBlank = &LineStart[i];
+                               i++;
                        }
-                       cprintf("%s ", aaa);
-                       ++c;
-                       c = c + strlen(aaa);
-                       strcpy(aaa, "");
+                       else if ((i > 80) && (LineStart != LastBlank))
+                               Found = ' ';
+                       else if (LineStart[i] == '\0')
+                               Found = '\0';
+                       else i++;
                }
-               if ((ch == 13) || (ch == 10)) {
-                       cprintf("%s%s", aaa, nl);
-                       c = 1;
-                       strcpy(aaa, "");
+               switch (Found)
+               {
+               case '\n':
+                       if (LineStart[i + 1] == '\r')
+                               mptr = &LineStart[i + 2];
+                       else 
+                               mptr = &LineStart[i + 1];
+                       i--;
+                       NLFound = 1;
+                       break;
+               case '\r':
+                       if (LineStart[i + 1] == '\n')
+                               mptr = &LineStart[i + 2];
+                       else 
+                               mptr = &LineStart[i + 1];
+                       i--;
+                       NLFound = 1;
+                       break;
+               case '\0':
+                       mptr = &LineStart[i + 1];
+                       i--;
+                       NLFound = 0;
+                       break;
+               case ' ':
+                       mptr = LastBlank + 1;
+                       i = LastBlank - LineStart;
+                       NLFound = 0;
+                       break;
+               case 'x':
+                       /* WHUT? */
+                       while (*mptr != '\0') mptr++;
+                       break;
                }
-
-       } while (ch > 0);
-
-       cprintf("%s%s", aaa, nl);
+               if (NLFoundLastTime)
+                       StrBufPlain(OutBuf, HKEY(" "));
+               else
+                       FlushStrBuf(OutBuf);
+               StrBufAppendBufPlain(OutBuf, LineStart, i, 0);
+               StrBufAppendBufPlain(OutBuf, nl, NLLen, 0);
+
+               cputbuf(OutBuf);
+               NLFoundLastTime = NLFound;
+       } while (*mptr != '\0');
+
+       FreeStrBuf(&OutBuf);
 }
 
 
@@ -962,8 +1028,15 @@ void list_this_part(char *name, char *filename, char *partnum, char *disp,
        
        ma = (struct ma_info *)cbuserdata;
        if (ma->is_ma == 0) {
-               cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
-                       name, filename, partnum, disp, cbtype, (long)length, cbid);
+               cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
+                       name, 
+                       filename, 
+                       partnum, 
+                       disp, 
+                       cbtype, 
+                       (long)length, 
+                       cbid, 
+                       cbcharset);
        }
 }
 
@@ -1051,11 +1124,12 @@ void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
        ||      (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
        ) {
                *found_it = 1;
-               cprintf("%d %d|-1|%s|%s\n",
+               cprintf("%d %d|-1|%s|%s|%s\n",
                        BINARY_FOLLOWS,
                        (int)length,
                        filename,
-                       cbtype
+                       cbtype,
+                       cbcharset
                );
                client_write(content, length);
        }
@@ -1426,6 +1500,16 @@ void extract_encapsulated_message(char *name, char *filename, char *partnum, cha
 
 
 
+
+
+int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
+       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+               return(om_not_logged_in);
+       }
+       return(om_ok);
+}
+
+
 /*
  * Get a message off disk.  (returns om_* values found in msgbase.h)
  * 
@@ -1436,21 +1520,29 @@ int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
                  int do_proto,         /* do Citadel protocol responses? */
                  int crlf,             /* Use CRLF newlines instead of LF? */
                  char *section,        /* NULL or a message/rfc822 section */
-                 int flags             /* should the bessage be exported clean? */
+                 int flags             /* various flags; see msgbase.h */
 ) {
        struct CtdlMessage *TheMessage = NULL;
        int retcode = om_no_such_msg;
        struct encapmsg encap;
+       int r;
 
-       CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n", 
+       CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n", 
                msg_num, mode,
                (section ? section : "<>")
        );
 
-       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
-               if (do_proto) cprintf("%d Not logged in.\n",
-                       ERROR + NOT_LOGGED_IN);
-               return(om_not_logged_in);
+       r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
+       if (r != om_ok) {
+               if (do_proto) {
+                       if (r == om_not_logged_in) {
+                               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+                       }
+                       else {
+                               cprintf("%d An unknown error has occurred.\n", ERROR);
+                       }
+               }
+               return(r);
        }
 
        /* FIXME: check message id against msglist for this room */
@@ -1672,7 +1764,7 @@ int CtdlOutputPreLoadedMsg(
        char allkeys[30];
        char display_name[256];
        char *mptr, *mpptr;
-       char *nl;       /* newline string */
+       const char *nl; /* newline string */
        int suppress_f = 0;
        int subject_found = 0;
        struct ma_info ma;
@@ -1702,6 +1794,13 @@ int CtdlOutputPreLoadedMsg(
                return(om_no_such_msg);
        }
 
+       /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
+        * Pad it with spaces in order to avoid changing the RFC822 length of the message.
+        */
+       if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
+               memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
+       }
+               
        /* Are we downloading a MIME component? */
        if (mode == MT_DOWNLOAD) {
                if (TheMessage->cm_format_type != FMT_RFC822) {
@@ -1809,7 +1908,7 @@ int CtdlOutputPreLoadedMsg(
                      if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
                        suppress_f = 1;
                }
-               
+
                /* Now spew the header fields in the order we like them. */
                safestrncpy(allkeys, FORDER, sizeof allkeys);
                for (i=0; i<strlen(allkeys); ++i) {
@@ -2054,19 +2153,49 @@ START_TEXT:
         */
        if (TheMessage->cm_format_type == FMT_FIXED) {
                int buflen;
+               int xlline = 0;
+               int nllen = strlen (nl);
                if (mode == MT_MIME) {
                        cprintf("Content-type: text/plain\n\n");
                }
                *buf = '\0';
                buflen = 0;
                while (ch = *mptr++, ch > 0) {
-                       if (ch == 13)
-                               ch = 10;
-                       if ((ch == 10) || (buflen > 250)) {
+                       if (ch == '\n')
+                               ch = '\r';
+
+                       if ((buflen > 250) && (!xlline)){
+                               int tbuflen;
+                               tbuflen = buflen;
+
+                               while ((buflen > 0) && 
+                                      (!isspace(buf[buflen])))
+                                       buflen --;
+                               if (buflen == 0) {
+                                       xlline = 1;
+                               }
+                               else {
+                                       mptr -= tbuflen - buflen;
+                                       buf[buflen] = '\0';
+                                       ch = '\r';
+                               }
+                       }
+                       /* if we reach the outer bounds of our buffer, 
+                          abort without respect what whe purge. */
+                       if (xlline && 
+                           ((isspace(ch)) || 
+                            (buflen > SIZ - nllen - 2)))
+                               ch = '\r';
+
+                       if (ch == '\r') {
+                               memcpy (&buf[buflen], nl, nllen);
+                               buflen += nllen;
                                buf[buflen] = '\0';
-                               cprintf("%s%s", buf, nl);
+
+                               client_write(buf, buflen);
                                *buf = '\0';
                                buflen = 0;
+                               xlline = 0;
                        } else {
                                buf[buflen] = ch;
                                buflen++;
@@ -2088,7 +2217,7 @@ START_TEXT:
                if (mode == MT_MIME) {
                        cprintf("Content-type: text/x-citadel-variformat\n\n");
                }
-               memfmout(mptr, 0, nl);
+               memfmout(mptr, nl);
        }
 
        /* If the message on disk is format 4 (MIME), we've gotta hand it
@@ -2298,7 +2427,7 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
        if (num_newmsgs > 1) supplied_msg = NULL;
 
        /* Now the regular stuff */
-       if (lgetroom(&CC->room,
+       if (CtdlGetRoomLock(&CC->room,
           ((roomname != NULL) ? roomname : CC->room.QRname) )
           != 0) {
                CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
@@ -2365,7 +2494,7 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
 
        /* Update the highest-message pointer and unlock the room. */
        CC->room.QRhighest = highest_msg;
-       lputroom(&CC->room);
+       CtdlPutRoomLock(&CC->room);
 
        /* Perform replication checks if necessary */
        if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
@@ -2406,7 +2535,7 @@ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newms
        PerformRoomHooks(&CC->room);
 
        /* Go back to the room we were in before we wandered here... */
-       getroom(&CC->room, hold_rm);
+       CtdlGetRoom(&CC->room, hold_rm);
 
        /* Bump the reference count for all messages which were merged */
        for (i=0; i<num_msgs_to_be_merged; ++i) {
@@ -2624,7 +2753,7 @@ void ReplicationChecks(struct CtdlMessage *msg) {
        /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
                msg->cm_fields['E'], CC->room.QRname);*/
 
-       old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
+       old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
        if (old_msgnum > 0L) {
                CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
                CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
@@ -2639,7 +2768,7 @@ void ReplicationChecks(struct CtdlMessage *msg) {
 long CtdlSubmitMsg(struct CtdlMessage *msg,    /* message to save */
                   struct recptypes *recps,     /* recipients (if mail) */
                   char *force,                 /* force a particular room? */
-                  int flags                    /* should the bessage be exported clean? */
+                  int flags                    /* should the message be exported clean? */
 ) {
        char submit_filename[128];
        char generated_timestamp[32];
@@ -2649,7 +2778,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
        char content_type[SIZ];                 /* We have to learn this */
        char recipient[SIZ];
        long newmsgid;
-       char *mptr = NULL;
+       const char *mptr = NULL;
        struct ctdluser userbuf;
        int a, i;
        struct MetaData smi;
@@ -2664,7 +2793,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
        struct addresses_to_be_filed *aptr = NULL;
        char *saved_rfc822_version = NULL;
        int qualified_for_journaling = 0;
-       struct CitContext *CCC = CC;            /* CachedCitContext - performance boost */
+       CitContext *CCC = CC;           /* CachedCitContext - performance boost */
        char bounce_to[1024] = "";
        size_t tmp = 0;
        int rv = 0;
@@ -2760,8 +2889,8 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
 
        CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
        if (strcasecmp(actual_rm, CCC->room.QRname)) {
-               /* getroom(&CCC->room, actual_rm); */
-               usergoto(actual_rm, 0, 1, NULL, NULL);
+               /* CtdlGetRoom(&CCC->room, actual_rm); */
+               CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
        }
 
        /*
@@ -2855,9 +2984,9 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
 
        /* Bump this user's messages posted counter. */
        CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
-       lgetuser(&CCC->user, CCC->curr_user);
+       CtdlGetUserLock(&CCC->user, CCC->curr_user);
        CCC->user.posted = CCC->user.posted + 1;
-       lputuser(&CCC->user);
+       CtdlPutUserLock(&CCC->user);
 
        /* Decide where bounces need to be delivered */
        if ((recps != NULL) && (recps->bounce_to != NULL)) {
@@ -2879,12 +3008,12 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
                                        '|', sizeof recipient);
                CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
                        recipient);
-               if (getuser(&userbuf, recipient) == 0) {
+               if (CtdlGetUser(&userbuf, recipient) == 0) {
                        // Add a flag so the Funambol module knows its mail
                        msg->cm_fields['W'] = strdup(recipient);
-                       MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
+                       CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
                        CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
-                       BumpNewMailCounter(userbuf.usernum);
+                       CtdlBumpNewMailCounter(userbuf.usernum);
                        if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
                        /* Generate a instruction message for the Funambol notification
                         * server, in the same style as the SMTP queue
@@ -2965,7 +3094,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
        /* Go back to the room we started from */
        CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
        if (strcasecmp(hold_rm, CCC->room.QRname))
-               usergoto(hold_rm, 0, 1, NULL, NULL);
+               CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
 
        /* For internet mail, generate delivery instructions.
         * Yes, this is recursive.  Deal with it.  Infinite recursion does
@@ -3020,7 +3149,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,       /* message to save */
        if (collected_addresses != NULL) {
                aptr = (struct addresses_to_be_filed *)
                        malloc(sizeof(struct addresses_to_be_filed));
-               MailboxName(actual_rm, sizeof actual_rm,
+               CtdlMailboxName(actual_rm, sizeof actual_rm,
                        &CCC->user, USERCONTACTSROOM);
                aptr->roomname = strdup(actual_rm);
                aptr->collected_addresses = collected_addresses;
@@ -3068,6 +3197,10 @@ long CtdlSubmitMsg(struct CtdlMessage *msg,      /* message to save */
 
 
 
+void aide_message (char *text, char *subject)
+{
+       quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
+}
 
 
 /*
@@ -3120,105 +3253,102 @@ void quickie_message(const char *from, const char *fromaddr, char *to, char *roo
 /*
  * Back end function used by CtdlMakeMessage() and similar functions
  */
-char *CtdlReadMessageBody(char *terminator,    /* token signalling EOT */
-                       size_t maxlen,          /* maximum message length */
-                       char *exist,            /* if non-null, append to it;
-                                                  exist is ALWAYS freed  */
-                       int crlf,               /* CRLF newlines instead of LF */
-                       int sock                /* socket handle or 0 for this session's client socket */
-                       ) {
-       char buf[1024];
-       int linelen;
-       size_t message_len = 0;
-       size_t buffer_len = 0;
-       char *ptr;
-       char *m;
+StrBuf *CtdlReadMessageBodyBuf(char *terminator,       /* token signalling EOT */
+                              long tlen,
+                              size_t maxlen,           /* maximum message length */
+                              char *exist,             /* if non-null, append to it;
+                                                          exist is ALWAYS freed  */
+                              int crlf,                /* CRLF newlines instead of LF */
+                              int *sock                /* socket handle or 0 for this session's client socket */
+                       ) 
+{
+       StrBuf *Message;
+       StrBuf *LineBuf;
        int flushing = 0;
        int finished = 0;
        int dotdot = 0;
 
+       LineBuf = NewStrBufPlain(NULL, SIZ);
        if (exist == NULL) {
-               m = malloc(4096);
-               m[0] = 0;
-               buffer_len = 4096;
-               message_len = 0;
+               Message = NewStrBufPlain(NULL, 4 * SIZ);
        }
        else {
-               message_len = strlen(exist);
-               buffer_len = message_len + 4096;
-               m = realloc(exist, buffer_len);
-               if (m == NULL) {
-                       free(exist);
-                       return m;
-               }
+               Message = NewStrBufPlain(exist, -1);
+               free(exist);
        }
 
        /* Do we need to change leading ".." to "." for SMTP escaping? */
-       if (!strcmp(terminator, ".")) {
+       if ((tlen == 1) && (*terminator == '.')) {
                dotdot = 1;
        }
 
-       /* flush the input if we have nowhere to store it */
-       if (m == NULL) {
-               flushing = 1;
-       }
-
        /* read in the lines of message text one by one */
        do {
-               if (sock > 0) {
-                       if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
-               }
-               else {
-                       if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
-               }
-               if (!strcmp(buf, terminator)) finished = 1;
-               if (crlf) {
-                       strcat(buf, "\r\n");
+               if (sock != NULL) {
+                       if ((CtdlSockGetLine(sock, LineBuf) < 0) ||
+                           (*sock == -1))
+                               finished = 1;
                }
                else {
-                       strcat(buf, "\n");
-               }
-
-               /* Unescape SMTP-style input of two dots at the beginning of the line */
-               if (dotdot) {
-                       if (!strncmp(buf, "..", 2)) {
-                               strcpy(buf, &buf[1]);
-                       }
+                       if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
                }
+               if ((StrLength(LineBuf) == tlen) && 
+                   (!strcmp(ChrPtr(LineBuf), terminator)))
+                       finished = 1;
 
                if ( (!flushing) && (!finished) ) {
-                       /* Measure the line */
-                       linelen = strlen(buf);
-       
-                       /* augment the buffer if we have to */
-                       if ((message_len + linelen) >= buffer_len) {
-                               ptr = realloc(m, (buffer_len * 2) );
-                               if (ptr == NULL) {      /* flush if can't allocate */
-                                       flushing = 1;
-                               } else {
-                                       buffer_len = (buffer_len * 2);
-                                       m = ptr;
-                                       CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
-                               }
+                       if (crlf) {
+                               StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
                        }
-       
-                       /* 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);
-                       message_len += linelen;
+                       else {
+                               StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
+                       }
+                       
+                       /* Unescape SMTP-style input of two dots at the beginning of the line */
+                       if ((dotdot) &&
+                           (StrLength(LineBuf) == 2) && 
+                           (!strcmp(ChrPtr(LineBuf), "..")))
+                       {
+                               StrBufCutLeft(LineBuf, 1);
+                       }
+                       
+                       StrBufAppendBuf(Message, LineBuf, 0);
                }
 
                /* if we've hit the max msg length, flush the rest */
-               if (message_len >= maxlen) flushing = 1;
+               if (StrLength(Message) >= maxlen) flushing = 1;
 
        } while (!finished);
-       return(m);
+       FreeStrBuf(&LineBuf);
+       return Message;
 }
 
 
+/*
+ * Back end function used by CtdlMakeMessage() and similar functions
+ */
+char *CtdlReadMessageBody(char *terminator,    /* token signalling EOT */
+                         long tlen,
+                         size_t maxlen,                /* maximum message length */
+                         char *exist,          /* if non-null, append to it;
+                                                  exist is ALWAYS freed  */
+                         int crlf,             /* CRLF newlines instead of LF */
+                         int *sock             /* socket handle or 0 for this session's client socket */
+       ) 
+{
+       StrBuf *Message;
+
+       Message = CtdlReadMessageBodyBuf(terminator,
+                                        tlen,
+                                        maxlen,
+                                        exist,
+                                        crlf,
+                                        sock);
+       if (Message == NULL)
+               return NULL;
+       else
+               return SmashStrBuf(&Message);
+}
 
 
 /*
@@ -3344,7 +3474,7 @@ struct CtdlMessage *CtdlMakeMessage(
                msg->cm_fields['M'] = preformatted_text;
        }
        else {
-               msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
+               msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
        }
 
        return(msg);
@@ -3466,7 +3596,7 @@ int CtdlCheckInternetMailPermission(struct ctdluser *who) {
  *
  * Caller needs to free the result using free_recipients()
  */
-struct recptypes *validate_recipients(char *supplied_recipients, 
+struct recptypes *validate_recipients(const char *supplied_recipients, 
                                      const char *RemoteIdentifier, 
                                      int Flags) {
        struct recptypes *ret;
@@ -3577,7 +3707,7 @@ struct recptypes *validate_recipients(char *supplied_recipients,
                                        strcat(ret->recp_room, this_recp);
                                }
                                else if ( (!strncasecmp(this_recp, "room_", 5))
-                                     && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
+                                     && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
 
                                        /* Save room so we can restore it later */
                                        tempQR2 = CC->room;
@@ -3606,7 +3736,7 @@ struct recptypes *validate_recipients(char *supplied_recipients,
                                        CC->room = tempQR2;
 
                                }
-                               else if (getuser(&tempUS, this_recp) == 0) {
+                               else if (CtdlGetUser(&tempUS, this_recp) == 0) {
                                        ++ret->num_local;
                                        strcpy(this_recp, tempUS.fullname);
                                        if (!IsEmptyStr(ret->recp_local)) {
@@ -3614,7 +3744,7 @@ struct recptypes *validate_recipients(char *supplied_recipients,
                                        }
                                        strcat(ret->recp_local, this_recp);
                                }
-                               else if (getuser(&tempUS, this_recp_cooked) == 0) {
+                               else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
                                        ++ret->num_local;
                                        strcpy(this_recp, tempUS.fullname);
                                        if (!IsEmptyStr(ret->recp_local)) {
@@ -4067,7 +4197,7 @@ int CtdlDeleteMessages(char *room_name,           /* which room */
                room_name, num_dmsgnums, content_type);
 
        /* get room record, obtaining a lock... */
-       if (lgetroom(&qrbuf, room_name) != 0) {
+       if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
                CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
                        room_name);
                if (need_to_free_re) regfree(&re);
@@ -4122,9 +4252,12 @@ int CtdlDeleteMessages(char *room_name,          /* which room */
                cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
                          msglist, (int)(num_msgs * sizeof(long)));
 
-               qrbuf.QRhighest = msglist[num_msgs - 1];
+               if (num_msgs > 0)
+                       qrbuf.QRhighest = msglist[num_msgs - 1];
+               else
+                       qrbuf.QRhighest = 0;
        }
-       lputroom(&qrbuf);
+       CtdlPutRoomLock(&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
@@ -4241,7 +4374,7 @@ void cmd_move(char *args)
        targ[ROOMNAMELEN - 1] = 0;
        is_copy = extract_int(args, 2);
 
-       if (getroom(&qtemp, targ) != 0) {
+       if (CtdlGetRoom(&qtemp, targ) != 0) {
                cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
                return;
        }
@@ -4251,7 +4384,7 @@ void cmd_move(char *args)
                return;
        }
 
-       getuser(&CC->user, CC->curr_user);
+       CtdlGetUser(&CC->user, CC->curr_user);
        CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
 
        /* Check for permission to perform this operation.
@@ -4536,7 +4669,7 @@ void CtdlWriteObject(char *req_room,                      /* Room to stuff it in */
        char *encoded_message = NULL;
 
        if (is_mailbox != NULL) {
-               MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
+               CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
        }
        else {
                safestrncpy(roomname, req_room, sizeof(roomname));
@@ -4595,8 +4728,8 @@ void CtdlWriteObject(char *req_room,                      /* Room to stuff it in */
        msg->cm_fields['M'] = encoded_message;
 
        /* Create the requested room if we have to. */
-       if (getroom(&qrbuf, roomname) != 0) {
-               create_room(roomname, 
+       if (CtdlGetRoom(&qrbuf, roomname) != 0) {
+               CtdlCreateRoom(roomname, 
                        ( (is_mailbox != NULL) ? 5 : 3 ),
                        "", 0, 1, 0, VIEW_BBS);
        }
@@ -4631,8 +4764,8 @@ char *CtdlGetSysConfig(char *sysconfname) {
        char buf[SIZ];
        
        strcpy(hold_rm, CC->room.QRname);
-       if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
-               getroom(&CC->room, hold_rm);
+       if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
+               CtdlGetRoom(&CC->room, hold_rm);
                return NULL;
        }
 
@@ -4659,7 +4792,7 @@ char *CtdlGetSysConfig(char *sysconfname) {
                }
        }
 
-       getroom(&CC->room, hold_rm);
+       CtdlGetRoom(&CC->room, hold_rm);
 
        if (conf != NULL) do {
                extract_token(buf, conf, 0, '\n', sizeof buf);
@@ -4729,18 +4862,20 @@ void cmd_isme(char *argbuf) {
 
 CTDL_MODULE_INIT(msgbase)
 {
-       CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
-       CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
-       CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
-       CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
-       CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
-       CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
-       CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
-       CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
-       CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
-       CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
-       CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
-       CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
+       if (!threading) {
+               CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
+               CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
+               CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
+               CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
+               CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
+               CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
+               CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
+               CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
+               CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
+               CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
+               CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
+               CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
+       }
 
         /* return our Subversion id for the Log */
        return "$Id$";