Back to index

citadel  8.12
msgbase.c
Go to the documentation of this file.
00001 /*
00002  * Implements the message store.
00003  *
00004  * Copyright (c) 1987-2012 by the citadel.org team
00005  *
00006  * This program is open source software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License version 3.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  */
00014 
00015 #include "sysdep.h"
00016 #include <stdlib.h>
00017 #include <unistd.h>
00018 #include <stdio.h>
00019 #include <fcntl.h>
00020 
00021 #if TIME_WITH_SYS_TIME
00022 # include <sys/time.h>
00023 # include <time.h>
00024 #else
00025 # if HAVE_SYS_TIME_H
00026 #  include <sys/time.h>
00027 # else
00028 #  include <time.h>
00029 # endif
00030 #endif
00031 
00032 
00033 #include <ctype.h>
00034 #include <string.h>
00035 #include <limits.h>
00036 #include <errno.h>
00037 #include <stdarg.h>
00038 #include <sys/stat.h>
00039 #include <sys/types.h>
00040 #include <regex.h>
00041 #include <libcitadel.h>
00042 #include "citadel.h"
00043 #include "server.h"
00044 #include "serv_extensions.h"
00045 #include "database.h"
00046 #include "msgbase.h"
00047 #include "support.h"
00048 #include "sysdep_decls.h"
00049 #include "citserver.h"
00050 #include "room_ops.h"
00051 #include "user_ops.h"
00052 #include "file_ops.h"
00053 #include "config.h"
00054 #include "control.h"
00055 #include "genstamp.h"
00056 #include "internet_addressing.h"
00057 #include "euidindex.h"
00058 #include "journaling.h"
00059 #include "citadel_dirs.h"
00060 #include "clientsocket.h"
00061 #include "threads.h"
00062 
00063 #include "ctdl_module.h"
00064 
00065 long config_msgnum;
00066 struct addresses_to_be_filed *atbf = NULL;
00067 
00068 /* This temp file holds the queue of operations for AdjRefCount() */
00069 static FILE *arcfp = NULL;
00070 void AdjRefCountList(long *msgnum, long nmsg, int incr);
00071 
00072 int MessageDebugEnabled = 0;
00073 
00074 /*
00075  * These are the four-character field headers we use when outputting
00076  * messages in Citadel format (as opposed to RFC822 format).
00077  */
00078 char *msgkeys[] = {
00079        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
00080        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
00081        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
00082        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
00083        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
00084        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
00085        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
00086        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
00087        NULL, 
00088        "from",
00089        NULL, NULL, NULL,
00090        "exti",
00091        "rfca",
00092        NULL, 
00093        "hnod",
00094        "msgn",
00095        "jrnl",
00096        "rep2",
00097        "list",
00098        "text",
00099        "node",
00100        "room",
00101        "path",
00102        NULL,
00103        "rcpt",
00104        "spec",
00105        "time",
00106        "subj",
00107        NULL,
00108        "wefw",
00109        NULL,
00110        "cccc",
00111        NULL
00112 };
00113 
00114 /*
00115  * This function is self explanatory.
00116  * (What can I say, I'm in a weird mood today...)
00117  */
00118 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
00119 {
00120        int i;
00121 
00122        for (i = 0; i < strlen(name); ++i) {
00123               if (name[i] == '@') {
00124                      while (isspace(name[i - 1]) && i > 0) {
00125                             strcpy(&name[i - 1], &name[i]);
00126                             --i;
00127                      }
00128                      while (isspace(name[i + 1])) {
00129                             strcpy(&name[i + 1], &name[i + 2]);
00130                      }
00131               }
00132        }
00133 }
00134 
00135 
00136 /*
00137  * Aliasing for network mail.
00138  * (Error messages have been commented out, because this is a server.)
00139  */
00140 int alias(char *name)
00141 {                           /* process alias and routing info for mail */
00142        struct CitContext *CCC = CC;
00143        FILE *fp;
00144        int a, i;
00145        char aaa[SIZ], bbb[SIZ];
00146        char *ignetcfg = NULL;
00147        char *ignetmap = NULL;
00148        int at = 0;
00149        char node[64];
00150        char testnode[64];
00151        char buf[SIZ];
00152 
00153        char original_name[256];
00154        safestrncpy(original_name, name, sizeof original_name);
00155 
00156        striplt(name);
00157        remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
00158        stripallbut(name, '<', '>');
00159 
00160        fp = fopen(file_mail_aliases, "r");
00161        if (fp == NULL) {
00162               fp = fopen("/dev/null", "r");
00163        }
00164        if (fp == NULL) {
00165               return (MES_ERROR);
00166        }
00167        strcpy(aaa, "");
00168        strcpy(bbb, "");
00169        while (fgets(aaa, sizeof aaa, fp) != NULL) {
00170               while (isspace(name[0]))
00171                      strcpy(name, &name[1]);
00172               aaa[strlen(aaa) - 1] = 0;
00173               strcpy(bbb, "");
00174               for (a = 0; a < strlen(aaa); ++a) {
00175                      if (aaa[a] == ',') {
00176                             strcpy(bbb, &aaa[a + 1]);
00177                             aaa[a] = 0;
00178                      }
00179               }
00180               if (!strcasecmp(name, aaa))
00181                      strcpy(name, bbb);
00182        }
00183        fclose(fp);
00184 
00185        /* Hit the Global Address Book */
00186        if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
00187               strcpy(name, aaa);
00188        }
00189 
00190        if (strcasecmp(original_name, name)) {
00191               MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
00192        }
00193 
00194        /* Change "user @ xxx" to "user" if xxx is an alias for this host */
00195        for (a=0; a<strlen(name); ++a) {
00196               if (name[a] == '@') {
00197                      if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
00198                             name[a] = 0;
00199                             MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
00200                      }
00201               }
00202        }
00203 
00204        /* determine local or remote type, see citadel.h */
00205        at = haschar(name, '@');
00206        if (at == 0) return(MES_LOCAL);           /* no @'s - local address */
00207        if (at > 1) return(MES_ERROR);            /* >1 @'s - invalid address */
00208        remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
00209 
00210        /* figure out the delivery mode */
00211        extract_token(node, name, 1, '@', sizeof node);
00212 
00213        /* If there are one or more dots in the nodename, we assume that it
00214         * is an FQDN and will attempt SMTP delivery to the Internet.
00215         */
00216        if (haschar(node, '.') > 0) {
00217               return(MES_INTERNET);
00218        }
00219 
00220        /* Otherwise we look in the IGnet maps for a valid Citadel node.
00221         * Try directly-connected nodes first...
00222         */
00223        ignetcfg = CtdlGetSysConfig(IGNETCFG);
00224        for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
00225               extract_token(buf, ignetcfg, i, '\n', sizeof buf);
00226               extract_token(testnode, buf, 0, '|', sizeof testnode);
00227               if (!strcasecmp(node, testnode)) {
00228                      free(ignetcfg);
00229                      return(MES_IGNET);
00230               }
00231        }
00232        free(ignetcfg);
00233 
00234        /*
00235         * Then try nodes that are two or more hops away.
00236         */
00237        ignetmap = CtdlGetSysConfig(IGNETMAP);
00238        for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
00239               extract_token(buf, ignetmap, i, '\n', sizeof buf);
00240               extract_token(testnode, buf, 0, '|', sizeof testnode);
00241               if (!strcasecmp(node, testnode)) {
00242                      free(ignetmap);
00243                      return(MES_IGNET);
00244               }
00245        }
00246        free(ignetmap);
00247 
00248        /* If we get to this point it's an invalid node name */
00249        return (MES_ERROR);
00250 }
00251 
00252 
00253 /*
00254  * Back end for the MSGS command: output message number only.
00255  */
00256 void simple_listing(long msgnum, void *userdata)
00257 {
00258        cprintf("%ld\n", msgnum);
00259 }
00260 
00261 
00262 
00263 /*
00264  * Back end for the MSGS command: output header summary.
00265  */
00266 void headers_listing(long msgnum, void *userdata)
00267 {
00268        struct CtdlMessage *msg;
00269 
00270        msg = CtdlFetchMessage(msgnum, 0);
00271        if (msg == NULL) {
00272               cprintf("%ld|0|||||\n", msgnum);
00273               return;
00274        }
00275 
00276        cprintf("%ld|%s|%s|%s|%s|%s|\n",
00277               msgnum,
00278               (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
00279               (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
00280               (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
00281               (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
00282               (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
00283        );
00284        CtdlFreeMessage(msg);
00285 }
00286 
00287 /*
00288  * Back end for the MSGS command: output EUID header.
00289  */
00290 void headers_euid(long msgnum, void *userdata)
00291 {
00292        struct CtdlMessage *msg;
00293 
00294        msg = CtdlFetchMessage(msgnum, 0);
00295        if (msg == NULL) {
00296               cprintf("%ld||\n", msgnum);
00297               return;
00298        }
00299 
00300        cprintf("%ld|%s|%s\n", 
00301               msgnum, 
00302               (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""),
00303               (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"));
00304        CtdlFreeMessage(msg);
00305 }
00306 
00307 
00308 
00309 
00310 
00311 /* Determine if a given message matches the fields in a message template.
00312  * Return 0 for a successful match.
00313  */
00314 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
00315        int i;
00316 
00317        /* If there aren't any fields in the template, all messages will
00318         * match.
00319         */
00320        if (template == NULL) return(0);
00321 
00322        /* Null messages are bogus. */
00323        if (msg == NULL) return(1);
00324 
00325        for (i='A'; i<='Z'; ++i) {
00326               if (template->cm_fields[i] != NULL) {
00327                      if (msg->cm_fields[i] == NULL) {
00328                             /* Considered equal if temmplate is empty string */
00329                             if (IsEmptyStr(template->cm_fields[i])) continue;
00330                             return 1;
00331                      }
00332                      if (strcasecmp(msg->cm_fields[i],
00333                             template->cm_fields[i])) return 1;
00334               }
00335        }
00336 
00337        /* All compares succeeded: we have a match! */
00338        return 0;
00339 }
00340 
00341 
00342 
00343 /*
00344  * Retrieve the "seen" message list for the current room.
00345  */
00346 void CtdlGetSeen(char *buf, int which_set) {
00347        struct CitContext *CCC = CC;
00348        visit vbuf;
00349 
00350        /* Learn about the user and room in question */
00351        CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
00352 
00353        if (which_set == ctdlsetseen_seen)
00354               safestrncpy(buf, vbuf.v_seen, SIZ);
00355        if (which_set == ctdlsetseen_answered)
00356               safestrncpy(buf, vbuf.v_answered, SIZ);
00357 }
00358 
00359 
00360 
00361 /*
00362  * Manipulate the "seen msgs" string (or other message set strings)
00363  */
00364 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
00365               int target_setting, int which_set,
00366               struct ctdluser *which_user, struct ctdlroom *which_room) {
00367        struct CitContext *CCC = CC;
00368        struct cdbdata *cdbfr;
00369        int i, k;
00370        int is_seen = 0;
00371        int was_seen = 0;
00372        long lo = (-1L);
00373        long hi = (-1L); 
00374        visit vbuf;
00375        long *msglist;
00376        int num_msgs = 0;
00377        StrBuf *vset;
00378        StrBuf *setstr;
00379        StrBuf *lostr;
00380        StrBuf *histr;
00381        const char *pvset;
00382        char *is_set; /* actually an array of booleans */
00383 
00384        /* Don't bother doing *anything* if we were passed a list of zero messages */
00385        if (num_target_msgnums < 1) {
00386               return;
00387        }
00388 
00389        /* If no room was specified, we go with the current room. */
00390        if (!which_room) {
00391               which_room = &CCC->room;
00392        }
00393 
00394        /* If no user was specified, we go with the current user. */
00395        if (!which_user) {
00396               which_user = &CCC->user;
00397        }
00398 
00399        MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
00400                  num_target_msgnums, target_msgnums[0],
00401                  (target_setting ? "SET" : "CLEAR"),
00402                  which_set,
00403                  which_room->QRname);
00404 
00405        /* Learn about the user and room in question */
00406        CtdlGetRelationship(&vbuf, which_user, which_room);
00407 
00408        /* Load the message list */
00409        cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
00410        if (cdbfr != NULL) {
00411               msglist = (long *) cdbfr->ptr;
00412               cdbfr->ptr = NULL;   /* CtdlSetSeen() now owns this memory */
00413               num_msgs = cdbfr->len / sizeof(long);
00414               cdb_free(cdbfr);
00415        } else {
00416               return;       /* No messages at all?  No further action. */
00417        }
00418 
00419        is_set = malloc(num_msgs * sizeof(char));
00420        memset(is_set, 0, (num_msgs * sizeof(char)) );
00421 
00422        /* Decide which message set we're manipulating */
00423        switch(which_set) {
00424        case ctdlsetseen_seen:
00425               vset = NewStrBufPlain(vbuf.v_seen, -1);
00426               break;
00427        case ctdlsetseen_answered:
00428               vset = NewStrBufPlain(vbuf.v_answered, -1);
00429               break;
00430        default:
00431               vset = NewStrBuf();
00432        }
00433 
00434 
00435 #if 0  /* This is a special diagnostic section.  Do not allow it to run during normal operation. */
00436        MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
00437        for (i=0; i<num_msgs; ++i) {
00438               if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
00439        }
00440        MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
00441        for (k=0; k<num_target_msgnums; ++k) {
00442               if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
00443        }
00444 #endif
00445 
00446        MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
00447 
00448        /* Translate the existing sequence set into an array of booleans */
00449        setstr = NewStrBuf();
00450        lostr = NewStrBuf();
00451        histr = NewStrBuf();
00452        pvset = NULL;
00453        while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
00454 
00455               StrBufExtract_token(lostr, setstr, 0, ':');
00456               if (StrBufNum_tokens(setstr, ':') >= 2) {
00457                      StrBufExtract_token(histr, setstr, 1, ':');
00458               }
00459               else {
00460                      FlushStrBuf(histr);
00461                      StrBufAppendBuf(histr, lostr, 0);
00462               }
00463               lo = StrTol(lostr);
00464               if (!strcmp(ChrPtr(histr), "*")) {
00465                      hi = LONG_MAX;
00466               }
00467               else {
00468                      hi = StrTol(histr);
00469               }
00470 
00471               for (i = 0; i < num_msgs; ++i) {
00472                      if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
00473                             is_set[i] = 1;
00474                      }
00475               }
00476        }
00477        FreeStrBuf(&setstr);
00478        FreeStrBuf(&lostr);
00479        FreeStrBuf(&histr);
00480 
00481 
00482        /* Now translate the array of booleans back into a sequence set */
00483        FlushStrBuf(vset);
00484        was_seen = 0;
00485        lo = (-1);
00486        hi = (-1);
00487 
00488        for (i=0; i<num_msgs; ++i) {
00489               is_seen = is_set[i];
00490 
00491               /* Apply changes */
00492               for (k=0; k<num_target_msgnums; ++k) {
00493                      if (msglist[i] == target_msgnums[k]) {
00494                             is_seen = target_setting;
00495                      }
00496               }
00497 
00498               if ((was_seen == 0) && (is_seen == 1)) {
00499                      lo = msglist[i];
00500               }
00501               else if ((was_seen == 1) && (is_seen == 0)) {
00502                      hi = msglist[i-1];
00503 
00504                      if (StrLength(vset) > 0) {
00505                             StrBufAppendBufPlain(vset, HKEY(","), 0);
00506                      }
00507                      if (lo == hi) {
00508                             StrBufAppendPrintf(vset, "%ld", hi);
00509                      }
00510                      else {
00511                             StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
00512                      }
00513               }
00514 
00515               if ((is_seen) && (i == num_msgs - 1)) {
00516                      if (StrLength(vset) > 0) {
00517                             StrBufAppendBufPlain(vset, HKEY(","), 0);
00518                      }
00519                      if ((i==0) || (was_seen == 0)) {
00520                             StrBufAppendPrintf(vset, "%ld", msglist[i]);
00521                      }
00522                      else {
00523                             StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
00524                      }
00525               }
00526 
00527               was_seen = is_seen;
00528        }
00529 
00530        /*
00531         * We will have to stuff this string back into a 4096 byte buffer, so if it's
00532         * larger than that now, truncate it by removing tokens from the beginning.
00533         * The limit of 100 iterations is there to prevent an infinite loop in case
00534         * something unexpected happens.
00535         */
00536        int number_of_truncations = 0;
00537        while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
00538               StrBufRemove_token(vset, 0, ',');
00539               ++number_of_truncations;
00540        }
00541 
00542        /*
00543         * If we're truncating the sequence set of messages marked with the 'seen' flag,
00544         * we want the earliest messages (the truncated ones) to be marked, not unmarked.
00545         * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
00546         */
00547        if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
00548               StrBuf *first_tok;
00549               first_tok = NewStrBuf();
00550               StrBufExtract_token(first_tok, vset, 0, ',');
00551               StrBufRemove_token(vset, 0, ',');
00552 
00553               if (StrBufNum_tokens(first_tok, ':') > 1) {
00554                      StrBufRemove_token(first_tok, 0, ':');
00555               }
00556               
00557               StrBuf *new_set;
00558               new_set = NewStrBuf();
00559               StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
00560               StrBufAppendBuf(new_set, first_tok, 0);
00561               StrBufAppendBufPlain(new_set, HKEY(":"), 0);
00562               StrBufAppendBuf(new_set, vset, 0);
00563 
00564               FreeStrBuf(&vset);
00565               FreeStrBuf(&first_tok);
00566               vset = new_set;
00567        }
00568 
00569        MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
00570 
00571        /* Decide which message set we're manipulating */
00572        switch (which_set) {
00573               case ctdlsetseen_seen:
00574                      safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
00575                      break;
00576               case ctdlsetseen_answered:
00577                      safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
00578                      break;
00579        }
00580 
00581        free(is_set);
00582        free(msglist);
00583        CtdlSetRelationship(&vbuf, which_user, which_room);
00584        FreeStrBuf(&vset);
00585 }
00586 
00587 
00588 /*
00589  * API function to perform an operation for each qualifying message in the
00590  * current room.  (Returns the number of messages processed.)
00591  */
00592 int CtdlForEachMessage(int mode, long ref, char *search_string,
00593                      char *content_type,
00594                      struct CtdlMessage *compare,
00595                         ForEachMsgCallback CallBack,
00596                      void *userdata)
00597 {
00598        struct CitContext *CCC = CC;
00599        int a, i, j;
00600        visit vbuf;
00601        struct cdbdata *cdbfr;
00602        long *msglist = NULL;
00603        int num_msgs = 0;
00604        int num_processed = 0;
00605        long thismsg;
00606        struct MetaData smi;
00607        struct CtdlMessage *msg = NULL;
00608        int is_seen = 0;
00609        long lastold = 0L;
00610        int printed_lastold = 0;
00611        int num_search_msgs = 0;
00612        long *search_msgs = NULL;
00613        regex_t re;
00614        int need_to_free_re = 0;
00615        regmatch_t pm;
00616 
00617        if ((content_type) && (!IsEmptyStr(content_type))) {
00618               regcomp(&re, content_type, 0);
00619               need_to_free_re = 1;
00620        }
00621 
00622        /* Learn about the user and room in question */
00623        if (server_shutting_down) {
00624               if (need_to_free_re) regfree(&re);
00625               return -1;
00626        }
00627        CtdlGetUser(&CCC->user, CCC->curr_user);
00628 
00629        if (server_shutting_down) {
00630               if (need_to_free_re) regfree(&re);
00631               return -1;
00632        }
00633        CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
00634 
00635        if (server_shutting_down) {
00636               if (need_to_free_re) regfree(&re);
00637               return -1;
00638        }
00639 
00640        /* Load the message list */
00641        cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
00642        if (cdbfr == NULL) {
00643               if (need_to_free_re) regfree(&re);
00644               return 0;     /* No messages at all?  No further action. */
00645        }
00646 
00647        msglist = (long *) cdbfr->ptr;
00648        num_msgs = cdbfr->len / sizeof(long);
00649 
00650        cdbfr->ptr = NULL;   /* clear this so that cdb_free() doesn't free it */
00651        cdb_free(cdbfr);     /* we own this memory now */
00652 
00653        /*
00654         * Now begin the traversal.
00655         */
00656        if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
00657 
00658               /* If the caller is looking for a specific MIME type, filter
00659                * out all messages which are not of the type requested.
00660                */
00661               if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
00662 
00663                      /* This call to GetMetaData() sits inside this loop
00664                       * so that we only do the extra database read per msg
00665                       * if we need to.  Doing the extra read all the time
00666                       * really kills the server.  If we ever need to use
00667                       * metadata for another search criterion, we need to
00668                       * move the read somewhere else -- but still be smart
00669                       * enough to only do the read if the caller has
00670                       * specified something that will need it.
00671                       */
00672                      if (server_shutting_down) {
00673                             if (need_to_free_re) regfree(&re);
00674                             free(msglist);
00675                             return -1;
00676                      }
00677                      GetMetaData(&smi, msglist[a]);
00678 
00679                      /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
00680                      if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
00681                             msglist[a] = 0L;
00682                      }
00683               }
00684        }
00685 
00686        num_msgs = sort_msglist(msglist, num_msgs);
00687 
00688        /* If a template was supplied, filter out the messages which
00689         * don't match.  (This could induce some delays!)
00690         */
00691        if (num_msgs > 0) {
00692               if (compare != NULL) {
00693                      for (a = 0; a < num_msgs; ++a) {
00694                             if (server_shutting_down) {
00695                                    if (need_to_free_re) regfree(&re);
00696                                    free(msglist);
00697                                    return -1;
00698                             }
00699                             msg = CtdlFetchMessage(msglist[a], 1);
00700                             if (msg != NULL) {
00701                                    if (CtdlMsgCmp(msg, compare)) {
00702                                           msglist[a] = 0L;
00703                                    }
00704                                    CtdlFreeMessage(msg);
00705                             }
00706                      }
00707               }
00708        }
00709 
00710        /* If a search string was specified, get a message list from
00711         * the full text index and remove messages which aren't on both
00712         * lists.
00713         *
00714         * How this works:
00715         * Since the lists are sorted and strictly ascending, and the
00716         * output list is guaranteed to be shorter than or equal to the
00717         * input list, we overwrite the bottom of the input list.  This
00718         * eliminates the need to memmove big chunks of the list over and
00719         * over again.
00720         */
00721        if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
00722 
00723               /* Call search module via hook mechanism.
00724                * NULL means use any search function available.
00725                * otherwise replace with a char * to name of search routine
00726                */
00727               CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
00728 
00729               if (num_search_msgs > 0) {
00730        
00731                      int orig_num_msgs;
00732 
00733                      orig_num_msgs = num_msgs;
00734                      num_msgs = 0;
00735                      for (i=0; i<orig_num_msgs; ++i) {
00736                             for (j=0; j<num_search_msgs; ++j) {
00737                                    if (msglist[i] == search_msgs[j]) {
00738                                           msglist[num_msgs++] = msglist[i];
00739                                    }
00740                             }
00741                      }
00742               }
00743               else {
00744                      num_msgs = 0; /* No messages qualify */
00745               }
00746               if (search_msgs != NULL) free(search_msgs);
00747 
00748               /* Now that we've purged messages which don't contain the search
00749                * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
00750                * point on.
00751                */
00752               mode = MSGS_ALL;
00753        }
00754 
00755        /*
00756         * Now iterate through the message list, according to the
00757         * criteria supplied by the caller.
00758         */
00759        if (num_msgs > 0)
00760               for (a = 0; a < num_msgs; ++a) {
00761                      if (server_shutting_down) {
00762                             if (need_to_free_re) regfree(&re);
00763                             free(msglist);
00764                             return num_processed;
00765                      }
00766                      thismsg = msglist[a];
00767                      if (mode == MSGS_ALL) {
00768                             is_seen = 0;
00769                      }
00770                      else {
00771                             is_seen = is_msg_in_sequence_set(
00772                                                  vbuf.v_seen, thismsg);
00773                             if (is_seen) lastold = thismsg;
00774                      }
00775                      if ((thismsg > 0L)
00776                          && (
00777 
00778                                    (mode == MSGS_ALL)
00779                                    || ((mode == MSGS_OLD) && (is_seen))
00780                                    || ((mode == MSGS_NEW) && (!is_seen))
00781                                    || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
00782                                || ((mode == MSGS_FIRST) && (a < ref))
00783                             || ((mode == MSGS_GT) && (thismsg > ref))
00784                             || ((mode == MSGS_LT) && (thismsg < ref))
00785                             || ((mode == MSGS_EQ) && (thismsg == ref))
00786                          )
00787                          ) {
00788                             if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
00789                                    if (CallBack)
00790                                           CallBack(lastold, userdata);
00791                                    printed_lastold = 1;
00792                                    ++num_processed;
00793                             }
00794                             if (CallBack) CallBack(thismsg, userdata);
00795                             ++num_processed;
00796                      }
00797               }
00798        if (need_to_free_re) regfree(&re);
00799 
00800        /*
00801         * We cache the most recent msglist in order to do security checks later
00802         */
00803        if (CCC->client_socket > 0) {
00804               if (CCC->cached_msglist != NULL) {
00805                      free(CCC->cached_msglist);
00806               }
00807               CCC->cached_msglist = msglist;
00808               CCC->cached_num_msgs = num_msgs;
00809        }
00810        else {
00811               free(msglist);
00812        }
00813 
00814        return num_processed;
00815 }
00816 
00817 
00818 
00819 /*
00820  * cmd_msgs()  -  get list of message #'s in this room
00821  *            implements the MSGS server command using CtdlForEachMessage()
00822  */
00823 void cmd_msgs(char *cmdbuf)
00824 {
00825        int mode = 0;
00826        char which[16];
00827        char buf[256];
00828        char tfield[256];
00829        char tvalue[256];
00830        int cm_ref = 0;
00831        int i;
00832        int with_template = 0;
00833        struct CtdlMessage *template = NULL;
00834        char search_string[1024];
00835        ForEachMsgCallback CallBack;
00836 
00837        if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
00838 
00839        extract_token(which, cmdbuf, 0, '|', sizeof which);
00840        cm_ref = extract_int(cmdbuf, 1);
00841        extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
00842        with_template = extract_int(cmdbuf, 2);
00843        switch (extract_int(cmdbuf, 3))
00844        {
00845        default:
00846        case MSG_HDRS_BRIEF:
00847               CallBack = simple_listing;
00848               break;
00849        case MSG_HDRS_ALL:
00850               CallBack = headers_listing;
00851               break;
00852        case MSG_HDRS_EUID:
00853               CallBack = headers_euid;
00854               break;
00855        }
00856 
00857        strcat(which, "   ");
00858        if (!strncasecmp(which, "OLD", 3))
00859               mode = MSGS_OLD;
00860        else if (!strncasecmp(which, "NEW", 3))
00861               mode = MSGS_NEW;
00862        else if (!strncasecmp(which, "FIRST", 5))
00863               mode = MSGS_FIRST;
00864        else if (!strncasecmp(which, "LAST", 4))
00865               mode = MSGS_LAST;
00866        else if (!strncasecmp(which, "GT", 2))
00867               mode = MSGS_GT;
00868        else if (!strncasecmp(which, "LT", 2))
00869               mode = MSGS_LT;
00870        else if (!strncasecmp(which, "SEARCH", 6))
00871               mode = MSGS_SEARCH;
00872        else
00873               mode = MSGS_ALL;
00874 
00875        if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
00876               cprintf("%d Full text index is not enabled on this server.\n",
00877                      ERROR + CMD_NOT_SUPPORTED);
00878               return;
00879        }
00880 
00881        if (with_template) {
00882               unbuffer_output();
00883               cprintf("%d Send template then receive message list\n",
00884                      START_CHAT_MODE);
00885               template = (struct CtdlMessage *)
00886                      malloc(sizeof(struct CtdlMessage));
00887               memset(template, 0, sizeof(struct CtdlMessage));
00888               template->cm_magic = CTDLMESSAGE_MAGIC;
00889               template->cm_anon_type = MES_NORMAL;
00890 
00891               while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
00892                      extract_token(tfield, buf, 0, '|', sizeof tfield);
00893                      extract_token(tvalue, buf, 1, '|', sizeof tvalue);
00894                      for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
00895                             if (!strcasecmp(tfield, msgkeys[i])) {
00896                                    template->cm_fields[i] =
00897                                           strdup(tvalue);
00898                             }
00899                      }
00900               }
00901               buffer_output();
00902        }
00903        else {
00904               cprintf("%d  \n", LISTING_FOLLOWS);
00905        }
00906 
00907        CtdlForEachMessage(mode,
00908                         ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
00909                         ( (mode == MSGS_SEARCH) ? search_string : NULL ),
00910                         NULL,
00911                         template,
00912                         CallBack,
00913                         NULL);
00914        if (template != NULL) CtdlFreeMessage(template);
00915        cprintf("000\n");
00916 }
00917 
00918 
00919 
00920 
00921 /* 
00922  * help_subst()  -  support routine for help file viewer
00923  */
00924 void help_subst(char *strbuf, char *source, char *dest)
00925 {
00926        char workbuf[SIZ];
00927        int p;
00928 
00929        while (p = pattern2(strbuf, source), (p >= 0)) {
00930               strcpy(workbuf, &strbuf[p + strlen(source)]);
00931               strcpy(&strbuf[p], dest);
00932               strcat(strbuf, workbuf);
00933        }
00934 }
00935 
00936 
00937 void do_help_subst(char *buffer)
00938 {
00939        char buf2[16];
00940 
00941        help_subst(buffer, "^nodename", config.c_nodename);
00942        help_subst(buffer, "^humannode", config.c_humannode);
00943        help_subst(buffer, "^fqdn", config.c_fqdn);
00944        help_subst(buffer, "^username", CC->user.fullname);
00945        snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
00946        help_subst(buffer, "^usernum", buf2);
00947        help_subst(buffer, "^sysadm", config.c_sysadm);
00948        help_subst(buffer, "^variantname", CITADEL);
00949        snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
00950        help_subst(buffer, "^maxsessions", buf2);
00951        help_subst(buffer, "^bbsdir", ctdl_message_dir);
00952 }
00953 
00954 
00955 
00956 /*
00957  * memfmout()  -  Citadel text formatter and paginator.
00958  *          Although the original purpose of this routine was to format
00959  *          text to the reader's screen width, all we're really using it
00960  *          for here is to format text out to 80 columns before sending it
00961  *          to the client.  The client software may reformat it again.
00962  */
00963 void memfmout(
00964        char *mptr,          /* where are we going to get our text from? */
00965        const char *nl              /* string to terminate lines with */
00966 ) {
00967        struct CitContext *CCC = CC;
00968        int column = 0;
00969        unsigned char ch = 0;
00970        char outbuf[1024];
00971        int len = 0;
00972        int nllen = 0;
00973 
00974        if (!mptr) return;
00975        nllen = strlen(nl);
00976        while (ch=*(mptr++), ch != 0) {
00977 
00978               if (ch == '\n') {
00979                      if (client_write(outbuf, len) == -1)
00980                      {
00981                             MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
00982                             return;
00983                      }
00984                      len = 0;
00985                      if (client_write(nl, nllen) == -1)
00986                      {
00987                             MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
00988                             return;
00989                      }
00990                      column = 0;
00991               }
00992               else if (ch == '\r') {
00993                      /* Ignore carriage returns.  Newlines are always LF or CRLF but never CR. */
00994               }
00995               else if (isspace(ch)) {
00996                      if (column > 72) {          /* Beyond 72 columns, break on the next space */
00997                             if (client_write(outbuf, len) == -1)
00998                             {
00999                                    MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
01000                                    return;
01001                             }
01002                             len = 0;
01003                             if (client_write(nl, nllen) == -1)
01004                             {
01005                                    MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
01006                                    return;
01007                             }
01008                             column = 0;
01009                      }
01010                      else {
01011                             outbuf[len++] = ch;
01012                             ++column;
01013                      }
01014               }
01015               else {
01016                      outbuf[len++] = ch;
01017                      ++column;
01018                      if (column > 1000) {        /* Beyond 1000 columns, break anywhere */
01019                             if (client_write(outbuf, len) == -1)
01020                             {
01021                                    MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
01022                                    return;
01023                             }
01024                             len = 0;
01025                             if (client_write(nl, nllen) == -1)
01026                             {
01027                                    MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
01028                                    return;
01029                             }
01030                             column = 0;
01031                      }
01032               }
01033        }
01034        if (len) {
01035               if (client_write(outbuf, len) == -1)
01036               {
01037                      MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
01038                      return;
01039               }
01040               len = 0;
01041               client_write(nl, nllen);
01042               column = 0;
01043        }
01044 }
01045 
01046 
01047 
01048 /*
01049  * Callback function for mime parser that simply lists the part
01050  */
01051 void list_this_part(char *name, char *filename, char *partnum, char *disp,
01052                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
01053                   char *cbid, void *cbuserdata)
01054 {
01055        struct ma_info *ma;
01056        
01057        ma = (struct ma_info *)cbuserdata;
01058        if (ma->is_ma == 0) {
01059               cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
01060                      name, 
01061                      filename, 
01062                      partnum, 
01063                      disp, 
01064                      cbtype, 
01065                      (long)length, 
01066                      cbid, 
01067                      cbcharset);
01068        }
01069 }
01070 
01071 /* 
01072  * Callback function for multipart prefix
01073  */
01074 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
01075                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
01076                   char *cbid, void *cbuserdata)
01077 {
01078        struct ma_info *ma;
01079        
01080        ma = (struct ma_info *)cbuserdata;
01081        if (!strcasecmp(cbtype, "multipart/alternative")) {
01082               ++ma->is_ma;
01083        }
01084 
01085        if (ma->is_ma == 0) {
01086               cprintf("pref=%s|%s\n", partnum, cbtype);
01087        }
01088 }
01089 
01090 /* 
01091  * Callback function for multipart sufffix
01092  */
01093 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
01094                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
01095                   char *cbid, void *cbuserdata)
01096 {
01097        struct ma_info *ma;
01098        
01099        ma = (struct ma_info *)cbuserdata;
01100        if (ma->is_ma == 0) {
01101               cprintf("suff=%s|%s\n", partnum, cbtype);
01102        }
01103        if (!strcasecmp(cbtype, "multipart/alternative")) {
01104               --ma->is_ma;
01105        }
01106 }
01107 
01108 
01109 /*
01110  * Callback function for mime parser that opens a section for downloading
01111  */
01112 void mime_download(char *name, char *filename, char *partnum, char *disp,
01113                  void *content, char *cbtype, char *cbcharset, size_t length,
01114                  char *encoding, char *cbid, void *cbuserdata)
01115 {
01116        int rv = 0;
01117        CitContext *CCC = MyContext();
01118 
01119        /* Silently go away if there's already a download open. */
01120        if (CCC->download_fp != NULL)
01121               return;
01122 
01123        if (
01124               (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
01125        ||     (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
01126        ) {
01127               CCC->download_fp = tmpfile();
01128               if (CCC->download_fp == NULL) {
01129                      MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
01130                                 strerror(errno));
01131                      cprintf("%d cannot open temporary file: %s\n",
01132                             ERROR + INTERNAL_ERROR, strerror(errno));
01133                      return;
01134               }
01135        
01136               rv = fwrite(content, length, 1, CC->download_fp);
01137               if (rv <= 0) {
01138                      MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
01139                                strerror(errno));
01140                      cprintf("%d unable to write tempfile.\n",
01141                             ERROR + TOO_BIG);
01142                      fclose(CCC->download_fp);
01143                      CCC->download_fp = NULL;
01144                      return;
01145               }
01146               fflush(CCC->download_fp);
01147               rewind(CCC->download_fp);
01148        
01149               OpenCmdResult(filename, cbtype);
01150        }
01151 }
01152 
01153 
01154 
01155 /*
01156  * Callback function for mime parser that outputs a section all at once.
01157  * We can specify the desired section by part number *or* content-id.
01158  */
01159 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
01160                  void *content, char *cbtype, char *cbcharset, size_t length,
01161                  char *encoding, char *cbid, void *cbuserdata)
01162 {
01163        int *found_it = (int *)cbuserdata;
01164 
01165        if (
01166               (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
01167        ||     (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
01168        ) {
01169               *found_it = 1;
01170               cprintf("%d %d|-1|%s|%s|%s\n",
01171                      BINARY_FOLLOWS,
01172                      (int)length,
01173                      filename,
01174                      cbtype,
01175                      cbcharset
01176               );
01177               client_write(content, length);
01178        }
01179 }
01180 
01181 
01182 /*
01183  * Load a message from disk into memory.
01184  * This is used by CtdlOutputMsg() and other fetch functions.
01185  *
01186  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
01187  *       using the CtdlMessageFree() function.
01188  */
01189 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
01190 {
01191        struct CitContext *CCC = CC;
01192        struct cdbdata *dmsgtext;
01193        struct CtdlMessage *ret = NULL;
01194        char *mptr;
01195        char *upper_bound;
01196        cit_uint8_t ch;
01197        cit_uint8_t field_header;
01198 
01199        MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
01200        dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
01201        if (dmsgtext == NULL) {
01202               return NULL;
01203        }
01204        mptr = dmsgtext->ptr;
01205        upper_bound = mptr + dmsgtext->len;
01206 
01207        /* Parse the three bytes that begin EVERY message on disk.
01208         * The first is always 0xFF, the on-disk magic number.
01209         * The second is the anonymous/public type byte.
01210         * The third is the format type byte (vari, fixed, or MIME).
01211         */
01212        ch = *mptr++;
01213        if (ch != 255) {
01214               MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
01215               cdb_free(dmsgtext);
01216               return NULL;
01217        }
01218        ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
01219        memset(ret, 0, sizeof(struct CtdlMessage));
01220 
01221        ret->cm_magic = CTDLMESSAGE_MAGIC;
01222        ret->cm_anon_type = *mptr++;       /* Anon type byte */
01223        ret->cm_format_type = *mptr++;     /* Format type byte */
01224 
01225        /*
01226         * The rest is zero or more arbitrary fields.  Load them in.
01227         * We're done when we encounter either a zero-length field or
01228         * have just processed the 'M' (message text) field.
01229         */
01230        do {
01231               if (mptr >= upper_bound) {
01232                      break;
01233               }
01234               field_header = *mptr++;
01235               ret->cm_fields[field_header] = strdup(mptr);
01236 
01237               while (*mptr++ != 0);       /* advance to next field */
01238 
01239        } while ((mptr < upper_bound) && (field_header != 'M'));
01240 
01241        cdb_free(dmsgtext);
01242 
01243        /* Always make sure there's something in the msg text field.  If
01244         * it's NULL, the message text is most likely stored separately,
01245         * so go ahead and fetch that.  Failing that, just set a dummy
01246         * body so other code doesn't barf.
01247         */
01248        if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
01249               dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
01250               if (dmsgtext != NULL) {
01251                      ret->cm_fields['M'] = dmsgtext->ptr;
01252                      dmsgtext->ptr = NULL;
01253                      cdb_free(dmsgtext);
01254               }
01255        }
01256        if (ret->cm_fields['M'] == NULL) {
01257               ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
01258        }
01259 
01260        /* Perform "before read" hooks (aborting if any return nonzero) */
01261        if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
01262               CtdlFreeMessage(ret);
01263               return NULL;
01264        }
01265 
01266        return (ret);
01267 }
01268 
01269 
01270 /*
01271  * Returns 1 if the supplied pointer points to a valid Citadel message.
01272  * If the pointer is NULL or the magic number check fails, returns 0.
01273  */
01274 int is_valid_message(struct CtdlMessage *msg) {
01275        if (msg == NULL)
01276               return 0;
01277        if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
01278               struct CitContext *CCC = CC;
01279               MSGM_syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
01280               return 0;
01281        }
01282        return 1;
01283 }
01284 
01285 void CtdlFreeMessageContents(struct CtdlMessage *msg)
01286 {
01287        int i;
01288 
01289        for (i = 0; i < 256; ++i)
01290               if (msg->cm_fields[i] != NULL) {
01291                      free(msg->cm_fields[i]);
01292               }
01293 
01294        msg->cm_magic = 0;   /* just in case */
01295 }
01296 /*
01297  * 'Destructor' for struct CtdlMessage
01298  */
01299 void CtdlFreeMessage(struct CtdlMessage *msg)
01300 {
01301        if (is_valid_message(msg) == 0) 
01302        {
01303               if (msg != NULL) free (msg);
01304               return;
01305        }
01306        CtdlFreeMessageContents(msg);
01307        free(msg);
01308 }
01309 
01310 
01311 /*
01312  * Pre callback function for multipart/alternative
01313  *
01314  * NOTE: this differs from the standard behavior for a reason.  Normally when
01315  *       displaying multipart/alternative you want to show the _last_ usable
01316  *       format in the message.  Here we show the _first_ one, because it's
01317  *       usually text/plain.  Since this set of functions is designed for text
01318  *       output to non-MIME-aware clients, this is the desired behavior.
01319  *
01320  */
01321 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
01322               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
01323               char *cbid, void *cbuserdata)
01324 {
01325        struct CitContext *CCC = CC;
01326        struct ma_info *ma;
01327        
01328        ma = (struct ma_info *)cbuserdata;
01329        MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);      
01330        if (!strcasecmp(cbtype, "multipart/alternative")) {
01331               ++ma->is_ma;
01332               ma->did_print = 0;
01333        }
01334        if (!strcasecmp(cbtype, "message/rfc822")) {
01335               ++ma->freeze;
01336        }
01337 }
01338 
01339 /*
01340  * Post callback function for multipart/alternative
01341  */
01342 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
01343               void *content, char *cbtype, char *cbcharset, size_t length,
01344               char *encoding, char *cbid, void *cbuserdata)
01345 {
01346        struct CitContext *CCC = CC;
01347        struct ma_info *ma;
01348        
01349        ma = (struct ma_info *)cbuserdata;
01350        MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);     
01351        if (!strcasecmp(cbtype, "multipart/alternative")) {
01352               --ma->is_ma;
01353               ma->did_print = 0;
01354        }
01355        if (!strcasecmp(cbtype, "message/rfc822")) {
01356               --ma->freeze;
01357        }
01358 }
01359 
01360 /*
01361  * Inline callback function for mime parser that wants to display text
01362  */
01363 void fixed_output(char *name, char *filename, char *partnum, char *disp,
01364               void *content, char *cbtype, char *cbcharset, size_t length,
01365               char *encoding, char *cbid, void *cbuserdata)
01366 {
01367        struct CitContext *CCC = CC;
01368        char *ptr;
01369        char *wptr;
01370        size_t wlen;
01371        struct ma_info *ma;
01372 
01373        ma = (struct ma_info *)cbuserdata;
01374 
01375        MSG_syslog(LOG_DEBUG,
01376               "fixed_output() part %s: %s (%s) (%ld bytes)\n",
01377               partnum, filename, cbtype, (long)length);
01378 
01379        /*
01380         * If we're in the middle of a multipart/alternative scope and
01381         * we've already printed another section, skip this one.
01382         */    
01383        if ( (ma->is_ma) && (ma->did_print) ) {
01384               MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
01385               return;
01386        }
01387        ma->did_print = 1;
01388 
01389        if ( (!strcasecmp(cbtype, "text/plain")) 
01390           || (IsEmptyStr(cbtype)) ) {
01391               wptr = content;
01392               if (length > 0) {
01393                      client_write(wptr, length);
01394                      if (wptr[length-1] != '\n') {
01395                             cprintf("\n");
01396                      }
01397               }
01398               return;
01399        }
01400 
01401        if (!strcasecmp(cbtype, "text/html")) {
01402               ptr = html_to_ascii(content, length, 80, 0);
01403               wlen = strlen(ptr);
01404               client_write(ptr, wlen);
01405               if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
01406                      cprintf("\n");
01407               }
01408               free(ptr);
01409               return;
01410        }
01411 
01412        if (ma->use_fo_hooks) {
01413               if (PerformFixedOutputHooks(cbtype, content, length)) {
01414               /* above function returns nonzero if it handled the part */
01415                      return;
01416               }
01417        }
01418 
01419        if (strncasecmp(cbtype, "multipart/", 10)) {
01420               cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
01421                      partnum, filename, cbtype, (long)length);
01422               return;
01423        }
01424 }
01425 
01426 /*
01427  * The client is elegant and sophisticated and wants to be choosy about
01428  * MIME content types, so figure out which multipart/alternative part
01429  * we're going to send.
01430  *
01431  * We use a system of weights.  When we find a part that matches one of the
01432  * MIME types we've declared as preferential, we can store it in ma->chosen_part
01433  * and then set ma->chosen_pref to that MIME type's position in our preference
01434  * list.  If we then hit another match, we only replace the first match if
01435  * the preference value is lower.
01436  */
01437 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
01438               void *content, char *cbtype, char *cbcharset, size_t length,
01439               char *encoding, char *cbid, void *cbuserdata)
01440 {
01441        struct CitContext *CCC = CC;
01442        char buf[1024];
01443        int i;
01444        struct ma_info *ma;
01445        
01446        ma = (struct ma_info *)cbuserdata;
01447 
01448        // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
01449        //       http://bugzilla.citadel.org/show_bug.cgi?id=220
01450        // I don't know if there are any side effects!  Please TEST TEST TEST
01451        //if (ma->is_ma > 0) {
01452 
01453        for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
01454               extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
01455               if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
01456                      if (i < ma->chosen_pref) {
01457                             MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
01458                             safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
01459                             ma->chosen_pref = i;
01460                      }
01461               }
01462        }
01463 }
01464 
01465 /*
01466  * Now that we've chosen our preferred part, output it.
01467  */
01468 void output_preferred(char *name, 
01469                     char *filename, 
01470                     char *partnum, 
01471                     char *disp,
01472                     void *content, 
01473                     char *cbtype, 
01474                     char *cbcharset, 
01475                     size_t length,
01476                     char *encoding, 
01477                     char *cbid, 
01478                     void *cbuserdata)
01479 {
01480        struct CitContext *CCC = CC;
01481        int i;
01482        char buf[128];
01483        int add_newline = 0;
01484        char *text_content;
01485        struct ma_info *ma;
01486        char *decoded = NULL;
01487        size_t bytes_decoded;
01488        int rc = 0;
01489 
01490        ma = (struct ma_info *)cbuserdata;
01491 
01492        /* This is not the MIME part you're looking for... */
01493        if (strcasecmp(partnum, ma->chosen_part)) return;
01494 
01495        /* If the content-type of this part is in our preferred formats
01496         * list, we can simply output it verbatim.
01497         */
01498        for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
01499               extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
01500               if (!strcasecmp(buf, cbtype)) {
01501                      /* Yeah!  Go!  W00t!! */
01502                      if (ma->dont_decode == 0) 
01503                             rc = mime_decode_now (content, 
01504                                                 length,
01505                                                 encoding,
01506                                                 &decoded,
01507                                                 &bytes_decoded);
01508                      if (rc < 0)
01509                             break; /* Give us the chance, maybe theres another one. */
01510 
01511                      if (rc == 0) text_content = (char *)content;
01512                      else {
01513                             text_content = decoded;
01514                             length = bytes_decoded;
01515                      }
01516 
01517                      if (text_content[length-1] != '\n') {
01518                             ++add_newline;
01519                      }
01520                      cprintf("Content-type: %s", cbtype);
01521                      if (!IsEmptyStr(cbcharset)) {
01522                             cprintf("; charset=%s", cbcharset);
01523                      }
01524                      cprintf("\nContent-length: %d\n",
01525                             (int)(length + add_newline) );
01526                      if (!IsEmptyStr(encoding)) {
01527                             cprintf("Content-transfer-encoding: %s\n", encoding);
01528                      }
01529                      else {
01530                             cprintf("Content-transfer-encoding: 7bit\n");
01531                      }
01532                      cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
01533                      cprintf("\n");
01534                      if (client_write(text_content, length) == -1)
01535                      {
01536                             MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
01537                             return;
01538                      }
01539                      if (add_newline) cprintf("\n");
01540                      if (decoded != NULL) free(decoded);
01541                      return;
01542               }
01543        }
01544 
01545        /* No translations required or possible: output as text/plain */
01546        cprintf("Content-type: text/plain\n\n");
01547        rc = 0;
01548        if (ma->dont_decode == 0)
01549               rc = mime_decode_now (content, 
01550                                   length,
01551                                   encoding,
01552                                   &decoded,
01553                                   &bytes_decoded);
01554        if (rc < 0)
01555               return; /* Give us the chance, maybe theres another one. */
01556        
01557        if (rc == 0) text_content = (char *)content;
01558        else {
01559               text_content = decoded;
01560               length = bytes_decoded;
01561        }
01562 
01563        fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
01564                      length, encoding, cbid, cbuserdata);
01565        if (decoded != NULL) free(decoded);
01566 }
01567 
01568 
01569 struct encapmsg {
01570        char desired_section[64];
01571        char *msg;
01572        size_t msglen;
01573 };
01574 
01575 
01576 /*
01577  * Callback function for
01578  */
01579 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
01580                  void *content, char *cbtype, char *cbcharset, size_t length,
01581                  char *encoding, char *cbid, void *cbuserdata)
01582 {
01583        struct encapmsg *encap;
01584 
01585        encap = (struct encapmsg *)cbuserdata;
01586 
01587        /* Only proceed if this is the desired section... */
01588        if (!strcasecmp(encap->desired_section, partnum)) {
01589               encap->msglen = length;
01590               encap->msg = malloc(length + 2);
01591               memcpy(encap->msg, content, length);
01592               return;
01593        }
01594 }
01595 
01596 
01597 /*
01598  * Determine whether the specified message exists in the cached_msglist
01599  * (This is a security check)
01600  */
01601 int check_cached_msglist(long msgnum) {
01602        struct CitContext *CCC = CC;
01603 
01604        /* cases in which we skip the check */
01605        if (!CCC) return om_ok;                                        /* not a session */
01606        if (CCC->client_socket <= 0) return om_ok;                     /* not a client session */
01607        if (CCC->cached_msglist == NULL) return om_access_denied;      /* no msglist fetched */
01608        if (CCC->cached_num_msgs == 0) return om_access_denied;        /* nothing to check */
01609 
01610 
01611        /* Do a binary search within the cached_msglist for the requested msgnum */
01612        int min = 0;
01613        int max = (CC->cached_num_msgs - 1);
01614 
01615        while (max >= min) {
01616               int middle = min + (max-min) / 2 ;
01617               if (msgnum == CCC->cached_msglist[middle]) {
01618                      return om_ok;
01619               }
01620               if (msgnum > CC->cached_msglist[middle]) {
01621                      min = middle + 1;
01622               }
01623               else {
01624                      max = middle - 1;
01625               }
01626        }
01627 
01628        return om_access_denied;
01629 }
01630 
01631 
01632 /* 
01633  * Determine whether the currently logged in session has permission to read
01634  * messages in the current room.
01635  */
01636 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
01637        if (   (!(CC->logged_in))
01638               && (!(CC->internal_pgm))
01639               && (!config.c_guest_logins)
01640        ) {
01641               return(om_not_logged_in);
01642        }
01643        return(om_ok);
01644 }
01645 
01646 
01647 /*
01648  * Get a message off disk.  (returns om_* values found in msgbase.h)
01649  * 
01650  */
01651 int CtdlOutputMsg(long msg_num,           /* message number (local) to fetch */
01652                 int mode,          /* how would you like that message? */
01653                 int headers_only,  /* eschew the message body? */
01654                 int do_proto,             /* do Citadel protocol responses? */
01655                 int crlf,          /* Use CRLF newlines instead of LF? */
01656                 char *section,     /* NULL or a message/rfc822 section */
01657                 int flags          /* various flags; see msgbase.h */
01658 ) {
01659        struct CitContext *CCC = CC;
01660        struct CtdlMessage *TheMessage = NULL;
01661        int retcode = CIT_OK;
01662        struct encapmsg encap;
01663        int r;
01664 
01665        MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n", 
01666               msg_num, mode,
01667               (section ? section : "<>")
01668        );
01669 
01670        r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
01671        if (r != om_ok) {
01672               if (do_proto) {
01673                      if (r == om_not_logged_in) {
01674                             cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
01675                      }
01676                      else {
01677                             cprintf("%d An unknown error has occurred.\n", ERROR);
01678                      }
01679               }
01680               return(r);
01681        }
01682 
01683        /*
01684         * Check to make sure the message is actually IN this room
01685         */
01686        r = check_cached_msglist(msg_num);
01687        if (r == om_access_denied) {
01688               /* Not in the cache?  We get ONE shot to check it again. */
01689               CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
01690               r = check_cached_msglist(msg_num);
01691        }
01692        if (r != om_ok) {
01693               MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
01694                         msg_num, CCC->room.QRname
01695               );
01696               if (do_proto) {
01697                      if (r == om_access_denied) {
01698                             cprintf("%d message %ld was not found in this room\n",
01699                                    ERROR + HIGHER_ACCESS_REQUIRED,
01700                                    msg_num
01701                             );
01702                      }
01703               }
01704               return(r);
01705        }
01706 
01707        /*
01708         * Fetch the message from disk.  If we're in HEADERS_FAST mode,
01709         * request that we don't even bother loading the body into memory.
01710         */
01711        if (headers_only == HEADERS_FAST) {
01712               TheMessage = CtdlFetchMessage(msg_num, 0);
01713        }
01714        else {
01715               TheMessage = CtdlFetchMessage(msg_num, 1);
01716        }
01717 
01718        if (TheMessage == NULL) {
01719               if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
01720                      ERROR + MESSAGE_NOT_FOUND, msg_num);
01721               return(om_no_such_msg);
01722        }
01723 
01724        /* Here is the weird form of this command, to process only an
01725         * encapsulated message/rfc822 section.
01726         */
01727        if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
01728               memset(&encap, 0, sizeof encap);
01729               safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
01730               mime_parser(TheMessage->cm_fields['M'],
01731                      NULL,
01732                      *extract_encapsulated_message,
01733                      NULL, NULL, (void *)&encap, 0
01734               );
01735               CtdlFreeMessage(TheMessage);
01736               TheMessage = NULL;
01737 
01738               if (encap.msg) {
01739                      encap.msg[encap.msglen] = 0;
01740                      TheMessage = convert_internet_message(encap.msg);
01741                      encap.msg = NULL;    /* no free() here, TheMessage owns it now */
01742 
01743                      /* Now we let it fall through to the bottom of this
01744                       * function, because TheMessage now contains the
01745                       * encapsulated message instead of the top-level
01746                       * message.  Isn't that neat?
01747                       */
01748 
01749               }
01750               else {
01751                      if (do_proto) {
01752                             cprintf("%d msg %ld has no part %s\n",
01753                                    ERROR + MESSAGE_NOT_FOUND,
01754                                    msg_num,
01755                                    section);
01756                      }
01757                      retcode = om_no_such_msg;
01758               }
01759 
01760        }
01761 
01762        /* Ok, output the message now */
01763        if (retcode == CIT_OK)
01764               retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
01765        CtdlFreeMessage(TheMessage);
01766 
01767        return(retcode);
01768 }
01769 
01770 
01771 char *qp_encode_email_addrs(char *source)
01772 {
01773        struct CitContext *CCC = CC;
01774        char *user, *node, *name;
01775        const char headerStr[] = "=?UTF-8?Q?";
01776        char *Encoded;
01777        char *EncodedName;
01778        char *nPtr;
01779        int need_to_encode = 0;
01780        long SourceLen;
01781        long EncodedMaxLen;
01782        long nColons = 0;
01783        long *AddrPtr;
01784        long *AddrUtf8;
01785        long nAddrPtrMax = 50;
01786        long nmax;
01787        int InQuotes = 0;
01788        int i, n;
01789 
01790        if (source == NULL) return source;
01791        if (IsEmptyStr(source)) return source;
01792        cit_backtrace();
01793        MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
01794 
01795        AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
01796        AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
01797        memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
01798        *AddrPtr = 0;
01799        i = 0;
01800        while (!IsEmptyStr (&source[i])) {
01801               if (nColons >= nAddrPtrMax){
01802                      long *ptr;
01803 
01804                      ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
01805                      memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
01806                      free (AddrPtr), AddrPtr = ptr;
01807 
01808                      ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
01809                      memset(&ptr[nAddrPtrMax], 0, 
01810                             sizeof (long) * nAddrPtrMax);
01811 
01812                      memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
01813                      free (AddrUtf8), AddrUtf8 = ptr;
01814                      nAddrPtrMax *= 2;                         
01815               }
01816               if (((unsigned char) source[i] < 32) || 
01817                   ((unsigned char) source[i] > 126)) {
01818                      need_to_encode = 1;
01819                      AddrUtf8[nColons] = 1;
01820               }
01821               if (source[i] == '"')
01822                      InQuotes = !InQuotes;
01823               if (!InQuotes && source[i] == ',') {
01824                      AddrPtr[nColons] = i;
01825                      nColons++;
01826               }
01827               i++;
01828        }
01829        if (need_to_encode == 0) {
01830               free(AddrPtr);
01831               free(AddrUtf8);
01832               return source;
01833        }
01834 
01835        SourceLen = i;
01836        EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
01837        Encoded = (char*) malloc (EncodedMaxLen);
01838 
01839        for (i = 0; i < nColons; i++)
01840               source[AddrPtr[i]++] = '\0';
01841        /* TODO: if libidn, this might get larger*/
01842        user = malloc(SourceLen + 1);
01843        node = malloc(SourceLen + 1);
01844        name = malloc(SourceLen + 1);
01845 
01846        nPtr = Encoded;
01847        *nPtr = '\0';
01848        for (i = 0; i < nColons && nPtr != NULL; i++) {
01849               nmax = EncodedMaxLen - (nPtr - Encoded);
01850               if (AddrUtf8[i]) {
01851                      process_rfc822_addr(&source[AddrPtr[i]], 
01852                                        user,
01853                                        node,
01854                                        name);
01855                      /* TODO: libIDN here ! */
01856                      if (IsEmptyStr(name)) {
01857                             n = snprintf(nPtr, nmax, 
01858                                         (i==0)?"%s@%s" : ",%s@%s",
01859                                         user, node);
01860                      }
01861                      else {
01862                             EncodedName = rfc2047encode(name, strlen(name));               
01863                             n = snprintf(nPtr, nmax, 
01864                                         (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
01865                                         EncodedName, user, node);
01866                             free(EncodedName);
01867                      }
01868               }
01869               else { 
01870                      n = snprintf(nPtr, nmax, 
01871                                  (i==0)?"%s" : ",%s",
01872                                  &source[AddrPtr[i]]);
01873               }
01874               if (n > 0 )
01875                      nPtr += n;
01876               else { 
01877                      char *ptr, *nnPtr;
01878                      ptr = (char*) malloc(EncodedMaxLen * 2);
01879                      memcpy(ptr, Encoded, EncodedMaxLen);
01880                      nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
01881                      free(Encoded), Encoded = ptr;
01882                      EncodedMaxLen *= 2;
01883                      i--; /* do it once more with properly lengthened buffer */
01884               }
01885        }
01886        for (i = 0; i < nColons; i++)
01887               source[--AddrPtr[i]] = ',';
01888 
01889        free(user);
01890        free(node);
01891        free(name);
01892        free(AddrUtf8);
01893        free(AddrPtr);
01894        return Encoded;
01895 }
01896 
01897 
01898 /* If the last item in a list of recipients was truncated to a partial address,
01899  * remove it completely in order to avoid choking libSieve
01900  */
01901 void sanitize_truncated_recipient(char *str)
01902 {
01903        if (!str) return;
01904        if (num_tokens(str, ',') < 2) return;
01905 
01906        int len = strlen(str);
01907        if (len < 900) return;
01908        if (len > 998) str[998] = 0;
01909 
01910        char *cptr = strrchr(str, ',');
01911        if (!cptr) return;
01912 
01913        char *lptr = strchr(cptr, '<');
01914        char *rptr = strchr(cptr, '>');
01915 
01916        if ( (lptr) && (rptr) && (rptr > lptr) ) return;
01917 
01918        *cptr = 0;
01919 }
01920 
01921 
01922 void OutputCtdlMsgHeaders(
01923        struct CtdlMessage *TheMessage,
01924        int do_proto)        /* do Citadel protocol responses? */
01925 {
01926        char allkeys[30];
01927        int i, k, n;
01928        int suppress_f = 0;
01929        char buf[SIZ];
01930        char display_name[256];
01931 
01932        /* begin header processing loop for Citadel message format */
01933        safestrncpy(display_name, "<unknown>", sizeof display_name);
01934        if (TheMessage->cm_fields['A']) {
01935               strcpy(buf, TheMessage->cm_fields['A']);
01936               if (TheMessage->cm_anon_type == MES_ANONONLY) {
01937                      safestrncpy(display_name, "****", sizeof display_name);
01938               }
01939               else if (TheMessage->cm_anon_type == MES_ANONOPT) {
01940                      safestrncpy(display_name, "anonymous", sizeof display_name);
01941               }
01942               else {
01943                      safestrncpy(display_name, buf, sizeof display_name);
01944               }
01945               if ((is_room_aide())
01946                   && ((TheMessage->cm_anon_type == MES_ANONONLY)
01947                      || (TheMessage->cm_anon_type == MES_ANONOPT))) {
01948                      size_t tmp = strlen(display_name);
01949                      snprintf(&display_name[tmp],
01950                              sizeof display_name - tmp,
01951                              " [%s]", buf);
01952               }
01953        }
01954 
01955        /* Don't show Internet address for users on the
01956         * local Citadel network.
01957         */
01958        suppress_f = 0;
01959        if (TheMessage->cm_fields['N'] != NULL)
01960               if (!IsEmptyStr(TheMessage->cm_fields['N']))
01961                      if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
01962                             suppress_f = 1;
01963                      }
01964 
01965        /* Now spew the header fields in the order we like them. */
01966        n = safestrncpy(allkeys, FORDER, sizeof allkeys);
01967        for (i=0; i<n; ++i) {
01968               k = (int) allkeys[i];
01969               if (k != 'M') {
01970                      if ( (TheMessage->cm_fields[k] != NULL)
01971                           && (msgkeys[k] != NULL) ) {
01972                             if ((k == 'V') || (k == 'R') || (k == 'Y')) {
01973                                    sanitize_truncated_recipient(TheMessage->cm_fields[k]);
01974                             }
01975                             if (k == 'A') {
01976                                    if (do_proto) cprintf("%s=%s\n",
01977                                                        msgkeys[k],
01978                                                        display_name);
01979                             }
01980                             else if ((k == 'F') && (suppress_f)) {
01981                                    /* do nothing */
01982                             }
01983                             /* Masquerade display name if needed */
01984                             else {
01985                                    if (do_proto) cprintf("%s=%s\n",
01986                                                        msgkeys[k],
01987                                                        TheMessage->cm_fields[k]
01988                                           );
01989                             }
01990                      }
01991               }
01992        }
01993 
01994 }
01995 
01996 void OutputRFC822MsgHeaders(
01997        struct CtdlMessage *TheMessage,
01998        int flags,           /* should the bessage be exported clean   */
01999        const char *nl,
02000        char *mid, long sizeof_mid,
02001        char *suser, long sizeof_suser,
02002        char *luser, long sizeof_luser,
02003        char *fuser, long sizeof_fuser,
02004        char *snode, long sizeof_snode)
02005 {
02006        char datestamp[100];
02007        int subject_found = 0;
02008        char buf[SIZ];
02009        int i, j, k;
02010        char *mptr = NULL;
02011        char *mpptr = NULL;
02012        char *hptr;
02013 
02014        for (i = 0; i < 256; ++i) {
02015               if (TheMessage->cm_fields[i]) {
02016                      mptr = mpptr = TheMessage->cm_fields[i];
02017                             
02018                      if (i == 'A') {
02019                             safestrncpy(luser, mptr, sizeof_luser);
02020                             safestrncpy(suser, mptr, sizeof_suser);
02021                      }
02022                      else if (i == 'Y') {
02023                             if ((flags & QP_EADDR) != 0) {
02024                                    mptr = qp_encode_email_addrs(mptr);
02025                             }
02026                             sanitize_truncated_recipient(mptr);
02027                             cprintf("CC: %s%s", mptr, nl);
02028                      }
02029                      else if (i == 'P') {
02030                             cprintf("Return-Path: %s%s", mptr, nl);
02031                      }
02032                      else if (i == 'L') {
02033                             cprintf("List-ID: %s%s", mptr, nl);
02034                      }
02035                      else if (i == 'V') {
02036                             if ((flags & QP_EADDR) != 0) 
02037                                    mptr = qp_encode_email_addrs(mptr);
02038                             hptr = mptr;
02039                             while ((*hptr != '\0') && isspace(*hptr))
02040                                    hptr ++;
02041                             if (!IsEmptyStr(hptr))
02042                                    cprintf("Envelope-To: %s%s", hptr, nl);
02043                      }
02044                      else if (i == 'U') {
02045                             cprintf("Subject: %s%s", mptr, nl);
02046                             subject_found = 1;
02047                      }
02048                      else if (i == 'I')
02049                             safestrncpy(mid, mptr, sizeof_mid); 
02050                      else if (i == 'F')
02051                             safestrncpy(fuser, mptr, sizeof_fuser);
02052                      /* else if (i == 'O')
02053                         cprintf("X-Citadel-Room: %s%s",
02054                         mptr, nl); */
02055                      else if (i == 'N')
02056                             safestrncpy(snode, mptr, sizeof_snode);
02057                      else if (i == 'R')
02058                      {
02059                             if (haschar(mptr, '@') == 0)
02060                             {
02061                                    sanitize_truncated_recipient(mptr);
02062                                    cprintf("To: %s@%s", mptr, config.c_fqdn);
02063                                    cprintf("%s", nl);
02064                             }
02065                             else
02066                             {
02067                                    if ((flags & QP_EADDR) != 0) {
02068                                           mptr = qp_encode_email_addrs(mptr);
02069                                    }
02070                                    sanitize_truncated_recipient(mptr);
02071                                    cprintf("To: %s", mptr);
02072                                    cprintf("%s", nl);
02073                             }
02074                      }
02075                      else if (i == 'T') {
02076                             datestring(datestamp, sizeof datestamp,
02077                                       atol(mptr), DATESTRING_RFC822);
02078                             cprintf("Date: %s%s", datestamp, nl);
02079                      }
02080                      else if (i == 'W') {
02081                             cprintf("References: ");
02082                             k = num_tokens(mptr, '|');
02083                             for (j=0; j<k; ++j) {
02084                                    extract_token(buf, mptr, j, '|', sizeof buf);
02085                                    cprintf("<%s>", buf);
02086                                    if (j == (k-1)) {
02087                                           cprintf("%s", nl);
02088                                    }
02089                                    else {
02090                                           cprintf(" ");
02091                                    }
02092                             }
02093                      }
02094                      else if (i == 'K') {
02095                             hptr = mptr;
02096                             while ((*hptr != '\0') && isspace(*hptr))
02097                                    hptr ++;
02098                             if (!IsEmptyStr(hptr))
02099                                    cprintf("Reply-To: %s%s", mptr, nl);
02100                      }
02101                      if (mptr != mpptr)
02102                             free (mptr);
02103               }
02104        }
02105        if (subject_found == 0) {
02106               cprintf("Subject: (no subject)%s", nl);
02107        }
02108 }
02109 
02110 
02111 void Dump_RFC822HeadersBody(
02112        struct CtdlMessage *TheMessage,
02113        int headers_only,    /* eschew the message body? */
02114        int flags,           /* should the bessage be exported clean? */
02115 
02116        const char *nl)
02117 {
02118        cit_uint8_t prev_ch;
02119        int eoh = 0;
02120        const char *StartOfText = StrBufNOTNULL;
02121        char outbuf[1024];
02122        int outlen = 0;
02123        int nllen = strlen(nl);
02124        char *mptr;
02125 
02126        mptr = TheMessage->cm_fields['M'];
02127 
02128 
02129        prev_ch = '\0';
02130        while (*mptr != '\0') {
02131               if (*mptr == '\r') {
02132                      /* do nothing */
02133               }
02134               else {
02135                      if ((!eoh) &&
02136                          (*mptr == '\n'))
02137                      {
02138                             eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
02139                             if (!eoh)
02140                                    eoh = *(mptr+1) == '\n';
02141                             if (eoh)
02142                             {
02143                                    StartOfText = mptr;
02144                                    StartOfText = strchr(StartOfText, '\n');
02145                                    StartOfText = strchr(StartOfText, '\n');
02146                             }
02147                      }
02148                      if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
02149                          ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
02150                          ((headers_only != HEADERS_NONE) && 
02151                           (headers_only != HEADERS_ONLY))
02152                             ) {
02153                             if (*mptr == '\n') {
02154                                    memcpy(&outbuf[outlen], nl, nllen);
02155                                    outlen += nllen;
02156                                    outbuf[outlen] = '\0';
02157                             }
02158                             else {
02159                                    outbuf[outlen++] = *mptr;
02160                             }
02161                      }
02162               }
02163               if (flags & ESC_DOT)
02164               {
02165                      if ((prev_ch == '\n') && 
02166                          (*mptr == '.') && 
02167                          ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
02168                      {
02169                             outbuf[outlen++] = '.';
02170                      }
02171                      prev_ch = *mptr;
02172               }
02173               ++mptr;
02174               if (outlen > 1000) {
02175                      if (client_write(outbuf, outlen) == -1)
02176                      {
02177                             struct CitContext *CCC = CC;
02178                             MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
02179                             return;
02180                      }
02181                      outlen = 0;
02182               }
02183        }
02184        if (outlen > 0) {
02185               client_write(outbuf, outlen);
02186        }
02187 }
02188 
02189 
02190 
02191 /* If the format type on disk is 1 (fixed-format), then we want
02192  * everything to be output completely literally ... regardless of
02193  * what message transfer format is in use.
02194  */
02195 void DumpFormatFixed(
02196        struct CtdlMessage *TheMessage,
02197        int mode,            /* how would you like that message? */
02198        const char *nl)
02199 {
02200        cit_uint8_t ch;
02201        char buf[SIZ];
02202        int buflen;
02203        int xlline = 0;
02204        int nllen = strlen (nl);
02205        char *mptr;
02206 
02207        mptr = TheMessage->cm_fields['M'];
02208        
02209        if (mode == MT_MIME) {
02210               cprintf("Content-type: text/plain\n\n");
02211        }
02212        *buf = '\0';
02213        buflen = 0;
02214        while (ch = *mptr++, ch > 0) {
02215               if (ch == '\n')
02216                      ch = '\r';
02217 
02218               if ((buflen > 250) && (!xlline)){
02219                      int tbuflen;
02220                      tbuflen = buflen;
02221 
02222                      while ((buflen > 0) && 
02223                             (!isspace(buf[buflen])))
02224                             buflen --;
02225                      if (buflen == 0) {
02226                             xlline = 1;
02227                      }
02228                      else {
02229                             mptr -= tbuflen - buflen;
02230                             buf[buflen] = '\0';
02231                             ch = '\r';
02232                      }
02233               }
02234               /* if we reach the outer bounds of our buffer, 
02235                  abort without respect what whe purge. */
02236               if (xlline && 
02237                   ((isspace(ch)) || 
02238                    (buflen > SIZ - nllen - 2)))
02239                      ch = '\r';
02240 
02241               if (ch == '\r') {
02242                      memcpy (&buf[buflen], nl, nllen);
02243                      buflen += nllen;
02244                      buf[buflen] = '\0';
02245 
02246                      if (client_write(buf, buflen) == -1)
02247                      {
02248                             struct CitContext *CCC = CC;
02249                             MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
02250                             return;
02251                      }
02252                      *buf = '\0';
02253                      buflen = 0;
02254                      xlline = 0;
02255               } else {
02256                      buf[buflen] = ch;
02257                      buflen++;
02258               }
02259        }
02260        buf[buflen] = '\0';
02261        if (!IsEmptyStr(buf))
02262               cprintf("%s%s", buf, nl);
02263 }
02264 
02265 /*
02266  * Get a message off disk.  (returns om_* values found in msgbase.h)
02267  */
02268 int CtdlOutputPreLoadedMsg(
02269               struct CtdlMessage *TheMessage,
02270               int mode,            /* how would you like that message? */
02271               int headers_only,    /* eschew the message body? */
02272               int do_proto,        /* do Citadel protocol responses? */
02273               int crlf,            /* Use CRLF newlines instead of LF? */
02274               int flags            /* should the bessage be exported clean? */
02275 ) {
02276        struct CitContext *CCC = CC;
02277        int i;
02278        char *mptr = NULL;
02279        const char *nl;      /* newline string */
02280        struct ma_info ma;
02281 
02282        /* Buffers needed for RFC822 translation.  These are all filled
02283         * using functions that are bounds-checked, and therefore we can
02284         * make them substantially smaller than SIZ.
02285         */
02286        char suser[100];
02287        char luser[100];
02288        char fuser[100];
02289        char snode[100];
02290        char mid[100];
02291 
02292        MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
02293                  ((TheMessage == NULL) ? "NULL" : "not null"),
02294                  mode, headers_only, do_proto, crlf);
02295 
02296        strcpy(mid, "unknown");
02297        nl = (crlf ? "\r\n" : "\n");
02298 
02299        if (!is_valid_message(TheMessage)) {
02300               MSGM_syslog(LOG_ERR,
02301                          "ERROR: invalid preloaded message for output\n");
02302               cit_backtrace ();
02303               return(om_no_such_msg);
02304        }
02305 
02306        /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
02307         * Pad it with spaces in order to avoid changing the RFC822 length of the message.
02308         */
02309        if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
02310               memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
02311        }
02312               
02313        /* Are we downloading a MIME component? */
02314        if (mode == MT_DOWNLOAD) {
02315               if (TheMessage->cm_format_type != FMT_RFC822) {
02316                      if (do_proto)
02317                             cprintf("%d This is not a MIME message.\n",
02318                             ERROR + ILLEGAL_VALUE);
02319               } else if (CCC->download_fp != NULL) {
02320                      if (do_proto) cprintf(
02321                             "%d You already have a download open.\n",
02322                             ERROR + RESOURCE_BUSY);
02323               } else {
02324                      /* Parse the message text component */
02325                      mptr = TheMessage->cm_fields['M'];
02326                      mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
02327                      /* If there's no file open by this time, the requested
02328                       * section wasn't found, so print an error
02329                       */
02330                      if (CCC->download_fp == NULL) {
02331                             if (do_proto) cprintf(
02332                                    "%d Section %s not found.\n",
02333                                    ERROR + FILE_NOT_FOUND,
02334                                    CCC->download_desired_section);
02335                      }
02336               }
02337               return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
02338        }
02339 
02340        /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
02341         * in a single server operation instead of opening a download file.
02342         */
02343        if (mode == MT_SPEW_SECTION) {
02344               if (TheMessage->cm_format_type != FMT_RFC822) {
02345                      if (do_proto)
02346                             cprintf("%d This is not a MIME message.\n",
02347                             ERROR + ILLEGAL_VALUE);
02348               } else {
02349                      /* Parse the message text component */
02350                      int found_it = 0;
02351 
02352                      mptr = TheMessage->cm_fields['M'];
02353                      mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
02354                      /* If section wasn't found, print an error
02355                       */
02356                      if (!found_it) {
02357                             if (do_proto) cprintf(
02358                                    "%d Section %s not found.\n",
02359                                    ERROR + FILE_NOT_FOUND,
02360                                    CCC->download_desired_section);
02361                      }
02362               }
02363               return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
02364        }
02365 
02366        /* now for the user-mode message reading loops */
02367        if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
02368 
02369        /* Does the caller want to skip the headers? */
02370        if (headers_only == HEADERS_NONE) goto START_TEXT;
02371 
02372        /* Tell the client which format type we're using. */
02373        if ( (mode == MT_CITADEL) && (do_proto) ) {
02374               cprintf("type=%d\n", TheMessage->cm_format_type);
02375        }
02376 
02377        /* nhdr=yes means that we're only displaying headers, no body */
02378        if ( (TheMessage->cm_anon_type == MES_ANONONLY)
02379           && ((mode == MT_CITADEL) || (mode == MT_MIME))
02380           && (do_proto)
02381           ) {
02382               cprintf("nhdr=yes\n");
02383        }
02384 
02385        if ((mode == MT_CITADEL) || (mode == MT_MIME)) 
02386               OutputCtdlMsgHeaders(TheMessage, do_proto);
02387 
02388 
02389        /* begin header processing loop for RFC822 transfer format */
02390        strcpy(suser, "");
02391        strcpy(luser, "");
02392        strcpy(fuser, "");
02393        strcpy(snode, NODENAME);
02394        if (mode == MT_RFC822) 
02395               OutputRFC822MsgHeaders(
02396                      TheMessage,
02397                      flags,
02398                      nl,
02399                      mid, sizeof(mid),
02400                      suser, sizeof(suser),
02401                      luser, sizeof(luser),
02402                      fuser, sizeof(fuser),
02403                      snode, sizeof(snode)
02404                      );
02405 
02406 
02407        for (i=0; !IsEmptyStr(&suser[i]); ++i) {
02408               suser[i] = tolower(suser[i]);
02409               if (!isalnum(suser[i])) suser[i]='_';
02410        }
02411 
02412        if (mode == MT_RFC822) {
02413               if (!strcasecmp(snode, NODENAME)) {
02414                      safestrncpy(snode, FQDN, sizeof snode);
02415               }
02416 
02417               /* Construct a fun message id */
02418               cprintf("Message-ID: <%s", mid);
02419               if (strchr(mid, '@')==NULL) {
02420                      cprintf("@%s", snode);
02421               }
02422               cprintf(">%s", nl);
02423 
02424               if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
02425                      cprintf("From: \"----\" <x@x.org>%s", nl);
02426               }
02427               else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
02428                      cprintf("From: \"anonymous\" <x@x.org>%s", nl);
02429               }
02430               else if (!IsEmptyStr(fuser)) {
02431                      cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
02432               }
02433               else {
02434                      cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
02435               }
02436 
02437               /* Blank line signifying RFC822 end-of-headers */
02438               if (TheMessage->cm_format_type != FMT_RFC822) {
02439                      cprintf("%s", nl);
02440               }
02441        }
02442 
02443        /* end header processing loop ... at this point, we're in the text */
02444 START_TEXT:
02445        if (headers_only == HEADERS_FAST) goto DONE;
02446 
02447        /* Tell the client about the MIME parts in this message */
02448        if (TheMessage->cm_format_type == FMT_RFC822) {
02449               if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
02450                      mptr = TheMessage->cm_fields['M'];
02451                      memset(&ma, 0, sizeof(struct ma_info));
02452                      mime_parser(mptr, NULL,
02453                             (do_proto ? *list_this_part : NULL),
02454                             (do_proto ? *list_this_pref : NULL),
02455                             (do_proto ? *list_this_suff : NULL),
02456                             (void *)&ma, 1);
02457               }
02458               else if (mode == MT_RFC822) {      /* unparsed RFC822 dump */
02459                      Dump_RFC822HeadersBody(
02460                             TheMessage,
02461                             headers_only,
02462                             flags,
02463                             nl);
02464                      goto DONE;
02465               }
02466        }
02467 
02468        if (headers_only == HEADERS_ONLY) {
02469               goto DONE;
02470        }
02471 
02472        /* signify start of msg text */
02473        if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
02474               if (do_proto) cprintf("text\n");
02475        }
02476 
02477        if (TheMessage->cm_format_type == FMT_FIXED) 
02478               DumpFormatFixed(
02479                      TheMessage,
02480                      mode,         /* how would you like that message? */
02481                      nl);
02482 
02483        /* If the message on disk is format 0 (Citadel vari-format), we
02484         * output using the formatter at 80 columns.  This is the final output
02485         * form if the transfer format is RFC822, but if the transfer format
02486         * is Citadel proprietary, it'll still work, because the indentation
02487         * for new paragraphs is correct and the client will reformat the
02488         * message to the reader's screen width.
02489         */
02490        if (TheMessage->cm_format_type == FMT_CITADEL) {
02491               mptr = TheMessage->cm_fields['M'];
02492 
02493               if (mode == MT_MIME) {
02494                      cprintf("Content-type: text/x-citadel-variformat\n\n");
02495               }
02496               memfmout(mptr, nl);
02497        }
02498 
02499        /* If the message on disk is format 4 (MIME), we've gotta hand it
02500         * off to the MIME parser.  The client has already been told that
02501         * this message is format 1 (fixed format), so the callback function
02502         * we use will display those parts as-is.
02503         */
02504        if (TheMessage->cm_format_type == FMT_RFC822) {
02505               memset(&ma, 0, sizeof(struct ma_info));
02506 
02507               if (mode == MT_MIME) {
02508                      ma.use_fo_hooks = 0;
02509                      strcpy(ma.chosen_part, "1");
02510                      ma.chosen_pref = 9999;
02511                      ma.dont_decode = CCC->msg4_dont_decode;
02512                      mime_parser(mptr, NULL,
02513                             *choose_preferred, *fixed_output_pre,
02514                             *fixed_output_post, (void *)&ma, 1);
02515                      mime_parser(mptr, NULL,
02516                             *output_preferred, NULL, NULL, (void *)&ma, 1);
02517               }
02518               else {
02519                      ma.use_fo_hooks = 1;
02520                      mime_parser(mptr, NULL,
02521                             *fixed_output, *fixed_output_pre,
02522                             *fixed_output_post, (void *)&ma, 0);
02523               }
02524 
02525        }
02526 
02527 DONE:  /* now we're done */
02528        if (do_proto) cprintf("000\n");
02529        return(om_ok);
02530 }
02531 
02532 
02533 /*
02534  * display a message (mode 0 - Citadel proprietary)
02535  */
02536 void cmd_msg0(char *cmdbuf)
02537 {
02538        long msgid;
02539        int headers_only = HEADERS_ALL;
02540 
02541        msgid = extract_long(cmdbuf, 0);
02542        headers_only = extract_int(cmdbuf, 1);
02543 
02544        CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
02545        return;
02546 }
02547 
02548 
02549 /*
02550  * display a message (mode 2 - RFC822)
02551  */
02552 void cmd_msg2(char *cmdbuf)
02553 {
02554        long msgid;
02555        int headers_only = HEADERS_ALL;
02556 
02557        msgid = extract_long(cmdbuf, 0);
02558        headers_only = extract_int(cmdbuf, 1);
02559 
02560        CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
02561 }
02562 
02563 
02564 
02565 /* 
02566  * display a message (mode 3 - IGnet raw format - internal programs only)
02567  */
02568 void cmd_msg3(char *cmdbuf)
02569 {
02570        long msgnum;
02571        struct CtdlMessage *msg = NULL;
02572        struct ser_ret smr;
02573 
02574        if (CC->internal_pgm == 0) {
02575               cprintf("%d This command is for internal programs only.\n",
02576                      ERROR + HIGHER_ACCESS_REQUIRED);
02577               return;
02578        }
02579 
02580        msgnum = extract_long(cmdbuf, 0);
02581        msg = CtdlFetchMessage(msgnum, 1);
02582        if (msg == NULL) {
02583               cprintf("%d Message %ld not found.\n", 
02584                      ERROR + MESSAGE_NOT_FOUND, msgnum);
02585               return;
02586        }
02587 
02588        serialize_message(&smr, msg);
02589        CtdlFreeMessage(msg);
02590 
02591        if (smr.len == 0) {
02592               cprintf("%d Unable to serialize message\n",
02593                      ERROR + INTERNAL_ERROR);
02594               return;
02595        }
02596 
02597        cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
02598        client_write((char *)smr.ser, (int)smr.len);
02599        free(smr.ser);
02600 }
02601 
02602 
02603 
02604 /* 
02605  * Display a message using MIME content types
02606  */
02607 void cmd_msg4(char *cmdbuf)
02608 {
02609        long msgid;
02610        char section[64];
02611 
02612        msgid = extract_long(cmdbuf, 0);
02613        extract_token(section, cmdbuf, 1, '|', sizeof section);
02614        CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
02615 }
02616 
02617 
02618 
02619 /* 
02620  * Client tells us its preferred message format(s)
02621  */
02622 void cmd_msgp(char *cmdbuf)
02623 {
02624        if (!strcasecmp(cmdbuf, "dont_decode")) {
02625               CC->msg4_dont_decode = 1;
02626               cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
02627        }
02628        else {
02629               safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
02630               cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
02631        }
02632 }
02633 
02634 
02635 /*
02636  * Open a component of a MIME message as a download file 
02637  */
02638 void cmd_opna(char *cmdbuf)
02639 {
02640        long msgid;
02641        char desired_section[128];
02642 
02643        msgid = extract_long(cmdbuf, 0);
02644        extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
02645        safestrncpy(CC->download_desired_section, desired_section,
02646               sizeof CC->download_desired_section);
02647        CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
02648 }                    
02649 
02650 
02651 /*
02652  * Open a component of a MIME message and transmit it all at once
02653  */
02654 void cmd_dlat(char *cmdbuf)
02655 {
02656        long msgid;
02657        char desired_section[128];
02658 
02659        msgid = extract_long(cmdbuf, 0);
02660        extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
02661        safestrncpy(CC->download_desired_section, desired_section,
02662               sizeof CC->download_desired_section);
02663        CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
02664 }                    
02665 
02666 
02667 /*
02668  * Save one or more message pointers into a specified room
02669  * (Returns 0 for success, nonzero for failure)
02670  * roomname may be NULL to use the current room
02671  *
02672  * Note that the 'supplied_msg' field may be set to NULL, in which case
02673  * the message will be fetched from disk, by number, if we need to perform
02674  * replication checks.  This adds an additional database read, so if the
02675  * caller already has the message in memory then it should be supplied.  (Obviously
02676  * this mode of operation only works if we're saving a single message.)
02677  */
02678 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
02679                      int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
02680 ) {
02681        struct CitContext *CCC = CC;
02682        int i, j, unique;
02683        char hold_rm[ROOMNAMELEN];
02684        struct cdbdata *cdbfr;
02685        int num_msgs;
02686        long *msglist;
02687        long highest_msg = 0L;
02688 
02689        long msgid = 0;
02690        struct CtdlMessage *msg = NULL;
02691 
02692        long *msgs_to_be_merged = NULL;
02693        int num_msgs_to_be_merged = 0;
02694 
02695        MSG_syslog(LOG_DEBUG,
02696                  "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
02697                  roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
02698        );
02699 
02700        strcpy(hold_rm, CCC->room.QRname);
02701 
02702        /* Sanity checks */
02703        if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
02704        if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
02705        if (num_newmsgs > 1) supplied_msg = NULL;
02706 
02707        /* Now the regular stuff */
02708        if (CtdlGetRoomLock(&CCC->room,
02709           ((roomname != NULL) ? roomname : CCC->room.QRname) )
02710           != 0) {
02711               MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
02712               return(ERROR + ROOM_NOT_FOUND);
02713        }
02714 
02715 
02716        msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
02717        num_msgs_to_be_merged = 0;
02718 
02719 
02720        cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
02721        if (cdbfr == NULL) {
02722               msglist = NULL;
02723               num_msgs = 0;
02724        } else {
02725               msglist = (long *) cdbfr->ptr;
02726               cdbfr->ptr = NULL;   /* CtdlSaveMsgPointerInRoom() now owns this memory */
02727               num_msgs = cdbfr->len / sizeof(long);
02728               cdb_free(cdbfr);
02729        }
02730 
02731 
02732        /* Create a list of msgid's which were supplied by the caller, but do
02733         * not already exist in the target room.  It is absolutely taboo to
02734         * have more than one reference to the same message in a room.
02735         */
02736        for (i=0; i<num_newmsgs; ++i) {
02737               unique = 1;
02738               if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
02739                      if (msglist[j] == newmsgidlist[i]) {
02740                             unique = 0;
02741                      }
02742               }
02743               if (unique) {
02744                      msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
02745               }
02746        }
02747 
02748        MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
02749 
02750        /*
02751         * Now merge the new messages
02752         */
02753        msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
02754        if (msglist == NULL) {
02755               MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
02756               free(msgs_to_be_merged);
02757               return (ERROR + INTERNAL_ERROR);
02758        }
02759        memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
02760        num_msgs += num_msgs_to_be_merged;
02761 
02762        /* Sort the message list, so all the msgid's are in order */
02763        num_msgs = sort_msglist(msglist, num_msgs);
02764 
02765        /* Determine the highest message number */
02766        highest_msg = msglist[num_msgs - 1];
02767 
02768        /* Write it back to disk. */
02769        cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
02770                 msglist, (int)(num_msgs * sizeof(long)));
02771 
02772        /* Free up the memory we used. */
02773        free(msglist);
02774 
02775        /* Update the highest-message pointer and unlock the room. */
02776        CCC->room.QRhighest = highest_msg;
02777        CtdlPutRoomLock(&CCC->room);
02778 
02779        /* Perform replication checks if necessary */
02780        if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
02781               MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
02782 
02783               for (i=0; i<num_msgs_to_be_merged; ++i) {
02784                      msgid = msgs_to_be_merged[i];
02785        
02786                      if (supplied_msg != NULL) {
02787                             msg = supplied_msg;
02788                      }
02789                      else {
02790                             msg = CtdlFetchMessage(msgid, 0);
02791                      }
02792        
02793                      if (msg != NULL) {
02794                             ReplicationChecks(msg);
02795               
02796                             /* If the message has an Exclusive ID, index that... */
02797                             if (msg->cm_fields['E'] != NULL) {
02798                                    index_message_by_euid(msg->cm_fields['E'], &CCC->room, msgid);
02799                             }
02800 
02801                             /* Free up the memory we may have allocated */
02802                             if (msg != supplied_msg) {
02803                                    CtdlFreeMessage(msg);
02804                             }
02805                      }
02806        
02807               }
02808        }
02809 
02810        else {
02811               MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
02812        }
02813 
02814        /* Submit this room for processing by hooks */
02815        PerformRoomHooks(&CCC->room);
02816 
02817        /* Go back to the room we were in before we wandered here... */
02818        CtdlGetRoom(&CCC->room, hold_rm);
02819 
02820        /* Bump the reference count for all messages which were merged */
02821        if (!suppress_refcount_adj) {
02822               AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
02823        }
02824 
02825        /* Free up memory... */
02826        if (msgs_to_be_merged != NULL) {
02827               free(msgs_to_be_merged);
02828        }
02829 
02830        /* Return success. */
02831        return (0);
02832 }
02833 
02834 
02835 /*
02836  * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
02837  * a single message.
02838  */
02839 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
02840                           int do_repl_check, struct CtdlMessage *supplied_msg)
02841 {
02842        return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
02843 }
02844 
02845 
02846 
02847 
02848 /*
02849  * Message base operation to save a new message to the message store
02850  * (returns new message number)
02851  *
02852  * This is the back end for CtdlSubmitMsg() and should not be directly
02853  * called by server-side modules.
02854  *
02855  */
02856 long send_message(struct CtdlMessage *msg) {
02857        struct CitContext *CCC = CC;
02858        long newmsgid;
02859        long retval;
02860        char msgidbuf[256];
02861        struct ser_ret smr;
02862        int is_bigmsg = 0;
02863        char *holdM = NULL;
02864 
02865        /* Get a new message number */
02866        newmsgid = get_new_message_number();
02867        snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
02868                (long unsigned int) time(NULL),
02869                (long unsigned int) newmsgid,
02870                config.c_fqdn
02871               );
02872 
02873        /* Generate an ID if we don't have one already */
02874        if (msg->cm_fields['I']==NULL) {
02875               msg->cm_fields['I'] = strdup(msgidbuf);
02876        }
02877 
02878        /* If the message is big, set its body aside for storage elsewhere */
02879        if (msg->cm_fields['M'] != NULL) {
02880               if (strlen(msg->cm_fields['M']) > BIGMSG) {
02881                      is_bigmsg = 1;
02882                      holdM = msg->cm_fields['M'];
02883                      msg->cm_fields['M'] = NULL;
02884               }
02885        }
02886 
02887        /* Serialize our data structure for storage in the database */ 
02888        serialize_message(&smr, msg);
02889 
02890        if (is_bigmsg) {
02891               msg->cm_fields['M'] = holdM;
02892        }
02893 
02894        if (smr.len == 0) {
02895               cprintf("%d Unable to serialize message\n",
02896                      ERROR + INTERNAL_ERROR);
02897               return (-1L);
02898        }
02899 
02900        /* Write our little bundle of joy into the message base */
02901        if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
02902                     smr.ser, smr.len) < 0) {
02903               MSGM_syslog(LOG_ERR, "Can't store message\n");
02904               retval = 0L;
02905        } else {
02906               if (is_bigmsg) {
02907                      cdb_store(CDB_BIGMSGS,
02908                               &newmsgid,
02909                               (int)sizeof(long),
02910                               holdM,
02911                               (strlen(holdM) + 1)
02912                             );
02913               }
02914               retval = newmsgid;
02915        }
02916 
02917        /* Free the memory we used for the serialized message */
02918        free(smr.ser);
02919 
02920        /* Return the *local* message ID to the caller
02921         * (even if we're storing an incoming network message)
02922         */
02923        return(retval);
02924 }
02925 
02926 
02927 
02928 /*
02929  * Serialize a struct CtdlMessage into the format used on disk and network.
02930  * 
02931  * This function loads up a "struct ser_ret" (defined in server.h) which
02932  * contains the length of the serialized message and a pointer to the
02933  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
02934  */
02935 void serialize_message(struct ser_ret *ret,             /* return values */
02936                      struct CtdlMessage *msg)    /* unserialized msg */
02937 {
02938        struct CitContext *CCC = CC;
02939        size_t wlen, fieldlen;
02940        int i;
02941        static char *forder = FORDER;
02942 
02943        /*
02944         * Check for valid message format
02945         */
02946        if (is_valid_message(msg) == 0) {
02947               MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
02948               ret->len = 0;
02949               ret->ser = NULL;
02950               return;
02951        }
02952 
02953        ret->len = 3;
02954        for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
02955                                  ret->len = ret->len +
02956                                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
02957 
02958        ret->ser = malloc(ret->len);
02959        if (ret->ser == NULL) {
02960               MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
02961                         (long)ret->len, strerror(errno));
02962               ret->len = 0;
02963               ret->ser = NULL;
02964               return;
02965        }
02966 
02967        ret->ser[0] = 0xFF;
02968        ret->ser[1] = msg->cm_anon_type;
02969        ret->ser[2] = msg->cm_format_type;
02970        wlen = 3;
02971 
02972        for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
02973                      fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
02974                      ret->ser[wlen++] = (char)forder[i];
02975                      safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
02976                      wlen = wlen + fieldlen + 1;
02977               }
02978        if (ret->len != wlen) {
02979               MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
02980                         (long)ret->len, (long)wlen);
02981        }
02982 
02983        return;
02984 }
02985 
02986 
02987 /*
02988  * Check to see if any messages already exist in the current room which
02989  * carry the same Exclusive ID as this one.  If any are found, delete them.
02990  */
02991 void ReplicationChecks(struct CtdlMessage *msg) {
02992        struct CitContext *CCC = CC;
02993        long old_msgnum = (-1L);
02994 
02995        if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
02996 
02997        MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
02998                  CCC->room.QRname);
02999 
03000        /* No exclusive id?  Don't do anything. */
03001        if (msg == NULL) return;
03002        if (msg->cm_fields['E'] == NULL) return;
03003        if (IsEmptyStr(msg->cm_fields['E'])) return;
03004        /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
03005          msg->cm_fields['E'], CCC->room.QRname);*/
03006 
03007        old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CCC->room);
03008        if (old_msgnum > 0L) {
03009               MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
03010               CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
03011        }
03012 }
03013 
03014 
03015 
03016 /*
03017  * Save a message to disk and submit it into the delivery system.
03018  */
03019 long CtdlSubmitMsg(struct CtdlMessage *msg,      /* message to save */
03020                  struct recptypes *recps, /* recipients (if mail) */
03021                  const char *force,              /* force a particular room? */
03022                  int flags                /* should the message be exported clean? */
03023        )
03024 {
03025        char submit_filename[128];
03026        char generated_timestamp[32];
03027        char hold_rm[ROOMNAMELEN];
03028        char actual_rm[ROOMNAMELEN];
03029        char force_room[ROOMNAMELEN];
03030        char content_type[SIZ];                   /* We have to learn this */
03031        char recipient[SIZ];
03032        const char *room;
03033        long newmsgid;
03034        const char *mptr = NULL;
03035        struct ctdluser userbuf;
03036        int a, i;
03037        struct MetaData smi;
03038        FILE *network_fp = NULL;
03039        static int seqnum = 1;
03040        struct CtdlMessage *imsg = NULL;
03041        char *instr = NULL;
03042        size_t instr_alloc = 0;
03043        struct ser_ret smr;
03044        char *hold_R, *hold_D;
03045        char *collected_addresses = NULL;
03046        struct addresses_to_be_filed *aptr = NULL;
03047        StrBuf *saved_rfc822_version = NULL;
03048        int qualified_for_journaling = 0;
03049        CitContext *CCC = MyContext();
03050        char bounce_to[1024] = "";
03051        int rv = 0;
03052 
03053        MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
03054        if (is_valid_message(msg) == 0) return(-1);      /* self check */
03055 
03056        /* If this message has no timestamp, we take the liberty of
03057         * giving it one, right now.
03058         */
03059        if (msg->cm_fields['T'] == NULL) {
03060               snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
03061               msg->cm_fields['T'] = strdup(generated_timestamp);
03062        }
03063 
03064        /* If this message has no path, we generate one.
03065         */
03066        if (msg->cm_fields['P'] == NULL) {
03067               if (msg->cm_fields['A'] != NULL) {
03068                      msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
03069                      for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
03070                             if (isspace(msg->cm_fields['P'][a])) {
03071                                    msg->cm_fields['P'][a] = ' ';
03072                             }
03073                      }
03074               }
03075               else {
03076                      msg->cm_fields['P'] = strdup("unknown");
03077               }
03078        }
03079 
03080        if (force == NULL) {
03081               strcpy(force_room, "");
03082        }
03083        else {
03084               strcpy(force_room, force);
03085        }
03086 
03087        /* Learn about what's inside, because it's what's inside that counts */
03088        if (msg->cm_fields['M'] == NULL) {
03089               MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
03090               return(-2);
03091        }
03092 
03093        switch (msg->cm_format_type) {
03094        case 0:
03095               strcpy(content_type, "text/x-citadel-variformat");
03096               break;
03097        case 1:
03098               strcpy(content_type, "text/plain");
03099               break;
03100        case 4:
03101               strcpy(content_type, "text/plain");
03102               mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
03103               if (mptr != NULL) {
03104                      char *aptr;
03105                      safestrncpy(content_type, &mptr[13], sizeof content_type);
03106                      striplt(content_type);
03107                      aptr = content_type;
03108                      while (!IsEmptyStr(aptr)) {
03109                             if ((*aptr == ';')
03110                                 || (*aptr == ' ')
03111                                 || (*aptr == 13)
03112                                 || (*aptr == 10)) {
03113                                    *aptr = 0;
03114                             }
03115                             else aptr++;
03116                      }
03117               }
03118        }
03119 
03120        /* Goto the correct room */
03121        room = (recps) ? CCC->room.QRname : SENTITEMS;
03122        MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
03123        strcpy(hold_rm, CCC->room.QRname);
03124        strcpy(actual_rm, CCC->room.QRname);
03125        if (recps != NULL) {
03126               strcpy(actual_rm, SENTITEMS);
03127        }
03128 
03129        /* If the user is a twit, move to the twit room for posting */
03130        if (TWITDETECT) {
03131               if (CCC->user.axlevel == AxProbU) {
03132                      strcpy(hold_rm, actual_rm);
03133                      strcpy(actual_rm, config.c_twitroom);
03134                      MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
03135               }
03136        }
03137 
03138        /* ...or if this message is destined for Aide> then go there. */
03139        if (!IsEmptyStr(force_room)) {
03140               strcpy(actual_rm, force_room);
03141        }
03142 
03143        MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
03144        if (strcasecmp(actual_rm, CCC->room.QRname)) {
03145               /* CtdlGetRoom(&CCC->room, actual_rm); */
03146               CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
03147        }
03148 
03149        /*
03150         * If this message has no O (room) field, generate one.
03151         */
03152        if (msg->cm_fields['O'] == NULL) {
03153               msg->cm_fields['O'] = strdup(CCC->room.QRname);
03154        }
03155 
03156        /* Perform "before save" hooks (aborting if any return nonzero) */
03157        MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
03158        if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
03159 
03160        /*
03161         * If this message has an Exclusive ID, and the room is replication
03162         * checking enabled, then do replication checks.
03163         */
03164        if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
03165               ReplicationChecks(msg);
03166        }
03167 
03168        /* Save it to disk */
03169        MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
03170        newmsgid = send_message(msg);
03171        if (newmsgid <= 0L) return(-5);
03172 
03173        /* Write a supplemental message info record.  This doesn't have to
03174         * be a critical section because nobody else knows about this message
03175         * yet.
03176         */
03177        MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
03178        memset(&smi, 0, sizeof(struct MetaData));
03179        smi.meta_msgnum = newmsgid;
03180        smi.meta_refcount = 0;
03181        safestrncpy(smi.meta_content_type, content_type,
03182                   sizeof smi.meta_content_type);
03183 
03184        /*
03185         * Measure how big this message will be when rendered as RFC822.
03186         * We do this for two reasons:
03187         * 1. We need the RFC822 length for the new metadata record, so the
03188         *    POP and IMAP services don't have to calculate message lengths
03189         *    while the user is waiting (multiplied by potentially hundreds
03190         *    or thousands of messages).
03191         * 2. If journaling is enabled, we will need an RFC822 version of the
03192         *    message to attach to the journalized copy.
03193         */
03194        if (CCC->redirect_buffer != NULL) {
03195               MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
03196               abort();
03197        }
03198        CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
03199        CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
03200        smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
03201        saved_rfc822_version = CCC->redirect_buffer;
03202        CCC->redirect_buffer = NULL;
03203 
03204        PutMetaData(&smi);
03205 
03206        /* Now figure out where to store the pointers */
03207        MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
03208 
03209        /* If this is being done by the networker delivering a private
03210         * message, we want to BYPASS saving the sender's copy (because there
03211         * is no local sender; it would otherwise go to the Trashcan).
03212         */
03213        if ((!CCC->internal_pgm) || (recps == NULL)) {
03214               if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
03215                      MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
03216                      CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
03217               }
03218        }
03219 
03220        /* For internet mail, drop a copy in the outbound queue room */
03221        if ((recps != NULL) && (recps->num_internet > 0)) {
03222               CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
03223        }
03224 
03225        /* If other rooms are specified, drop them there too. */
03226        if ((recps != NULL) && (recps->num_room > 0))
03227               for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
03228                      extract_token(recipient, recps->recp_room, i,
03229                                   '|', sizeof recipient);
03230                      MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);
03231                      CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
03232               }
03233 
03234        /* Bump this user's messages posted counter. */
03235        MSGM_syslog(LOG_DEBUG, "Updating user\n");
03236        CtdlGetUserLock(&CCC->user, CCC->curr_user);
03237        CCC->user.posted = CCC->user.posted + 1;
03238        CtdlPutUserLock(&CCC->user);
03239 
03240        /* Decide where bounces need to be delivered */
03241        if ((recps != NULL) && (recps->bounce_to != NULL)) {
03242               safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
03243        }
03244        else if (CCC->logged_in) {
03245               snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
03246        }
03247        else {
03248               snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
03249        }
03250 
03251        /* If this is private, local mail, make a copy in the
03252         * recipient's mailbox and bump the reference count.
03253         */
03254        if ((recps != NULL) && (recps->num_local > 0))
03255               for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
03256                      extract_token(recipient, recps->recp_local, i,
03257                                   '|', sizeof recipient);
03258                      MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
03259                             recipient);
03260                      if (CtdlGetUser(&userbuf, recipient) == 0) {
03261                             CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
03262                             CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
03263                             CtdlBumpNewMailCounter(userbuf.usernum);
03264                             if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
03265                                    /* Generate a instruction message for the Funambol notification
03266                                     * server, in the same style as the SMTP queue
03267                                     */
03268                                    instr_alloc = 1024;
03269                                    instr = malloc(instr_alloc);
03270                                    snprintf(instr, instr_alloc,
03271                                            "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
03272                                            "bounceto|%s\n",
03273                                            SPOOLMIME, newmsgid, (long)time(NULL),
03274                                            bounce_to
03275                                           );
03276                             
03277                                    imsg = malloc(sizeof(struct CtdlMessage));
03278                                    memset(imsg, 0, sizeof(struct CtdlMessage));
03279                                    imsg->cm_magic = CTDLMESSAGE_MAGIC;
03280                                    imsg->cm_anon_type = MES_NORMAL;
03281                                    imsg->cm_format_type = FMT_RFC822;
03282                                    imsg->cm_fields['U'] = strdup("QMSG");
03283                                    imsg->cm_fields['A'] = strdup("Citadel");
03284                                    imsg->cm_fields['J'] = strdup("do not journal");
03285                                    imsg->cm_fields['M'] = instr;      /* imsg owns this memory now */
03286                                    imsg->cm_fields['2'] = strdup(recipient);
03287                                    CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
03288                                    CtdlFreeMessage(imsg);
03289                             }
03290                      }
03291                      else {
03292                             MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
03293                             CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
03294                      }
03295               }
03296 
03297        /* Perform "after save" hooks */
03298        MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
03299        if (msg->cm_fields['3'] != NULL) free(msg->cm_fields['3']);
03300        msg->cm_fields['3'] = malloc(20);
03301        snprintf(msg->cm_fields['3'], 20, "%ld", newmsgid);
03302        PerformMessageHooks(msg, EVT_AFTERSAVE);
03303        free(msg->cm_fields['3']);
03304        msg->cm_fields['3'] = NULL;
03305 
03306        /* For IGnet mail, we have to save a new copy into the spooler for
03307         * each recipient, with the R and D fields set to the recipient and
03308         * destination-node.  This has two ugly side effects: all other
03309         * recipients end up being unlisted in this recipient's copy of the
03310         * message, and it has to deliver multiple messages to the same
03311         * node.  We'll revisit this again in a year or so when everyone has
03312         * a network spool receiver that can handle the new style messages.
03313         */
03314        if ((recps != NULL) && (recps->num_ignet > 0))
03315               for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
03316                      extract_token(recipient, recps->recp_ignet, i,
03317                                   '|', sizeof recipient);
03318 
03319                      hold_R = msg->cm_fields['R'];
03320                      hold_D = msg->cm_fields['D'];
03321                      msg->cm_fields['R'] = malloc(SIZ);
03322                      msg->cm_fields['D'] = malloc(128);
03323                      extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
03324                      extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
03325               
03326                      serialize_message(&smr, msg);
03327                      if (smr.len > 0) {
03328                             snprintf(submit_filename, sizeof submit_filename,
03329                                     "%s/netmail.%04lx.%04x.%04x",
03330                                     ctdl_netin_dir,
03331                                     (long) getpid(), CCC->cs_pid, ++seqnum);
03332                             network_fp = fopen(submit_filename, "wb+");
03333                             if (network_fp != NULL) {
03334                                    rv = fwrite(smr.ser, smr.len, 1, network_fp);
03335                                    if (rv == -1) {
03336                                           MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
03337                                                     strerror(errno));
03338                                    }
03339                                    fclose(network_fp);
03340                             }
03341                             free(smr.ser);
03342                      }
03343 
03344                      free(msg->cm_fields['R']);
03345                      free(msg->cm_fields['D']);
03346                      msg->cm_fields['R'] = hold_R;
03347                      msg->cm_fields['D'] = hold_D;
03348               }
03349 
03350        /* Go back to the room we started from */
03351        MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
03352        if (strcasecmp(hold_rm, CCC->room.QRname))
03353               CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
03354 
03355        /* For internet mail, generate delivery instructions.
03356         * Yes, this is recursive.  Deal with it.  Infinite recursion does
03357         * not happen because the delivery instructions message does not
03358         * contain a recipient.
03359         */
03360        if ((recps != NULL) && (recps->num_internet > 0)) {
03361               StrBuf *SpoolMsg = NewStrBuf();
03362               long nTokens;
03363 
03364               MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
03365 
03366               StrBufPrintf(SpoolMsg,
03367                           "Content-type: "SPOOLMIME"\n"
03368                           "\n"
03369                           "msgid|%ld\n"
03370                           "submitted|%ld\n"
03371                           "bounceto|%s\n",
03372                           newmsgid,
03373                           (long)time(NULL),
03374                           bounce_to);
03375 
03376               if (recps->envelope_from != NULL) {
03377                      StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
03378                      StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
03379                      StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
03380               }
03381               if (recps->sending_room != NULL) {
03382                      StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
03383                      StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
03384                      StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
03385               }
03386 
03387               nTokens = num_tokens(recps->recp_internet, '|');
03388               for (i = 0; i < nTokens; i++) {
03389                      long len;
03390                      len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
03391                      if (len > 0) {
03392                             StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
03393                             StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
03394                             StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
03395                      }
03396               }
03397 
03398               imsg = malloc(sizeof(struct CtdlMessage));
03399               memset(imsg, 0, sizeof(struct CtdlMessage));
03400               imsg->cm_magic = CTDLMESSAGE_MAGIC;
03401               imsg->cm_anon_type = MES_NORMAL;
03402               imsg->cm_format_type = FMT_RFC822;
03403               imsg->cm_fields['U'] = strdup("QMSG");
03404               imsg->cm_fields['A'] = strdup("Citadel");
03405               imsg->cm_fields['J'] = strdup("do not journal");
03406               imsg->cm_fields['M'] = SmashStrBuf(&SpoolMsg);   /* imsg owns this memory now */
03407               CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
03408               CtdlFreeMessage(imsg);
03409        }
03410 
03411        /*
03412         * Any addresses to harvest for someone's address book?
03413         */
03414        if ( (CCC->logged_in) && (recps != NULL) ) {
03415               collected_addresses = harvest_collected_addresses(msg);
03416        }
03417 
03418        if (collected_addresses != NULL) {
03419               aptr = (struct addresses_to_be_filed *)
03420                      malloc(sizeof(struct addresses_to_be_filed));
03421               CtdlMailboxName(actual_rm, sizeof actual_rm,
03422                             &CCC->user, USERCONTACTSROOM);
03423               aptr->roomname = strdup(actual_rm);
03424               aptr->collected_addresses = collected_addresses;
03425               begin_critical_section(S_ATBF);
03426               aptr->next = atbf;
03427               atbf = aptr;
03428               end_critical_section(S_ATBF);
03429        }
03430 
03431        /*
03432         * Determine whether this message qualifies for journaling.
03433         */
03434        if (msg->cm_fields['J'] != NULL) {
03435               qualified_for_journaling = 0;
03436        }
03437        else {
03438               if (recps == NULL) {
03439                      qualified_for_journaling = config.c_journal_pubmsgs;
03440               }
03441               else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
03442                      qualified_for_journaling = config.c_journal_email;
03443               }
03444               else {
03445                      qualified_for_journaling = config.c_journal_pubmsgs;
03446               }
03447        }
03448 
03449        /*
03450         * Do we have to perform journaling?  If so, hand off the saved
03451         * RFC822 version will be handed off to the journaler for background
03452         * submit.  Otherwise, we have to free the memory ourselves.
03453         */
03454        if (saved_rfc822_version != NULL) {
03455               if (qualified_for_journaling) {
03456                      JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
03457               }
03458               else {
03459                      FreeStrBuf(&saved_rfc822_version);
03460               }
03461        }
03462 
03463        /* Done. */
03464        return(newmsgid);
03465 }
03466 
03467 
03468 
03469 void aide_message (char *text, char *subject)
03470 {
03471        quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
03472 }
03473 
03474 
03475 /*
03476  * Convenience function for generating small administrative messages.
03477  */
03478 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text, 
03479                    int format_type, const char *subject)
03480 {
03481        struct CtdlMessage *msg;
03482        struct recptypes *recp = NULL;
03483 
03484        msg = malloc(sizeof(struct CtdlMessage));
03485        memset(msg, 0, sizeof(struct CtdlMessage));
03486        msg->cm_magic = CTDLMESSAGE_MAGIC;
03487        msg->cm_anon_type = MES_NORMAL;
03488        msg->cm_format_type = format_type;
03489 
03490        if (from != NULL) {
03491               msg->cm_fields['A'] = strdup(from);
03492        }
03493        else if (fromaddr != NULL) {
03494               msg->cm_fields['A'] = strdup(fromaddr);
03495               if (strchr(msg->cm_fields['A'], '@')) {
03496                      *strchr(msg->cm_fields['A'], '@') = 0;
03497               }
03498        }
03499        else {
03500               msg->cm_fields['A'] = strdup("Citadel");
03501        }
03502 
03503        if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
03504        if (room != NULL) msg->cm_fields['O'] = strdup(room);
03505        msg->cm_fields['N'] = strdup(NODENAME);
03506        if (to != NULL) {
03507               msg->cm_fields['R'] = strdup(to);
03508               recp = validate_recipients(to, NULL, 0);
03509        }
03510        if (subject != NULL) {
03511               msg->cm_fields['U'] = strdup(subject);
03512        }
03513        msg->cm_fields['M'] = strdup(text);
03514 
03515        CtdlSubmitMsg(msg, recp, room, 0);
03516        CtdlFreeMessage(msg);
03517        if (recp != NULL) free_recipients(recp);
03518 }
03519 
03520 
03521 
03522 /*
03523  * Back end function used by CtdlMakeMessage() and similar functions
03524  */
03525 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
03526                             long tlen,
03527                             size_t maxlen,              /* maximum message length */
03528                             char *exist,         /* if non-null, append to it;
03529                                                     exist is ALWAYS freed  */
03530                             int crlf,            /* CRLF newlines instead of LF */
03531                             int *sock            /* socket handle or 0 for this session's client socket */
03532        ) 
03533 {
03534        StrBuf *Message;
03535        StrBuf *LineBuf;
03536        int flushing = 0;
03537        int finished = 0;
03538        int dotdot = 0;
03539 
03540        LineBuf = NewStrBufPlain(NULL, SIZ);
03541        if (exist == NULL) {
03542               Message = NewStrBufPlain(NULL, 4 * SIZ);
03543        }
03544        else {
03545               Message = NewStrBufPlain(exist, -1);
03546               free(exist);
03547        }
03548 
03549        /* Do we need to change leading ".." to "." for SMTP escaping? */
03550        if ((tlen == 1) && (*terminator == '.')) {
03551               dotdot = 1;
03552        }
03553 
03554        /* read in the lines of message text one by one */
03555        do {
03556               if (sock != NULL) {
03557                      if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
03558                          (*sock == -1))
03559                             finished = 1;
03560               }
03561               else {
03562                      if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
03563               }
03564               if ((StrLength(LineBuf) == tlen) && 
03565                   (!strcmp(ChrPtr(LineBuf), terminator)))
03566                      finished = 1;
03567 
03568               if ( (!flushing) && (!finished) ) {
03569                      if (crlf) {
03570                             StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
03571                      }
03572                      else {
03573                             StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
03574                      }
03575                      
03576                      /* Unescape SMTP-style input of two dots at the beginning of the line */
03577                      if ((dotdot) &&
03578                          (StrLength(LineBuf) == 2) && 
03579                          (!strcmp(ChrPtr(LineBuf), "..")))
03580                      {
03581                             StrBufCutLeft(LineBuf, 1);
03582                      }
03583                      
03584                      StrBufAppendBuf(Message, LineBuf, 0);
03585               }
03586 
03587               /* if we've hit the max msg length, flush the rest */
03588               if (StrLength(Message) >= maxlen) flushing = 1;
03589 
03590        } while (!finished);
03591        FreeStrBuf(&LineBuf);
03592        return Message;
03593 }
03594 
03595 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
03596 {
03597        if (*Msg == NULL)
03598               return;
03599        FreeStrBuf(&(*Msg)->MsgBuf);
03600 
03601        free(*Msg);
03602        *Msg = NULL;
03603 }
03604 
03605 ReadAsyncMsg *NewAsyncMsg(const char *terminator,       /* token signalling EOT */
03606                        long tlen,
03607                        size_t maxlen,            /* maximum message length */
03608                        size_t expectlen,             /* if we expect a message, how long should it be? */
03609                        char *exist,                     /* if non-null, append to it;
03610                                                     exist is ALWAYS freed  */
03611                        long eLen,                /* length of exist */
03612                        int crlf                  /* CRLF newlines instead of LF */
03613        )
03614 {
03615        ReadAsyncMsg *NewMsg;
03616 
03617        NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
03618        memset(NewMsg, 0, sizeof(ReadAsyncMsg));
03619 
03620        if (exist == NULL) {
03621               long len;
03622 
03623               if (expectlen == 0) {
03624                      len = 4 * SIZ;
03625               }
03626               else {
03627                      len = expectlen + 10;
03628               }
03629               NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
03630        }
03631        else {
03632               NewMsg->MsgBuf = NewStrBufPlain(exist, eLen);
03633               free(exist);
03634        }
03635        /* Do we need to change leading ".." to "." for SMTP escaping? */
03636        if ((tlen == 1) && (*terminator == '.')) {
03637               NewMsg->dodot = 1;
03638        }
03639 
03640        NewMsg->terminator = terminator;
03641        NewMsg->tlen = tlen;
03642 
03643        NewMsg->maxlen = maxlen;
03644 
03645        NewMsg->crlf = crlf;
03646 
03647        return NewMsg;
03648 }
03649 
03650 /*
03651  * Back end function used by CtdlMakeMessage() and similar functions
03652  */
03653 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
03654 {
03655        ReadAsyncMsg *ReadMsg;
03656        int MsgFinished = 0;
03657        eReadState Finished = eMustReadMore;
03658 
03659 #ifdef BIGBAD_IODBG
03660        char fn [SIZ];
03661        FILE *fd;
03662        const char *pch = ChrPtr(IO->SendBuf.Buf);
03663        const char *pchh = IO->SendBuf.ReadWritePointer;
03664        long nbytes;
03665        
03666        if (pchh == NULL)
03667               pchh = pch;
03668        
03669        nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
03670        snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
03671                ((CitContext*)(IO->CitContext))->ServiceName,
03672                IO->SendBuf.fd);
03673        
03674        fd = fopen(fn, "a+");
03675 #endif
03676 
03677        ReadMsg = IO->ReadMsg;
03678 
03679        /* read in the lines of message text one by one */
03680        do {
03681               Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
03682               
03683               switch (Finished) {
03684               case eMustReadMore: 
03685 #ifdef BIGBAD_IODBG
03686                      if (IO->RecvBuf.ReadWritePointer != NULL) {
03687                             nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
03688                             fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
03689                             
03690                             fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
03691                      
03692                             fprintf(fd, "]\n");
03693                      } else {
03694                             fprintf(fd, "BufferEmpty! \n");
03695                      }
03696                      fclose(fd);
03697 #endif
03698                      return Finished;
03699                   break;
03700               case eBufferNotEmpty: /* shouldn't happen... */
03701               case eReadSuccess: 
03702                   break;
03703               case eReadFail: 
03704 
03705                      break;
03706               }
03707            
03708 
03709               if ((StrLength(IO->IOBuf) == ReadMsg->tlen) && 
03710                   (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
03711                      MsgFinished = 1;
03712 #ifdef BIGBAD_IODBG
03713                      fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
03714 #endif
03715               }
03716               else if (!ReadMsg->flushing) {
03717 
03718 #ifdef BIGBAD_IODBG
03719                      fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
03720 #endif
03721 
03722                      /* Unescape SMTP-style input of two dots at the beginning of the line */
03723                      if ((ReadMsg->dodot) &&
03724                          (StrLength(IO->IOBuf) == 2) &&  /* TODO: do we just unescape lines with two dots or any line? */
03725                          (!strcmp(ChrPtr(IO->IOBuf), "..")))
03726                      {
03727 #ifdef BIGBAD_IODBG
03728                             fprintf(fd, "UnEscaped!\n");
03729 #endif
03730                             StrBufCutLeft(IO->IOBuf, 1);
03731                      }
03732 
03733                      if (ReadMsg->crlf) {
03734                             StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
03735                      }
03736                      else {
03737                             StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
03738                      }
03739 
03740                      StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
03741               }
03742 
03743               /* if we've hit the max msg length, flush the rest */
03744               if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
03745 
03746        } while (!MsgFinished);
03747 
03748 #ifdef BIGBAD_IODBG
03749        fprintf(fd, "Done with reading; %s.\n, ",
03750               (MsgFinished)?"Message Finished": "FAILED");
03751        fclose(fd);
03752 #endif
03753        if (MsgFinished)
03754               return eReadSuccess;
03755        else 
03756               return eAbort;
03757 }
03758 
03759 
03760 /*
03761  * Back end function used by CtdlMakeMessage() and similar functions
03762  */
03763 char *CtdlReadMessageBody(char *terminator,      /* token signalling EOT */
03764                        long tlen,
03765                        size_t maxlen,            /* maximum message length */
03766                        char *exist,              /* if non-null, append to it;
03767                                              exist is ALWAYS freed  */
03768                        int crlf,          /* CRLF newlines instead of LF */
03769                        int *sock          /* socket handle or 0 for this session's client socket */
03770        ) 
03771 {
03772        StrBuf *Message;
03773 
03774        Message = CtdlReadMessageBodyBuf(terminator,
03775                                     tlen,
03776                                     maxlen,
03777                                     exist,
03778                                     crlf,
03779                                     sock);
03780        if (Message == NULL)
03781               return NULL;
03782        else
03783               return SmashStrBuf(&Message);
03784 }
03785 
03786 
03787 /*
03788  * Build a binary message to be saved on disk.
03789  * (NOTE: if you supply 'preformatted_text', the buffer you give it
03790  * will become part of the message.  This means you are no longer
03791  * responsible for managing that memory -- it will be freed along with
03792  * the rest of the fields when CtdlFreeMessage() is called.)
03793  */
03794 
03795 struct CtdlMessage *CtdlMakeMessage(
03796        struct ctdluser *author,    /* author's user structure */
03797        char *recipient,            /* NULL if it's not mail */
03798        char *recp_cc,                     /* NULL if it's not mail */
03799        char *room,                 /* room where it's going */
03800        int type,                   /* see MES_ types in header file */
03801        int format_type,            /* variformat, plain text, MIME... */
03802        char *fake_name,            /* who we're masquerading as */
03803        char *my_email,                    /* which of my email addresses to use (empty is ok) */
03804        char *subject,                     /* Subject (optional) */
03805        char *supplied_euid,        /* ...or NULL if this is irrelevant */
03806        char *preformatted_text,    /* ...or NULL to read text from client */
03807        char *references            /* Thread references */
03808        ) {
03809        char dest_node[256];
03810        char buf[1024];
03811        struct CtdlMessage *msg;
03812        StrBuf *FakeAuthor;
03813        StrBuf *FakeEncAuthor = NULL;
03814 
03815        msg = malloc(sizeof(struct CtdlMessage));
03816        memset(msg, 0, sizeof(struct CtdlMessage));
03817        msg->cm_magic = CTDLMESSAGE_MAGIC;
03818        msg->cm_anon_type = type;
03819        msg->cm_format_type = format_type;
03820 
03821        /* Don't confuse the poor folks if it's not routed mail. */
03822        strcpy(dest_node, "");
03823 
03824        if (recipient != NULL) striplt(recipient);
03825        if (recp_cc != NULL) striplt(recp_cc);
03826 
03827        /* Path or Return-Path */
03828        if (my_email == NULL) my_email = "";
03829 
03830        if (!IsEmptyStr(my_email)) {
03831               msg->cm_fields['P'] = strdup(my_email);
03832        }
03833        else {
03834               snprintf(buf, sizeof buf, "%s", author->fullname);
03835               msg->cm_fields['P'] = strdup(buf);
03836        }
03837        convert_spaces_to_underscores(msg->cm_fields['P']);
03838 
03839        snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
03840        msg->cm_fields['T'] = strdup(buf);
03841 
03842        if ((fake_name != NULL) && (fake_name[0])) {            /* author */
03843               FakeAuthor = NewStrBufPlain (fake_name, -1);
03844        }
03845        else {
03846               FakeAuthor = NewStrBufPlain (author->fullname, -1);
03847        }
03848        StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
03849        msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
03850        FreeStrBuf(&FakeAuthor);
03851 
03852        if (CC->room.QRflags & QR_MAILBOX) {             /* room */
03853               msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
03854        }
03855        else {
03856               msg->cm_fields['O'] = strdup(CC->room.QRname);
03857        }
03858 
03859        msg->cm_fields['N'] = strdup(NODENAME);          /* nodename */
03860        msg->cm_fields['H'] = strdup(HUMANNODE);         /* hnodename */
03861 
03862        if ((recipient != NULL) && (recipient[0] != 0)) {
03863               msg->cm_fields['R'] = strdup(recipient);
03864        }
03865        if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
03866               msg->cm_fields['Y'] = strdup(recp_cc);
03867        }
03868        if (dest_node[0] != 0) {
03869               msg->cm_fields['D'] = strdup(dest_node);
03870        }
03871 
03872        if (!IsEmptyStr(my_email)) {
03873               msg->cm_fields['F'] = strdup(my_email);
03874        }
03875        else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
03876               msg->cm_fields['F'] = strdup(CC->cs_inet_email);
03877        }
03878 
03879        if (subject != NULL) {
03880               long length;
03881               striplt(subject);
03882               length = strlen(subject);
03883               if (length > 0) {
03884                      long i;
03885                      long IsAscii;
03886                      IsAscii = -1;
03887                      i = 0;
03888                      while ((subject[i] != '\0') &&
03889                             (IsAscii = isascii(subject[i]) != 0 ))
03890                             i++;
03891                      if (IsAscii != 0)
03892                             msg->cm_fields['U'] = strdup(subject);
03893                      else /* ok, we've got utf8 in the string. */
03894                      {
03895                             msg->cm_fields['U'] = rfc2047encode(subject, length);
03896                      }
03897 
03898               }
03899        }
03900 
03901        if (supplied_euid != NULL) {
03902               msg->cm_fields['E'] = strdup(supplied_euid);
03903        }
03904 
03905        if ((references != NULL) && (!IsEmptyStr(references))) {
03906               if (msg->cm_fields['W'] != NULL)
03907                      free(msg->cm_fields['W']);
03908               msg->cm_fields['W'] = strdup(references);
03909        }
03910 
03911        if (preformatted_text != NULL) {
03912               msg->cm_fields['M'] = preformatted_text;
03913        }
03914        else {
03915               msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
03916        }
03917 
03918        return(msg);
03919 }
03920 
03921 extern int netconfig_check_roomaccess(
03922        char *errmsgbuf, 
03923        size_t n,
03924        const char* RemoteIdentifier); /* TODO: find a smarter way */
03925 
03926 /*
03927  * Check to see whether we have permission to post a message in the current
03928  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
03929  * returns 0 on success.
03930  */
03931 int CtdlDoIHavePermissionToPostInThisRoom(
03932        char *errmsgbuf, 
03933        size_t n, 
03934        const char* RemoteIdentifier,
03935        int PostPublic,
03936        int is_reply
03937        ) {
03938        int ra;
03939 
03940        if (!(CC->logged_in) && 
03941            (PostPublic == POST_LOGGED_IN)) {
03942               snprintf(errmsgbuf, n, "Not logged in.");
03943               return (ERROR + NOT_LOGGED_IN);
03944        }
03945        else if (PostPublic == CHECK_EXISTANCE) {
03946               return (0); // We're Evaling whether a recipient exists
03947        }
03948        else if (!(CC->logged_in)) {
03949               
03950               if ((CC->room.QRflags & QR_READONLY)) {
03951                      snprintf(errmsgbuf, n, "Not logged in.");
03952                      return (ERROR + NOT_LOGGED_IN);
03953               }
03954               if (CC->room.QRflags2 & QR2_MODERATED) {
03955                      snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
03956                      return (ERROR + NOT_LOGGED_IN);
03957               }
03958               if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
03959 
03960                      return netconfig_check_roomaccess(errmsgbuf, n, RemoteIdentifier);
03961               }
03962               return (0);
03963 
03964        }
03965 
03966        if ((CC->user.axlevel < AxProbU)
03967            && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
03968               snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
03969               return (ERROR + HIGHER_ACCESS_REQUIRED);
03970        }
03971 
03972        CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
03973 
03974        if (ra & UA_POSTALLOWED) {
03975               strcpy(errmsgbuf, "OK to post or reply here");
03976               return(0);
03977        }
03978 
03979        if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
03980               /*
03981                * To be thorough, we ought to check to see if the message they are
03982                * replying to is actually a valid one in this room, but unless this
03983                * actually becomes a problem we'll go with high performance instead.
03984                */
03985               strcpy(errmsgbuf, "OK to reply here");
03986               return(0);
03987        }
03988 
03989        if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
03990               /* Clarify what happened with a better error message */
03991               snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
03992               return (ERROR + HIGHER_ACCESS_REQUIRED);
03993        }
03994 
03995        snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
03996        return (ERROR + HIGHER_ACCESS_REQUIRED);
03997 
03998 }
03999 
04000 
04001 /*
04002  * Check to see if the specified user has Internet mail permission
04003  * (returns nonzero if permission is granted)
04004  */
04005 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
04006 
04007        /* Do not allow twits to send Internet mail */
04008        if (who->axlevel <= AxProbU) return(0);
04009 
04010        /* Globally enabled? */
04011        if (config.c_restrict == 0) return(1);
04012 
04013        /* User flagged ok? */
04014        if (who->flags & US_INTERNET) return(2);
04015 
04016        /* Aide level access? */
04017        if (who->axlevel >= AxAideU) return(3);
04018 
04019        /* No mail for you! */
04020        return(0);
04021 }
04022 
04023 
04024 /*
04025  * Validate recipients, count delivery types and errors, and handle aliasing
04026  * FIXME check for dupes!!!!!
04027  *
04028  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
04029  * were specified, or the number of addresses found invalid.
04030  *
04031  * Caller needs to free the result using free_recipients()
04032  */
04033 struct recptypes *validate_recipients(const char *supplied_recipients, 
04034                                   const char *RemoteIdentifier, 
04035                                   int Flags) {
04036        struct CitContext *CCC = CC;
04037        struct recptypes *ret;
04038        char *recipients = NULL;
04039        char *org_recp;
04040        char this_recp[256];
04041        char this_recp_cooked[256];
04042        char append[SIZ];
04043        long len;
04044        int num_recps = 0;
04045        int i, j;
04046        int mailtype;
04047        int invalid;
04048        struct ctdluser tempUS;
04049        struct ctdlroom tempQR;
04050        struct ctdlroom tempQR2;
04051        int err = 0;
04052        char errmsg[SIZ];
04053        int in_quotes = 0;
04054 
04055        /* Initialize */
04056        ret = (struct recptypes *) malloc(sizeof(struct recptypes));
04057        if (ret == NULL) return(NULL);
04058 
04059        /* Set all strings to null and numeric values to zero */
04060        memset(ret, 0, sizeof(struct recptypes));
04061 
04062        if (supplied_recipients == NULL) {
04063               recipients = strdup("");
04064        }
04065        else {
04066               recipients = strdup(supplied_recipients);
04067        }
04068 
04069        /* Allocate some memory.  Yes, this allocates 500% more memory than we will
04070         * actually need, but it's healthier for the heap than doing lots of tiny
04071         * realloc() calls instead.
04072         */
04073        len = strlen(recipients) + 1024;
04074        ret->errormsg = malloc(len);
04075        ret->recp_local = malloc(len);
04076        ret->recp_internet = malloc(len);
04077        ret->recp_ignet = malloc(len);
04078        ret->recp_room = malloc(len);
04079        ret->display_recp = malloc(len);
04080        ret->recp_orgroom = malloc(len);
04081        org_recp = malloc(len);
04082 
04083        ret->errormsg[0] = 0;
04084        ret->recp_local[0] = 0;
04085        ret->recp_internet[0] = 0;
04086        ret->recp_ignet[0] = 0;
04087        ret->recp_room[0] = 0;
04088        ret->recp_orgroom[0] = 0;
04089        ret->display_recp[0] = 0;
04090 
04091        ret->recptypes_magic = RECPTYPES_MAGIC;
04092 
04093        /* Change all valid separator characters to commas */
04094        for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
04095               if ((recipients[i] == ';') || (recipients[i] == '|')) {
04096                      recipients[i] = ',';
04097               }
04098        }
04099 
04100        /* Now start extracting recipients... */
04101 
04102        while (!IsEmptyStr(recipients)) {
04103               for (i=0; i<=strlen(recipients); ++i) {
04104                      if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
04105                      if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
04106                             safestrncpy(this_recp, recipients, i+1);
04107                             this_recp[i] = 0;
04108                             if (recipients[i] == ',') {
04109                                    strcpy(recipients, &recipients[i+1]);
04110                             }
04111                             else {
04112                                    strcpy(recipients, "");
04113                             }
04114                             break;
04115                      }
04116               }
04117 
04118               striplt(this_recp);
04119               if (IsEmptyStr(this_recp))
04120                      break;
04121               MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
04122               ++num_recps;
04123 
04124               strcpy(org_recp, this_recp);
04125               alias(this_recp);
04126               alias(this_recp);
04127               mailtype = alias(this_recp);
04128 
04129               for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
04130                      if (this_recp[j]=='_') {
04131                             this_recp_cooked[j] = ' ';
04132                      }
04133                      else {
04134                             this_recp_cooked[j] = this_recp[j];
04135                      }
04136               }
04137               this_recp_cooked[j] = '\0';
04138               invalid = 0;
04139               errmsg[0] = 0;
04140               switch(mailtype) {
04141               case MES_LOCAL:
04142                      if (!strcasecmp(this_recp, "sysop")) {
04143                             ++ret->num_room;
04144                             strcpy(this_recp, config.c_aideroom);
04145                             if (!IsEmptyStr(ret->recp_room)) {
04146                                    strcat(ret->recp_room, "|");
04147                             }
04148                             strcat(ret->recp_room, this_recp);
04149                      }
04150                      else if ( (!strncasecmp(this_recp, "room_", 5))
04151                               && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
04152 
04153                             /* Save room so we can restore it later */
04154                             tempQR2 = CCC->room;
04155                             CCC->room = tempQR;
04156                                    
04157                             /* Check permissions to send mail to this room */
04158                             err = CtdlDoIHavePermissionToPostInThisRoom(
04159                                    errmsg, 
04160                                    sizeof errmsg, 
04161                                    RemoteIdentifier,
04162                                    Flags,
04163                                    0                    /* 0 = not a reply */
04164                                    );
04165                             if (err)
04166                             {
04167                                    ++ret->num_error;
04168                                    invalid = 1;
04169                             } 
04170                             else {
04171                                    ++ret->num_room;
04172                                    if (!IsEmptyStr(ret->recp_room)) {
04173                                           strcat(ret->recp_room, "|");
04174                                    }
04175                                    strcat(ret->recp_room, &this_recp_cooked[5]);
04176 
04177                                    if (!IsEmptyStr(ret->recp_orgroom)) {
04178                                           strcat(ret->recp_orgroom, "|");
04179                                    }
04180                                    strcat(ret->recp_orgroom, org_recp);
04181 
04182                             }
04183                                    
04184                             /* Restore room in case something needs it */
04185                             CCC->room = tempQR2;
04186 
04187                      }
04188                      else if (CtdlGetUser(&tempUS, this_recp) == 0) {
04189                             ++ret->num_local;
04190                             strcpy(this_recp, tempUS.fullname);
04191                             if (!IsEmptyStr(ret->recp_local)) {
04192                                    strcat(ret->recp_local, "|");
04193                             }
04194                             strcat(ret->recp_local, this_recp);
04195                      }
04196                      else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
04197                             ++ret->num_local;
04198                             strcpy(this_recp, tempUS.fullname);
04199                             if (!IsEmptyStr(ret->recp_local)) {
04200                                    strcat(ret->recp_local, "|");
04201                             }
04202                             strcat(ret->recp_local, this_recp);
04203                      }
04204                      else {
04205                             ++ret->num_error;
04206                             invalid = 1;
04207                      }
04208                      break;
04209               case MES_INTERNET:
04210                      /* Yes, you're reading this correctly: if the target
04211                       * domain points back to the local system or an attached
04212                       * Citadel directory, the address is invalid.  That's
04213                       * because if the address were valid, we would have
04214                       * already translated it to a local address by now.
04215                       */
04216                      if (IsDirectory(this_recp, 0)) {
04217                             ++ret->num_error;
04218                             invalid = 1;
04219                      }
04220                      else {
04221                             ++ret->num_internet;
04222                             if (!IsEmptyStr(ret->recp_internet)) {
04223                                    strcat(ret->recp_internet, "|");
04224                             }
04225                             strcat(ret->recp_internet, this_recp);
04226                      }
04227                      break;
04228               case MES_IGNET:
04229                      ++ret->num_ignet;
04230                      if (!IsEmptyStr(ret->recp_ignet)) {
04231                             strcat(ret->recp_ignet, "|");
04232                      }
04233                      strcat(ret->recp_ignet, this_recp);
04234                      break;
04235               case MES_ERROR:
04236                      ++ret->num_error;
04237                      invalid = 1;
04238                      break;
04239               }
04240               if (invalid) {
04241                      if (IsEmptyStr(errmsg)) {
04242                             snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
04243                      }
04244                      else {
04245                             snprintf(append, sizeof append, "%s", errmsg);
04246                      }
04247                      if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
04248                             if (!IsEmptyStr(ret->errormsg)) {
04249                                    strcat(ret->errormsg, "; ");
04250                             }
04251                             strcat(ret->errormsg, append);
04252                      }
04253               }
04254               else {
04255                      if (IsEmptyStr(ret->display_recp)) {
04256                             strcpy(append, this_recp);
04257                      }
04258                      else {
04259                             snprintf(append, sizeof append, ", %s", this_recp);
04260                      }
04261                      if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
04262                             strcat(ret->display_recp, append);
04263                      }
04264               }
04265        }
04266        free(org_recp);
04267 
04268        if ((ret->num_local + ret->num_internet + ret->num_ignet +
04269             ret->num_room + ret->num_error) == 0) {
04270               ret->num_error = (-1);
04271               strcpy(ret->errormsg, "No recipients specified.");
04272        }
04273 
04274        MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
04275        MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
04276        MSG_syslog(LOG_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
04277        MSG_syslog(LOG_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
04278        MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
04279        MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
04280 
04281        free(recipients);
04282        return(ret);
04283 }
04284 
04285 
04286 /*
04287  * Destructor for struct recptypes
04288  */
04289 void free_recipients(struct recptypes *valid) {
04290 
04291        if (valid == NULL) {
04292               return;
04293        }
04294 
04295        if (valid->recptypes_magic != RECPTYPES_MAGIC) {
04296               struct CitContext *CCC = CC;
04297               MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
04298               abort();
04299        }
04300 
04301        if (valid->errormsg != NULL)              free(valid->errormsg);
04302        if (valid->recp_local != NULL)            free(valid->recp_local);
04303        if (valid->recp_internet != NULL)  free(valid->recp_internet);
04304        if (valid->recp_ignet != NULL)            free(valid->recp_ignet);
04305        if (valid->recp_room != NULL)             free(valid->recp_room);
04306        if (valid->recp_orgroom != NULL)        free(valid->recp_orgroom);
04307        if (valid->display_recp != NULL)   free(valid->display_recp);
04308        if (valid->bounce_to != NULL)             free(valid->bounce_to);
04309        if (valid->envelope_from != NULL)  free(valid->envelope_from);
04310        if (valid->sending_room != NULL)   free(valid->sending_room);
04311        free(valid);
04312 }
04313 
04314 
04315 
04316 /*
04317  * message entry  -  mode 0 (normal)
04318  */
04319 void cmd_ent0(char *entargs)
04320 {
04321        struct CitContext *CCC = CC;
04322        int post = 0;
04323        char recp[SIZ];
04324        char cc[SIZ];
04325        char bcc[SIZ];
04326        char supplied_euid[128];
04327        int anon_flag = 0;
04328        int format_type = 0;
04329        char newusername[256];
04330        char newuseremail[256];
04331        struct CtdlMessage *msg;
04332        int anonymous = 0;
04333        char errmsg[SIZ];
04334        int err = 0;
04335        struct recptypes *valid = NULL;
04336        struct recptypes *valid_to = NULL;
04337        struct recptypes *valid_cc = NULL;
04338        struct recptypes *valid_bcc = NULL;
04339        char subject[SIZ];
04340        int subject_required = 0;
04341        int do_confirm = 0;
04342        long msgnum;
04343        int i, j;
04344        char buf[256];
04345        int newuseremail_ok = 0;
04346        char references[SIZ];
04347        char *ptr;
04348 
04349        unbuffer_output();
04350 
04351        post = extract_int(entargs, 0);
04352        extract_token(recp, entargs, 1, '|', sizeof recp);
04353        anon_flag = extract_int(entargs, 2);
04354        format_type = extract_int(entargs, 3);
04355        extract_token(subject, entargs, 4, '|', sizeof subject);
04356        extract_token(newusername, entargs, 5, '|', sizeof newusername);
04357        do_confirm = extract_int(entargs, 6);
04358        extract_token(cc, entargs, 7, '|', sizeof cc);
04359        extract_token(bcc, entargs, 8, '|', sizeof bcc);
04360        switch(CC->room.QRdefaultview) {
04361        case VIEW_NOTES:
04362        case VIEW_WIKI:
04363               extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
04364               break;
04365        default:
04366               supplied_euid[0] = 0;
04367               break;
04368        }
04369        extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
04370        extract_token(references, entargs, 11, '|', sizeof references);
04371        for (ptr=references; *ptr != 0; ++ptr) {
04372               if (*ptr == '!') *ptr = '|';
04373        }
04374 
04375        /* first check to make sure the request is valid. */
04376 
04377        err = CtdlDoIHavePermissionToPostInThisRoom(
04378               errmsg,
04379               sizeof errmsg,
04380               NULL,
04381               POST_LOGGED_IN,
04382               (!IsEmptyStr(references))          /* is this a reply?  or a top-level post? */
04383               );
04384        if (err)
04385        {
04386               cprintf("%d %s\n", err, errmsg);
04387               return;
04388        }
04389 
04390        /* Check some other permission type things. */
04391 
04392        if (IsEmptyStr(newusername)) {
04393               strcpy(newusername, CCC->user.fullname);
04394        }
04395        if (  (CCC->user.axlevel < AxAideU)
04396              && (strcasecmp(newusername, CCC->user.fullname))
04397              && (strcasecmp(newusername, CCC->cs_inet_fn))
04398               ) {    
04399               cprintf("%d You don't have permission to author messages as '%s'.\n",
04400                      ERROR + HIGHER_ACCESS_REQUIRED,
04401                      newusername
04402                      );
04403               return;
04404        }
04405 
04406 
04407        if (IsEmptyStr(newuseremail)) {
04408               newuseremail_ok = 1;
04409        }
04410 
04411        if (!IsEmptyStr(newuseremail)) {
04412               if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
04413                      newuseremail_ok = 1;
04414               }
04415               else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
04416                      j = num_tokens(CCC->cs_inet_other_emails, '|');
04417                      for (i=0; i<j; ++i) {
04418                             extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
04419                             if (!strcasecmp(newuseremail, buf)) {
04420                                    newuseremail_ok = 1;
04421                             }
04422                      }
04423               }
04424        }
04425 
04426        if (!newuseremail_ok) {
04427               cprintf("%d You don't have permission to author messages as '%s'.\n",
04428                      ERROR + HIGHER_ACCESS_REQUIRED,
04429                      newuseremail
04430                      );
04431               return;
04432        }
04433 
04434        CCC->cs_flags |= CS_POSTING;
04435 
04436        /* In mailbox rooms we have to behave a little differently --
04437         * make sure the user has specified at least one recipient.  Then
04438         * validate the recipient(s).  We do this for the Mail> room, as
04439         * well as any room which has the "Mailbox" view set - unless it
04440         * is the DRAFTS room which does not require recipients
04441         */
04442 
04443        if ( (  ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
04444               || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
04445                    ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
04446               if (CCC->user.axlevel < AxProbU) {
04447                      strcpy(recp, "sysop");
04448                      strcpy(cc, "");
04449                      strcpy(bcc, "");
04450               }
04451 
04452               valid_to = validate_recipients(recp, NULL, 0);
04453               if (valid_to->num_error > 0) {
04454                      cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
04455                      free_recipients(valid_to);
04456                      return;
04457               }
04458 
04459               valid_cc = validate_recipients(cc, NULL, 0);
04460               if (valid_cc->num_error > 0) {
04461                      cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
04462                      free_recipients(valid_to);
04463                      free_recipients(valid_cc);
04464                      return;
04465               }
04466 
04467               valid_bcc = validate_recipients(bcc, NULL, 0);
04468               if (valid_bcc->num_error > 0) {
04469                      cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
04470                      free_recipients(valid_to);
04471                      free_recipients(valid_cc);
04472                      free_recipients(valid_bcc);
04473                      return;
04474               }
04475 
04476               /* Recipient required, but none were specified */
04477               if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
04478                      free_recipients(valid_to);
04479                      free_recipients(valid_cc);
04480                      free_recipients(valid_bcc);
04481                      cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
04482                      return;
04483               }
04484 
04485               if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
04486                      if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
04487                             cprintf("%d You do not have permission "
04488                                    "to send Internet mail.\n",
04489                                    ERROR + HIGHER_ACCESS_REQUIRED);
04490                             free_recipients(valid_to);
04491                             free_recipients(valid_cc);
04492                             free_recipients(valid_bcc);
04493                             return;
04494                      }
04495               }
04496 
04497               if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0)
04498                    && (CCC->user.axlevel < AxNetU) ) {
04499                      cprintf("%d Higher access required for network mail.\n",
04500                             ERROR + HIGHER_ACCESS_REQUIRED);
04501                      free_recipients(valid_to);
04502                      free_recipients(valid_cc);
04503                      free_recipients(valid_bcc);
04504                      return;
04505               }
04506        
04507               if ((RESTRICT_INTERNET == 1)
04508                   && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
04509                   && ((CCC->user.flags & US_INTERNET) == 0)
04510                   && (!CCC->internal_pgm)) {
04511                      cprintf("%d You don't have access to Internet mail.\n",
04512                             ERROR + HIGHER_ACCESS_REQUIRED);
04513                      free_recipients(valid_to);
04514                      free_recipients(valid_cc);
04515                      free_recipients(valid_bcc);
04516                      return;
04517               }
04518 
04519        }
04520 
04521        /* Is this a room which has anonymous-only or anonymous-option? */
04522        anonymous = MES_NORMAL;
04523        if (CCC->room.QRflags & QR_ANONONLY) {
04524               anonymous = MES_ANONONLY;
04525        }
04526        if (CCC->room.QRflags & QR_ANONOPT) {
04527               if (anon_flag == 1) {       /* only if the user requested it */
04528                      anonymous = MES_ANONOPT;
04529               }
04530        }
04531 
04532        if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
04533               recp[0] = 0;
04534        }
04535 
04536        /* Recommend to the client that the use of a message subject is
04537         * strongly recommended in this room, if either the SUBJECTREQ flag
04538         * is set, or if there is one or more Internet email recipients.
04539         */
04540        if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
04541        if ((valid_to)       && (valid_to->num_internet > 0))   subject_required = 1;
04542        if ((valid_cc)       && (valid_cc->num_internet > 0))   subject_required = 1;
04543        if ((valid_bcc)      && (valid_bcc->num_internet > 0))  subject_required = 1;
04544 
04545        /* If we're only checking the validity of the request, return
04546         * success without creating the message.
04547         */
04548        if (post == 0) {
04549               cprintf("%d %s|%d\n", CIT_OK,
04550                      ((valid_to != NULL) ? valid_to->display_recp : ""), 
04551                      subject_required);
04552               free_recipients(valid_to);
04553               free_recipients(valid_cc);
04554               free_recipients(valid_bcc);
04555               return;
04556        }
04557 
04558        /* We don't need these anymore because we'll do it differently below */
04559        free_recipients(valid_to);
04560        free_recipients(valid_cc);
04561        free_recipients(valid_bcc);
04562 
04563        /* Read in the message from the client. */
04564        if (do_confirm) {
04565               cprintf("%d send message\n", START_CHAT_MODE);
04566        } else {
04567               cprintf("%d send message\n", SEND_LISTING);
04568        }
04569 
04570        msg = CtdlMakeMessage(&CCC->user, recp, cc,
04571                            CCC->room.QRname, anonymous, format_type,
04572                            newusername, newuseremail, subject,
04573                            ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
04574                            NULL, references);
04575 
04576        /* Put together one big recipients struct containing to/cc/bcc all in
04577         * one.  This is for the envelope.
04578         */
04579        char *all_recps = malloc(SIZ * 3);
04580        strcpy(all_recps, recp);
04581        if (!IsEmptyStr(cc)) {
04582               if (!IsEmptyStr(all_recps)) {
04583                      strcat(all_recps, ",");
04584               }
04585               strcat(all_recps, cc);
04586        }
04587        if (!IsEmptyStr(bcc)) {
04588               if (!IsEmptyStr(all_recps)) {
04589                      strcat(all_recps, ",");
04590               }
04591               strcat(all_recps, bcc);
04592        }
04593        if (!IsEmptyStr(all_recps)) {
04594               valid = validate_recipients(all_recps, NULL, 0);
04595        }
04596        else {
04597               valid = NULL;
04598        }
04599        free(all_recps);
04600 
04601        if ((valid != NULL) && (valid->num_room == 1))
04602        {
04603               /* posting into an ML room? set the envelope from 
04604                * to the actual mail address so others get a valid
04605                * reply-to-header.
04606                */
04607               msg->cm_fields['V'] = strdup(valid->recp_orgroom);
04608        }
04609 
04610        if (msg != NULL) {
04611               msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
04612               if (do_confirm) {
04613                      cprintf("%ld\n", msgnum);
04614 
04615                      if (StrLength(CCC->StatusMessage) > 0) {
04616                             cprintf("%s\n", ChrPtr(CCC->StatusMessage));
04617                      }
04618                      else if (msgnum >= 0L) {
04619                             client_write(HKEY("Message accepted.\n"));
04620                      }
04621                      else {
04622                             client_write(HKEY("Internal error.\n"));
04623                      }
04624 
04625                      if (msg->cm_fields['E'] != NULL) {
04626                             cprintf("%s\n", msg->cm_fields['E']);
04627                      } else {
04628                             cprintf("\n");
04629                      }
04630                      cprintf("000\n");
04631               }
04632 
04633               CtdlFreeMessage(msg);
04634        }
04635        if (valid != NULL) {
04636               free_recipients(valid);
04637        }
04638        return;
04639 }
04640 
04641 
04642 
04643 /*
04644  * API function to delete messages which match a set of criteria
04645  * (returns the actual number of messages deleted)
04646  */
04647 int CtdlDeleteMessages(char *room_name,          /* which room */
04648                      long *dmsgnums,             /* array of msg numbers to be deleted */
04649                      int num_dmsgnums,    /* number of msgs to be deleted, or 0 for "any" */
04650                      char *content_type   /* or "" for any.  regular expressions expected. */
04651        )
04652 {
04653        struct CitContext *CCC = CC;
04654        struct ctdlroom qrbuf;
04655        struct cdbdata *cdbfr;
04656        long *msglist = NULL;
04657        long *dellist = NULL;
04658        int num_msgs = 0;
04659        int i, j;
04660        int num_deleted = 0;
04661        int delete_this;
04662        struct MetaData smi;
04663        regex_t re;
04664        regmatch_t pm;
04665        int need_to_free_re = 0;
04666 
04667        if (content_type) if (!IsEmptyStr(content_type)) {
04668                      regcomp(&re, content_type, 0);
04669                      need_to_free_re = 1;
04670               }
04671        MSG_syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
04672                  room_name, num_dmsgnums, content_type);
04673 
04674        /* get room record, obtaining a lock... */
04675        if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
04676               MSG_syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
04677                         room_name);
04678               if (need_to_free_re) regfree(&re);
04679               return (0);   /* room not found */
04680        }
04681        cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
04682 
04683        if (cdbfr != NULL) {
04684               dellist = malloc(cdbfr->len);
04685               msglist = (long *) cdbfr->ptr;
04686               cdbfr->ptr = NULL;   /* CtdlDeleteMessages() now owns this memory */
04687               num_msgs = cdbfr->len / sizeof(long);
04688               cdb_free(cdbfr);
04689        }
04690        if (num_msgs > 0) {
04691               int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
04692               int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
04693               int have_more_del = 1;
04694 
04695               num_msgs = sort_msglist(msglist, num_msgs);
04696               if (num_dmsgnums > 1)
04697                      num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
04698 /*
04699               {
04700                      StrBuf *dbg = NewStrBuf();
04701                      for (i = 0; i < num_dmsgnums; i++)
04702                             StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
04703                      MSG_syslog(LOG_DEBUG, "Deleting before: %s", ChrPtr(dbg));
04704                      FreeStrBuf(&dbg);
04705               }
04706 */
04707               i = 0; j = 0;
04708               while ((i < num_msgs) && (have_more_del)) {
04709                      delete_this = 0x00;
04710 
04711 
04712                      /* Set/clear a bit for each criterion */
04713 
04714                      /* 0 messages in the list or a null list means that we are
04715                       * interested in deleting any messages which meet the other criteria.
04716                       */
04717                      if (have_delmsgs) {
04718                             delete_this |= 0x01;
04719                      }
04720                      else {
04721                             while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
04722                             if (msglist[i] == dmsgnums[j]) {
04723                                    delete_this |= 0x01;
04724                             }
04725                             j++;
04726                             have_more_del = (j < num_dmsgnums);
04727                      }
04728 
04729                      if (have_contenttype) {
04730                             GetMetaData(&smi, msglist[i]);
04731                             if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
04732                                    delete_this |= 0x02;
04733                             }
04734                      } else {
04735                             delete_this |= 0x02;
04736                      }
04737 
04738                      /* Delete message only if all bits are set */
04739                      if (delete_this == 0x03) {
04740                             dellist[num_deleted++] = msglist[i];
04741                             msglist[i] = 0L;
04742                      }
04743                      i++;
04744               }
04745 /*
04746               {
04747                      StrBuf *dbg = NewStrBuf();
04748                      for (i = 0; i < num_deleted; i++)
04749                             StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
04750                      MSG_syslog(LOG_DEBUG, "Deleting: %s", ChrPtr(dbg));
04751                      FreeStrBuf(&dbg);
04752               }
04753 */
04754               num_msgs = sort_msglist(msglist, num_msgs);
04755               cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
04756                        msglist, (int)(num_msgs * sizeof(long)));
04757 
04758               if (num_msgs > 0)
04759                      qrbuf.QRhighest = msglist[num_msgs - 1];
04760               else
04761                      qrbuf.QRhighest = 0;
04762        }
04763        CtdlPutRoomLock(&qrbuf);
04764 
04765        /* Go through the messages we pulled out of the index, and decrement
04766         * their reference counts by 1.  If this is the only room the message
04767         * was in, the reference count will reach zero and the message will
04768         * automatically be deleted from the database.  We do this in a
04769         * separate pass because there might be plug-in hooks getting called,
04770         * and we don't want that happening during an S_ROOMS critical
04771         * section.
04772         */
04773        if (num_deleted) {
04774               for (i=0; i<num_deleted; ++i) {
04775                      PerformDeleteHooks(qrbuf.QRname, dellist[i]);
04776               }
04777               AdjRefCountList(dellist, num_deleted, -1);
04778        }
04779        /* Now free the memory we used, and go away. */
04780        if (msglist != NULL) free(msglist);
04781        if (dellist != NULL) free(dellist);
04782        MSG_syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
04783        if (need_to_free_re) regfree(&re);
04784        return (num_deleted);
04785 }
04786 
04787 
04788 
04789 /*
04790  * Check whether the current user has permission to delete messages from
04791  * the current room (returns 1 for yes, 0 for no)
04792  */
04793 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
04794        int ra;
04795        CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
04796        if (ra & UA_DELETEALLOWED) return(1);
04797        return(0);
04798 }
04799 
04800 
04801 
04802 
04803 /*
04804  * Delete message from current room
04805  */
04806 void cmd_dele(char *args)
04807 {
04808        int num_deleted;
04809        int i;
04810        char msgset[SIZ];
04811        char msgtok[32];
04812        long *msgs;
04813        int num_msgs = 0;
04814 
04815        extract_token(msgset, args, 0, '|', sizeof msgset);
04816        num_msgs = num_tokens(msgset, ',');
04817        if (num_msgs < 1) {
04818               cprintf("%d Nothing to do.\n", CIT_OK);
04819               return;
04820        }
04821 
04822        if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
04823               cprintf("%d Higher access required.\n",
04824                      ERROR + HIGHER_ACCESS_REQUIRED);
04825               return;
04826        }
04827 
04828        /*
04829         * Build our message set to be moved/copied
04830         */
04831        msgs = malloc(num_msgs * sizeof(long));
04832        for (i=0; i<num_msgs; ++i) {
04833               extract_token(msgtok, msgset, i, ',', sizeof msgtok);
04834               msgs[i] = atol(msgtok);
04835        }
04836 
04837        num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
04838        free(msgs);
04839 
04840        if (num_deleted) {
04841               cprintf("%d %d message%s deleted.\n", CIT_OK,
04842                      num_deleted, ((num_deleted != 1) ? "s" : ""));
04843        } else {
04844               cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
04845        }
04846 }
04847 
04848 
04849 
04850 
04851 /*
04852  * move or copy a message to another room
04853  */
04854 void cmd_move(char *args)
04855 {
04856        char msgset[SIZ];
04857        char msgtok[32];
04858        long *msgs;
04859        int num_msgs = 0;
04860 
04861        char targ[ROOMNAMELEN];
04862        struct ctdlroom qtemp;
04863        int err;
04864        int is_copy = 0;
04865        int ra;
04866        int permit = 0;
04867        int i;
04868 
04869        extract_token(msgset, args, 0, '|', sizeof msgset);
04870        num_msgs = num_tokens(msgset, ',');
04871        if (num_msgs < 1) {
04872               cprintf("%d Nothing to do.\n", CIT_OK);
04873               return;
04874        }
04875 
04876        extract_token(targ, args, 1, '|', sizeof targ);
04877        convert_room_name_macros(targ, sizeof targ);
04878        targ[ROOMNAMELEN - 1] = 0;
04879        is_copy = extract_int(args, 2);
04880 
04881        if (CtdlGetRoom(&qtemp, targ) != 0) {
04882               cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
04883               return;
04884        }
04885 
04886        if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
04887               cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
04888               return;
04889        }
04890 
04891        CtdlGetUser(&CC->user, CC->curr_user);
04892        CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
04893 
04894        /* Check for permission to perform this operation.
04895         * Remember: "CC->room" is source, "qtemp" is target.
04896         */
04897        permit = 0;
04898 
04899        /* Aides can move/copy */
04900        if (CC->user.axlevel >= AxAideU) permit = 1;
04901 
04902        /* Room aides can move/copy */
04903        if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
04904 
04905        /* Permit move/copy from personal rooms */
04906        if ((CC->room.QRflags & QR_MAILBOX)
04907            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
04908 
04909        /* Permit only copy from public to personal room */
04910        if ( (is_copy)
04911             && (!(CC->room.QRflags & QR_MAILBOX))
04912             && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
04913 
04914        /* Permit message removal from collaborative delete rooms */
04915        if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
04916 
04917        /* Users allowed to post into the target room may move into it too. */
04918        if ((CC->room.QRflags & QR_MAILBOX) && 
04919            (qtemp.QRflags & UA_POSTALLOWED))  permit = 1;
04920 
04921        /* User must have access to target room */
04922        if (!(ra & UA_KNOWN))  permit = 0;
04923 
04924        if (!permit) {
04925               cprintf("%d Higher access required.\n",
04926                      ERROR + HIGHER_ACCESS_REQUIRED);
04927               return;
04928        }
04929 
04930        /*
04931         * Build our message set to be moved/copied
04932         */
04933        msgs = malloc(num_msgs * sizeof(long));
04934        for (i=0; i<num_msgs; ++i) {
04935               extract_token(msgtok, msgset, i, ',', sizeof msgtok);
04936               msgs[i] = atol(msgtok);
04937        }
04938 
04939        /*
04940         * Do the copy
04941         */
04942        err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
04943        if (err != 0) {
04944               cprintf("%d Cannot store message(s) in %s: error %d\n",
04945                      err, targ, err);
04946               free(msgs);
04947               return;
04948        }
04949 
04950        /* Now delete the message from the source room,
04951         * if this is a 'move' rather than a 'copy' operation.
04952         */
04953        if (is_copy == 0) {
04954               CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
04955        }
04956        free(msgs);
04957 
04958        cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
04959 }
04960 
04961 
04962 
04963 /*
04964  * GetMetaData()  -  Get the supplementary record for a message
04965  */
04966 void GetMetaData(struct MetaData *smibuf, long msgnum)
04967 {
04968 
04969        struct cdbdata *cdbsmi;
04970        long TheIndex;
04971 
04972        memset(smibuf, 0, sizeof(struct MetaData));
04973        smibuf->meta_msgnum = msgnum;
04974        smibuf->meta_refcount = 1;  /* Default reference count is 1 */
04975 
04976        /* Use the negative of the message number for its supp record index */
04977        TheIndex = (0L - msgnum);
04978 
04979        cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
04980        if (cdbsmi == NULL) {
04981               return;              /* record not found; go with defaults */
04982        }
04983        memcpy(smibuf, cdbsmi->ptr,
04984               ((cdbsmi->len > sizeof(struct MetaData)) ?
04985               sizeof(struct MetaData) : cdbsmi->len));
04986        cdb_free(cdbsmi);
04987        return;
04988 }
04989 
04990 
04991 /*
04992  * PutMetaData()  -  (re)write supplementary record for a message
04993  */
04994 void PutMetaData(struct MetaData *smibuf)
04995 {
04996        long TheIndex;
04997 
04998        /* Use the negative of the message number for the metadata db index */
04999        TheIndex = (0L - smibuf->meta_msgnum);
05000 
05001        cdb_store(CDB_MSGMAIN,
05002                 &TheIndex, (int)sizeof(long),
05003                 smibuf, (int)sizeof(struct MetaData));
05004 
05005 }
05006 
05007 /*
05008  * AdjRefCount  -  submit an adjustment to the reference count for a message.
05009  *                 (These are just queued -- we actually process them later.)
05010  */
05011 void AdjRefCount(long msgnum, int incr)
05012 {
05013        struct CitContext *CCC = CC;
05014        struct arcq new_arcq;
05015        int rv = 0;
05016 
05017        MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
05018 
05019        begin_critical_section(S_SUPPMSGMAIN);
05020        if (arcfp == NULL) {
05021               arcfp = fopen(file_arcq, "ab+");
05022               chown(file_arcq, CTDLUID, (-1));
05023               chmod(file_arcq, 0600);
05024        }
05025        end_critical_section(S_SUPPMSGMAIN);
05026 
05027        /* msgnum < 0 means that we're trying to close the file */
05028        if (msgnum < 0) {
05029               MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
05030               begin_critical_section(S_SUPPMSGMAIN);
05031               if (arcfp != NULL) {
05032                      fclose(arcfp);
05033                      arcfp = NULL;
05034               }
05035               end_critical_section(S_SUPPMSGMAIN);
05036               return;
05037        }
05038 
05039        /*
05040         * If we can't open the queue, perform the operation synchronously.
05041         */
05042        if (arcfp == NULL) {
05043               TDAP_AdjRefCount(msgnum, incr);
05044               return;
05045        }
05046 
05047        new_arcq.arcq_msgnum = msgnum;
05048        new_arcq.arcq_delta = incr;
05049        rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
05050        if (rv == -1) {
05051               MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
05052                         file_arcq,
05053                         strerror(errno));
05054        }
05055        fflush(arcfp);
05056 
05057        return;
05058 }
05059 
05060 void AdjRefCountList(long *msgnum, long nmsg, int incr)
05061 {
05062        struct CitContext *CCC = CC;
05063        long i, the_size, offset;
05064        struct arcq *new_arcq;
05065        int rv = 0;
05066 
05067        MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
05068 
05069        begin_critical_section(S_SUPPMSGMAIN);
05070        if (arcfp == NULL) {
05071               arcfp = fopen(file_arcq, "ab+");
05072               chown(file_arcq, CTDLUID, (-1));
05073               chmod(file_arcq, 0600);
05074        }
05075        end_critical_section(S_SUPPMSGMAIN);
05076 
05077        /*
05078         * If we can't open the queue, perform the operation synchronously.
05079         */
05080        if (arcfp == NULL) {
05081               for (i = 0; i < nmsg; i++)
05082                      TDAP_AdjRefCount(msgnum[i], incr);
05083               return;
05084        }
05085 
05086        the_size = sizeof(struct arcq) * nmsg;
05087        new_arcq = malloc(the_size);
05088        for (i = 0; i < nmsg; i++) {
05089               new_arcq[i].arcq_msgnum = msgnum[i];
05090               new_arcq[i].arcq_delta = incr;
05091        }
05092        rv = 0;
05093        offset = 0;
05094        while ((rv >= 0) && (offset < the_size))
05095        {
05096               rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
05097               if (rv == -1) {
05098                      MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
05099                                file_arcq,
05100                                strerror(errno));
05101               }
05102               else {
05103                      offset += rv;
05104               }
05105        }
05106        free(new_arcq);
05107        fflush(arcfp);
05108 
05109        return;
05110 }
05111 
05112 
05113 /*
05114  * TDAP_ProcessAdjRefCountQueue()
05115  *
05116  * Process the queue of message count adjustments that was created by calls
05117  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
05118  * for each one.  This should be an "off hours" operation.
05119  */
05120 int TDAP_ProcessAdjRefCountQueue(void)
05121 {
05122        struct CitContext *CCC = CC;
05123        char file_arcq_temp[PATH_MAX];
05124        int r;
05125        FILE *fp;
05126        struct arcq arcq_rec;
05127        int num_records_processed = 0;
05128 
05129        snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
05130 
05131        begin_critical_section(S_SUPPMSGMAIN);
05132        if (arcfp != NULL) {
05133               fclose(arcfp);
05134               arcfp = NULL;
05135        }
05136 
05137        r = link(file_arcq, file_arcq_temp);
05138        if (r != 0) {
05139               MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
05140               end_critical_section(S_SUPPMSGMAIN);
05141               return(num_records_processed);
05142        }
05143 
05144        unlink(file_arcq);
05145        end_critical_section(S_SUPPMSGMAIN);
05146 
05147        fp = fopen(file_arcq_temp, "rb");
05148        if (fp == NULL) {
05149               MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
05150               return(num_records_processed);
05151        }
05152 
05153        while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
05154               TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
05155               ++num_records_processed;
05156        }
05157 
05158        fclose(fp);
05159        r = unlink(file_arcq_temp);
05160        if (r != 0) {
05161               MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
05162        }
05163 
05164        return(num_records_processed);
05165 }
05166 
05167 
05168 
05169 /*
05170  * TDAP_AdjRefCount  -  adjust the reference count for a message.
05171  *                      This one does it "for real" because it's called by
05172  *                      the autopurger function that processes the queue
05173  *                      created by AdjRefCount().   If a message's reference
05174  *                      count becomes zero, we also delete the message from
05175  *                      disk and de-index it.
05176  */
05177 void TDAP_AdjRefCount(long msgnum, int incr)
05178 {
05179        struct CitContext *CCC = CC;
05180 
05181        struct MetaData smi;
05182        long delnum;
05183 
05184        /* This is a *tight* critical section; please keep it that way, as
05185         * it may get called while nested in other critical sections.  
05186         * Complicating this any further will surely cause deadlock!
05187         */
05188        begin_critical_section(S_SUPPMSGMAIN);
05189        GetMetaData(&smi, msgnum);
05190        smi.meta_refcount += incr;
05191        PutMetaData(&smi);
05192        end_critical_section(S_SUPPMSGMAIN);
05193        MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
05194                  msgnum, incr, smi.meta_refcount
05195               );
05196 
05197        /* If the reference count is now zero, delete the message
05198         * (and its supplementary record as well).
05199         */
05200        if (smi.meta_refcount == 0) {
05201               MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
05202               
05203               /* Call delete hooks with NULL room to show it has gone altogether */
05204               PerformDeleteHooks(NULL, msgnum);
05205 
05206               /* Remove from message base */
05207               delnum = msgnum;
05208               cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
05209               cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
05210 
05211               /* Remove metadata record */
05212               delnum = (0L - msgnum);
05213               cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
05214        }
05215 
05216 }
05217 
05218 /*
05219  * Write a generic object to this room
05220  *
05221  * Note: this could be much more efficient.  Right now we use two temporary
05222  * files, and still pull the message into memory as with all others.
05223  */
05224 void CtdlWriteObject(char *req_room,                    /* Room to stuff it in */
05225                    char *content_type,           /* MIME type of this object */
05226                    char *raw_message,            /* Data to be written */
05227                    off_t raw_length,             /* Size of raw_message */
05228                    struct ctdluser *is_mailbox,  /* Mailbox room? */
05229                    int is_binary,                /* Is encoding necessary? */
05230                    int is_unique,                /* Del others of this type? */
05231                    unsigned int flags            /* Internal save flags */
05232        )
05233 {
05234        struct CitContext *CCC = CC;
05235        struct ctdlroom qrbuf;
05236        char roomname[ROOMNAMELEN];
05237        struct CtdlMessage *msg;
05238        char *encoded_message = NULL;
05239 
05240        if (is_mailbox != NULL) {
05241               CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
05242        }
05243        else {
05244               safestrncpy(roomname, req_room, sizeof(roomname));
05245        }
05246 
05247        MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
05248 
05249        if (is_binary) {
05250               encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
05251        }
05252        else {
05253               encoded_message = malloc((size_t)(raw_length + 4096));
05254        }
05255 
05256        sprintf(encoded_message, "Content-type: %s\n", content_type);
05257 
05258        if (is_binary) {
05259               sprintf(&encoded_message[strlen(encoded_message)],
05260                      "Content-transfer-encoding: base64\n\n"
05261                      );
05262        }
05263        else {
05264               sprintf(&encoded_message[strlen(encoded_message)],
05265                      "Content-transfer-encoding: 7bit\n\n"
05266                      );
05267        }
05268 
05269        if (is_binary) {
05270               CtdlEncodeBase64(
05271                      &encoded_message[strlen(encoded_message)],
05272                      raw_message,
05273                      (int)raw_length,
05274                      0
05275                      );
05276        }
05277        else {
05278               memcpy(
05279                      &encoded_message[strlen(encoded_message)],
05280                      raw_message,
05281                      (int)(raw_length+1)
05282                      );
05283        }
05284 
05285        MSGM_syslog(LOG_DEBUG, "Allocating\n");
05286        msg = malloc(sizeof(struct CtdlMessage));
05287        memset(msg, 0, sizeof(struct CtdlMessage));
05288        msg->cm_magic = CTDLMESSAGE_MAGIC;
05289        msg->cm_anon_type = MES_NORMAL;
05290        msg->cm_format_type = 4;
05291        msg->cm_fields['A'] = strdup(CCC->user.fullname);
05292        msg->cm_fields['O'] = strdup(req_room);
05293        msg->cm_fields['N'] = strdup(config.c_nodename);
05294        msg->cm_fields['H'] = strdup(config.c_humannode);
05295        msg->cm_flags = flags;
05296        
05297        msg->cm_fields['M'] = encoded_message;
05298 
05299        /* Create the requested room if we have to. */
05300        if (CtdlGetRoom(&qrbuf, roomname) != 0) {
05301               CtdlCreateRoom(roomname, 
05302                             ( (is_mailbox != NULL) ? 5 : 3 ),
05303                             "", 0, 1, 0, VIEW_BBS);
05304        }
05305        /* If the caller specified this object as unique, delete all
05306         * other objects of this type that are currently in the room.
05307         */
05308        if (is_unique) {
05309               MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
05310                         CtdlDeleteMessages(roomname, NULL, 0, content_type)
05311                      );
05312        }
05313        /* Now write the data */
05314        CtdlSubmitMsg(msg, NULL, roomname, 0);
05315        CtdlFreeMessage(msg);
05316 }
05317 
05318 
05319 
05320 
05321 
05322 
05323 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
05324        config_msgnum = msgnum;
05325 }
05326 
05327 
05328 char *CtdlGetSysConfig(char *sysconfname) {
05329        char hold_rm[ROOMNAMELEN];
05330        long msgnum;
05331        char *conf;
05332        struct CtdlMessage *msg;
05333        char buf[SIZ];
05334        
05335        strcpy(hold_rm, CC->room.QRname);
05336        if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
05337               CtdlGetRoom(&CC->room, hold_rm);
05338               return NULL;
05339        }
05340 
05341 
05342        /* We want the last (and probably only) config in this room */
05343        begin_critical_section(S_CONFIG);
05344        config_msgnum = (-1L);
05345        CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
05346                         CtdlGetSysConfigBackend, NULL);
05347        msgnum = config_msgnum;
05348        end_critical_section(S_CONFIG);
05349 
05350        if (msgnum < 0L) {
05351               conf = NULL;
05352        }
05353        else {
05354               msg = CtdlFetchMessage(msgnum, 1);
05355               if (msg != NULL) {
05356                      conf = strdup(msg->cm_fields['M']);
05357                      CtdlFreeMessage(msg);
05358               }
05359               else {
05360                      conf = NULL;
05361               }
05362        }
05363 
05364        CtdlGetRoom(&CC->room, hold_rm);
05365 
05366        if (conf != NULL) do {
05367                      extract_token(buf, conf, 0, '\n', sizeof buf);
05368                      strcpy(conf, &conf[strlen(buf)+1]);
05369               } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
05370 
05371        return(conf);
05372 }
05373 
05374 
05375 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
05376        CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
05377 }
05378 
05379 
05380 /*
05381  * Determine whether a given Internet address belongs to the current user
05382  */
05383 int CtdlIsMe(char *addr, int addr_buf_len)
05384 {
05385        struct recptypes *recp;
05386        int i;
05387 
05388        recp = validate_recipients(addr, NULL, 0);
05389        if (recp == NULL) return(0);
05390 
05391        if (recp->num_local == 0) {
05392               free_recipients(recp);
05393               return(0);
05394        }
05395 
05396        for (i=0; i<recp->num_local; ++i) {
05397               extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
05398               if (!strcasecmp(addr, CC->user.fullname)) {
05399                      free_recipients(recp);
05400                      return(1);
05401               }
05402        }
05403 
05404        free_recipients(recp);
05405        return(0);
05406 }
05407 
05408 
05409 /*
05410  * Citadel protocol command to do the same
05411  */
05412 void cmd_isme(char *argbuf) {
05413        char addr[256];
05414 
05415        if (CtdlAccessCheck(ac_logged_in)) return;
05416        extract_token(addr, argbuf, 0, '|', sizeof addr);
05417 
05418        if (CtdlIsMe(addr, sizeof addr)) {
05419               cprintf("%d %s\n", CIT_OK, addr);
05420        }
05421        else {
05422               cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
05423        }
05424 
05425 }
05426 
05427 
05428 /*****************************************************************************/
05429 /*                      MODULE INITIALIZATION STUFF                          */
05430 /*****************************************************************************/
05431 void SetMessageDebugEnabled(const int n)
05432 {
05433        MessageDebugEnabled = n;
05434 }
05435 CTDL_MODULE_INIT(msgbase)
05436 {
05437        if (!threading) {
05438               CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
05439 
05440               CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
05441               CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
05442               CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
05443               CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
05444               CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
05445               CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
05446               CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
05447               CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
05448               CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
05449               CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
05450               CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
05451               CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
05452        }
05453 
05454         /* return our Subversion id for the Log */
05455        return "msgbase";
05456 }