361f791a3e626776bbdaa6c95170da50028b36f8
[citadel.git] / citadel / msgbase.c
1 /*
2  * $Id$
3  *
4  * Implements the message store.
5  *
6  */
7
8 #include "sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
16 # include <time.h>
17 #else
18 # if HAVE_SYS_TIME_H
19 #  include <sys/time.h>
20 # else
21 #  include <time.h>
22 # endif
23 #endif
24
25
26 #include <ctype.h>
27 #include <string.h>
28 #include <limits.h>
29 #include <errno.h>
30 #include <stdarg.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <regex.h>
34 #include "citadel.h"
35 #include "server.h"
36 #include "serv_extensions.h"
37 #include "database.h"
38 #include "msgbase.h"
39 #include "support.h"
40 #include "sysdep_decls.h"
41 #include "citserver.h"
42 #include "room_ops.h"
43 #include "user_ops.h"
44 #include "file_ops.h"
45 #include "config.h"
46 #include "control.h"
47 #include "tools.h"
48 #include "mime_parser.h"
49 #include "html.h"
50 #include "genstamp.h"
51 #include "internet_addressing.h"
52 #include "vcard.h"
53 #include "euidindex.h"
54 #include "journaling.h"
55 #include "citadel_dirs.h"
56
57
58 long config_msgnum;
59 struct addresses_to_be_filed *atbf = NULL;
60
61 /* This temp file holds the queue of operations for AdjRefCount() */
62 static FILE *arcfp = NULL;
63
64 /* 
65  * This really belongs in serv_network.c, but I don't know how to export
66  * symbols between modules.
67  */
68 struct FilterList *filterlist = NULL;
69
70
71 /*
72  * These are the four-character field headers we use when outputting
73  * messages in Citadel format (as opposed to RFC822 format).
74  */
75 char *msgkeys[] = {
76         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
77         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
78         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
79         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
80         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
81         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
82         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
83         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
84         NULL, 
85         "from",
86         NULL, NULL, NULL,
87         "exti",
88         "rfca",
89         NULL, 
90         "hnod",
91         "msgn",
92         "jrnl",
93         NULL, NULL,
94         "text",
95         "node",
96         "room",
97         "path",
98         NULL,
99         "rcpt",
100         "spec",
101         "time",
102         "subj",
103         NULL,
104         NULL,
105         NULL,
106         "cccc",
107         NULL
108 };
109
110 /*
111  * This function is self explanatory.
112  * (What can I say, I'm in a weird mood today...)
113  */
114 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
115 {
116         int i;
117
118         for (i = 0; i < strlen(name); ++i) {
119                 if (name[i] == '@') {
120                         while (isspace(name[i - 1]) && i > 0) {
121                                 strcpy(&name[i - 1], &name[i]);
122                                 --i;
123                         }
124                         while (isspace(name[i + 1])) {
125                                 strcpy(&name[i + 1], &name[i + 2]);
126                         }
127                 }
128         }
129 }
130
131
132 /*
133  * Aliasing for network mail.
134  * (Error messages have been commented out, because this is a server.)
135  */
136 int alias(char *name)
137 {                               /* process alias and routing info for mail */
138         FILE *fp;
139         int a, i;
140         char aaa[SIZ], bbb[SIZ];
141         char *ignetcfg = NULL;
142         char *ignetmap = NULL;
143         int at = 0;
144         char node[64];
145         char testnode[64];
146         char buf[SIZ];
147
148         char original_name[256];
149         safestrncpy(original_name, name, sizeof original_name);
150
151         striplt(name);
152         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
153         stripallbut(name, '<', '>');
154
155         fp = fopen(file_mail_aliases, "r");
156         if (fp == NULL) {
157                 fp = fopen("/dev/null", "r");
158         }
159         if (fp == NULL) {
160                 return (MES_ERROR);
161         }
162         strcpy(aaa, "");
163         strcpy(bbb, "");
164         while (fgets(aaa, sizeof aaa, fp) != NULL) {
165                 while (isspace(name[0]))
166                         strcpy(name, &name[1]);
167                 aaa[strlen(aaa) - 1] = 0;
168                 strcpy(bbb, "");
169                 for (a = 0; a < strlen(aaa); ++a) {
170                         if (aaa[a] == ',') {
171                                 strcpy(bbb, &aaa[a + 1]);
172                                 aaa[a] = 0;
173                         }
174                 }
175                 if (!strcasecmp(name, aaa))
176                         strcpy(name, bbb);
177         }
178         fclose(fp);
179
180         /* Hit the Global Address Book */
181         if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
182                 strcpy(name, aaa);
183         }
184
185         if (strcasecmp(original_name, name)) {
186                 lprintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
187         }
188
189         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
190         for (a=0; a<strlen(name); ++a) {
191                 if (name[a] == '@') {
192                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
193                                 name[a] = 0;
194                                 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
195                         }
196                 }
197         }
198
199         /* determine local or remote type, see citadel.h */
200         at = haschar(name, '@');
201         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
202         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
203         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
204
205         /* figure out the delivery mode */
206         extract_token(node, name, 1, '@', sizeof node);
207
208         /* If there are one or more dots in the nodename, we assume that it
209          * is an FQDN and will attempt SMTP delivery to the Internet.
210          */
211         if (haschar(node, '.') > 0) {
212                 return(MES_INTERNET);
213         }
214
215         /* Otherwise we look in the IGnet maps for a valid Citadel node.
216          * Try directly-connected nodes first...
217          */
218         ignetcfg = CtdlGetSysConfig(IGNETCFG);
219         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
220                 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
221                 extract_token(testnode, buf, 0, '|', sizeof testnode);
222                 if (!strcasecmp(node, testnode)) {
223                         free(ignetcfg);
224                         return(MES_IGNET);
225                 }
226         }
227         free(ignetcfg);
228
229         /*
230          * Then try nodes that are two or more hops away.
231          */
232         ignetmap = CtdlGetSysConfig(IGNETMAP);
233         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
234                 extract_token(buf, ignetmap, i, '\n', sizeof buf);
235                 extract_token(testnode, buf, 0, '|', sizeof testnode);
236                 if (!strcasecmp(node, testnode)) {
237                         free(ignetmap);
238                         return(MES_IGNET);
239                 }
240         }
241         free(ignetmap);
242
243         /* If we get to this point it's an invalid node name */
244         return (MES_ERROR);
245 }
246
247
248 /*
249  * Back end for the MSGS command: output message number only.
250  */
251 void simple_listing(long msgnum, void *userdata)
252 {
253         cprintf("%ld\n", msgnum);
254 }
255
256
257
258 /*
259  * Back end for the MSGS command: output header summary.
260  */
261 void headers_listing(long msgnum, void *userdata)
262 {
263         struct CtdlMessage *msg;
264
265         msg = CtdlFetchMessage(msgnum, 0);
266         if (msg == NULL) {
267                 cprintf("%ld|0|||||\n", msgnum);
268                 return;
269         }
270
271         cprintf("%ld|%s|%s|%s|%s|%s|\n",
272                 msgnum,
273                 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
274                 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
275                 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
276                 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
277                 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
278         );
279         CtdlFreeMessage(msg);
280 }
281
282
283
284 /* Determine if a given message matches the fields in a message template.
285  * Return 0 for a successful match.
286  */
287 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
288         int i;
289
290         /* If there aren't any fields in the template, all messages will
291          * match.
292          */
293         if (template == NULL) return(0);
294
295         /* Null messages are bogus. */
296         if (msg == NULL) return(1);
297
298         for (i='A'; i<='Z'; ++i) {
299                 if (template->cm_fields[i] != NULL) {
300                         if (msg->cm_fields[i] == NULL) {
301                                 return 1;
302                         }
303                         if (strcasecmp(msg->cm_fields[i],
304                                 template->cm_fields[i])) return 1;
305                 }
306         }
307
308         /* All compares succeeded: we have a match! */
309         return 0;
310 }
311
312
313
314 /*
315  * Retrieve the "seen" message list for the current room.
316  */
317 void CtdlGetSeen(char *buf, int which_set) {
318         struct visit vbuf;
319
320         /* Learn about the user and room in question */
321         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
322
323         if (which_set == ctdlsetseen_seen)
324                 safestrncpy(buf, vbuf.v_seen, SIZ);
325         if (which_set == ctdlsetseen_answered)
326                 safestrncpy(buf, vbuf.v_answered, SIZ);
327 }
328
329
330
331 /*
332  * Manipulate the "seen msgs" string (or other message set strings)
333  */
334 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
335                 int target_setting, int which_set,
336                 struct ctdluser *which_user, struct ctdlroom *which_room) {
337         struct cdbdata *cdbfr;
338         int i, j, k;
339         int is_seen = 0;
340         int was_seen = 0;
341         long lo = (-1L);
342         long hi = (-1L);
343         long t = (-1L);
344         int trimming = 0;
345         struct visit vbuf;
346         long *msglist;
347         int num_msgs = 0;
348         char vset[SIZ];
349         char *is_set;   /* actually an array of booleans */
350         int num_sets;
351         int s;
352         char setstr[SIZ], lostr[SIZ], histr[SIZ];
353         size_t tmp;
354
355         /* Don't bother doing *anything* if we were passed a list of zero messages */
356         if (num_target_msgnums < 1) {
357                 return;
358         }
359
360         lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
361                 num_target_msgnums, target_msgnums[0],
362                 target_setting, which_set);
363
364         /* Learn about the user and room in question */
365         CtdlGetRelationship(&vbuf,
366                 ((which_user != NULL) ? which_user : &CC->user),
367                 ((which_room != NULL) ? which_room : &CC->room)
368         );
369
370         /* Load the message list */
371         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
372         if (cdbfr != NULL) {
373                 msglist = (long *) cdbfr->ptr;
374                 cdbfr->ptr = NULL;      /* CtdlSetSeen() now owns this memory */
375                 num_msgs = cdbfr->len / sizeof(long);
376                 cdb_free(cdbfr);
377         } else {
378                 return; /* No messages at all?  No further action. */
379         }
380
381         is_set = malloc(num_msgs * sizeof(char));
382         memset(is_set, 0, (num_msgs * sizeof(char)) );
383
384         /* Decide which message set we're manipulating */
385         switch(which_set) {
386                 case ctdlsetseen_seen:
387                         safestrncpy(vset, vbuf.v_seen, sizeof vset);
388                         break;
389                 case ctdlsetseen_answered:
390                         safestrncpy(vset, vbuf.v_answered, sizeof vset);
391                         break;
392         }
393
394         /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
395
396         /* Translate the existing sequence set into an array of booleans */
397         num_sets = num_tokens(vset, ',');
398         for (s=0; s<num_sets; ++s) {
399                 extract_token(setstr, vset, s, ',', sizeof setstr);
400
401                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
402                 if (num_tokens(setstr, ':') >= 2) {
403                         extract_token(histr, setstr, 1, ':', sizeof histr);
404                         if (!strcmp(histr, "*")) {
405                                 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
406                         }
407                 }
408                 else {
409                         strcpy(histr, lostr);
410                 }
411                 lo = atol(lostr);
412                 hi = atol(histr);
413
414                 for (i = 0; i < num_msgs; ++i) {
415                         if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
416                                 is_set[i] = 1;
417                         }
418                 }
419         }
420
421         /* Now translate the array of booleans back into a sequence set */
422         strcpy(vset, "");
423         lo = (-1L);
424         hi = (-1L);
425
426         for (i=0; i<num_msgs; ++i) {
427
428                 is_seen = is_set[i];    /* Default to existing setting */
429
430                 for (k=0; k<num_target_msgnums; ++k) {
431                         if (msglist[i] == target_msgnums[k]) {
432                                 is_seen = target_setting;
433                         }
434                 }
435
436                 if (is_seen) {
437                         if (lo < 0L) lo = msglist[i];
438                         hi = msglist[i];
439                 }
440
441                 if (  ((is_seen == 0) && (was_seen == 1))
442                    || ((is_seen == 1) && (i == num_msgs-1)) ) {
443
444                         /* begin trim-o-matic code */
445                         j=9;
446                         trimming = 0;
447                         while ( (strlen(vset) + 20) > sizeof vset) {
448                                 remove_token(vset, 0, ',');
449                                 trimming = 1;
450                                 if (j--) break; /* loop no more than 9 times */
451                         }
452                         if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
453                                 t = atol(vset);
454                                 if (t<2) t=2;
455                                 --t;
456                                 snprintf(lostr, sizeof lostr,
457                                         "1:%ld,%s", t, vset);
458                                 safestrncpy(vset, lostr, sizeof vset);
459                         }
460                         /* end trim-o-matic code */
461
462                         tmp = strlen(vset);
463                         if (tmp > 0) {
464                                 strcat(vset, ",");
465                                 ++tmp;
466                         }
467                         if (lo == hi) {
468                                 snprintf(&vset[tmp], (sizeof vset) - tmp,
469                                          "%ld", lo);
470                         }
471                         else {
472                                 snprintf(&vset[tmp], (sizeof vset) - tmp,
473                                          "%ld:%ld", lo, hi);
474                         }
475                         lo = (-1L);
476                         hi = (-1L);
477                 }
478                 was_seen = is_seen;
479         }
480
481         /* Decide which message set we're manipulating */
482         switch (which_set) {
483                 case ctdlsetseen_seen:
484                         safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
485                         break;
486                 case ctdlsetseen_answered:
487                         safestrncpy(vbuf.v_answered, vset,
488                                                 sizeof vbuf.v_answered);
489                         break;
490         }
491         free(is_set);
492
493         /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
494         free(msglist);
495         CtdlSetRelationship(&vbuf,
496                 ((which_user != NULL) ? which_user : &CC->user),
497                 ((which_room != NULL) ? which_room : &CC->room)
498         );
499 }
500
501
502 /*
503  * API function to perform an operation for each qualifying message in the
504  * current room.  (Returns the number of messages processed.)
505  */
506 int CtdlForEachMessage(int mode, long ref, char *search_string,
507                         char *content_type,
508                         struct CtdlMessage *compare,
509                         void (*CallBack) (long, void *),
510                         void *userdata)
511 {
512
513         int a, i, j;
514         struct visit vbuf;
515         struct cdbdata *cdbfr;
516         long *msglist = NULL;
517         int num_msgs = 0;
518         int num_processed = 0;
519         long thismsg;
520         struct MetaData smi;
521         struct CtdlMessage *msg = NULL;
522         int is_seen = 0;
523         long lastold = 0L;
524         int printed_lastold = 0;
525         int num_search_msgs = 0;
526         long *search_msgs = NULL;
527         regex_t re;
528         int need_to_free_re = 0;
529         regmatch_t pm;
530
531         if (content_type) if (!IsEmptyStr(content_type)) {
532                 regcomp(&re, content_type, 0);
533                 need_to_free_re = 1;
534         }
535
536         /* Learn about the user and room in question */
537         getuser(&CC->user, CC->curr_user);
538         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
539
540         /* Load the message list */
541         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
542         if (cdbfr != NULL) {
543                 msglist = (long *) cdbfr->ptr;
544                 cdbfr->ptr = NULL;      /* CtdlForEachMessage() now owns this memory */
545                 num_msgs = cdbfr->len / sizeof(long);
546                 cdb_free(cdbfr);
547         } else {
548                 if (need_to_free_re) regfree(&re);
549                 return 0;       /* No messages at all?  No further action. */
550         }
551
552
553         /*
554          * Now begin the traversal.
555          */
556         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
557
558                 /* If the caller is looking for a specific MIME type, filter
559                  * out all messages which are not of the type requested.
560                  */
561                 if (content_type != NULL) if (!IsEmptyStr(content_type)) {
562
563                         /* This call to GetMetaData() sits inside this loop
564                          * so that we only do the extra database read per msg
565                          * if we need to.  Doing the extra read all the time
566                          * really kills the server.  If we ever need to use
567                          * metadata for another search criterion, we need to
568                          * move the read somewhere else -- but still be smart
569                          * enough to only do the read if the caller has
570                          * specified something that will need it.
571                          */
572                         GetMetaData(&smi, msglist[a]);
573
574                         /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
575                         if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
576                                 msglist[a] = 0L;
577                         }
578                 }
579         }
580
581         num_msgs = sort_msglist(msglist, num_msgs);
582
583         /* If a template was supplied, filter out the messages which
584          * don't match.  (This could induce some delays!)
585          */
586         if (num_msgs > 0) {
587                 if (compare != NULL) {
588                         for (a = 0; a < num_msgs; ++a) {
589                                 msg = CtdlFetchMessage(msglist[a], 1);
590                                 if (msg != NULL) {
591                                         if (CtdlMsgCmp(msg, compare)) {
592                                                 msglist[a] = 0L;
593                                         }
594                                         CtdlFreeMessage(msg);
595                                 }
596                         }
597                 }
598         }
599
600         /* If a search string was specified, get a message list from
601          * the full text index and remove messages which aren't on both
602          * lists.
603          *
604          * How this works:
605          * Since the lists are sorted and strictly ascending, and the
606          * output list is guaranteed to be shorter than or equal to the
607          * input list, we overwrite the bottom of the input list.  This
608          * eliminates the need to memmove big chunks of the list over and
609          * over again.
610          */
611         if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
612
613                 /* Call search module via hook mechanism.
614                  * NULL means use any search function available.
615                  * otherwise replace with a char * to name of search routine
616                  */
617                 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
618
619                 if (num_search_msgs > 0) {
620         
621                         int orig_num_msgs;
622
623                         orig_num_msgs = num_msgs;
624                         num_msgs = 0;
625                         for (i=0; i<orig_num_msgs; ++i) {
626                                 for (j=0; j<num_search_msgs; ++j) {
627                                         if (msglist[i] == search_msgs[j]) {
628                                                 msglist[num_msgs++] = msglist[i];
629                                         }
630                                 }
631                         }
632                 }
633                 else {
634                         num_msgs = 0;   /* No messages qualify */
635                 }
636                 if (search_msgs != NULL) free(search_msgs);
637
638                 /* Now that we've purged messages which don't contain the search
639                  * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
640                  * point on.
641                  */
642                 mode = MSGS_ALL;
643         }
644
645         /*
646          * Now iterate through the message list, according to the
647          * criteria supplied by the caller.
648          */
649         if (num_msgs > 0)
650                 for (a = 0; a < num_msgs; ++a) {
651                         thismsg = msglist[a];
652                         if (mode == MSGS_ALL) {
653                                 is_seen = 0;
654                         }
655                         else {
656                                 is_seen = is_msg_in_sequence_set(
657                                                         vbuf.v_seen, thismsg);
658                                 if (is_seen) lastold = thismsg;
659                         }
660                         if ((thismsg > 0L)
661                             && (
662
663                                        (mode == MSGS_ALL)
664                                        || ((mode == MSGS_OLD) && (is_seen))
665                                        || ((mode == MSGS_NEW) && (!is_seen))
666                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
667                                    || ((mode == MSGS_FIRST) && (a < ref))
668                                 || ((mode == MSGS_GT) && (thismsg > ref))
669                                 || ((mode == MSGS_EQ) && (thismsg == ref))
670                             )
671                             ) {
672                                 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
673                                         if (CallBack)
674                                                 CallBack(lastold, userdata);
675                                         printed_lastold = 1;
676                                         ++num_processed;
677                                 }
678                                 if (CallBack) CallBack(thismsg, userdata);
679                                 ++num_processed;
680                         }
681                 }
682         free(msglist);          /* Clean up */
683         if (need_to_free_re) regfree(&re);
684         return num_processed;
685 }
686
687
688
689 /*
690  * cmd_msgs()  -  get list of message #'s in this room
691  *              implements the MSGS server command using CtdlForEachMessage()
692  */
693 void cmd_msgs(char *cmdbuf)
694 {
695         int mode = 0;
696         char which[16];
697         char buf[256];
698         char tfield[256];
699         char tvalue[256];
700         int cm_ref = 0;
701         int i;
702         int with_template = 0;
703         struct CtdlMessage *template = NULL;
704         int with_headers = 0;
705         char search_string[1024];
706
707         extract_token(which, cmdbuf, 0, '|', sizeof which);
708         cm_ref = extract_int(cmdbuf, 1);
709         extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
710         with_template = extract_int(cmdbuf, 2);
711         with_headers = extract_int(cmdbuf, 3);
712
713         strcat(which, "   ");
714         if (!strncasecmp(which, "OLD", 3))
715                 mode = MSGS_OLD;
716         else if (!strncasecmp(which, "NEW", 3))
717                 mode = MSGS_NEW;
718         else if (!strncasecmp(which, "FIRST", 5))
719                 mode = MSGS_FIRST;
720         else if (!strncasecmp(which, "LAST", 4))
721                 mode = MSGS_LAST;
722         else if (!strncasecmp(which, "GT", 2))
723                 mode = MSGS_GT;
724         else if (!strncasecmp(which, "SEARCH", 6))
725                 mode = MSGS_SEARCH;
726         else
727                 mode = MSGS_ALL;
728
729         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
730                 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
731                 return;
732         }
733
734         if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
735                 cprintf("%d Full text index is not enabled on this server.\n",
736                         ERROR + CMD_NOT_SUPPORTED);
737                 return;
738         }
739
740         if (with_template) {
741                 unbuffer_output();
742                 cprintf("%d Send template then receive message list\n",
743                         START_CHAT_MODE);
744                 template = (struct CtdlMessage *)
745                         malloc(sizeof(struct CtdlMessage));
746                 memset(template, 0, sizeof(struct CtdlMessage));
747                 template->cm_magic = CTDLMESSAGE_MAGIC;
748                 template->cm_anon_type = MES_NORMAL;
749
750                 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
751                         extract_token(tfield, buf, 0, '|', sizeof tfield);
752                         extract_token(tvalue, buf, 1, '|', sizeof tvalue);
753                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
754                                 if (!strcasecmp(tfield, msgkeys[i])) {
755                                         template->cm_fields[i] =
756                                                 strdup(tvalue);
757                                 }
758                         }
759                 }
760                 buffer_output();
761         }
762         else {
763                 cprintf("%d  \n", LISTING_FOLLOWS);
764         }
765
766         CtdlForEachMessage(mode,
767                         ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
768                         ( (mode == MSGS_SEARCH) ? search_string : NULL ),
769                         NULL,
770                         template,
771                         (with_headers ? headers_listing : simple_listing),
772                         NULL
773         );
774         if (template != NULL) CtdlFreeMessage(template);
775         cprintf("000\n");
776 }
777
778
779
780
781 /* 
782  * help_subst()  -  support routine for help file viewer
783  */
784 void help_subst(char *strbuf, char *source, char *dest)
785 {
786         char workbuf[SIZ];
787         int p;
788
789         while (p = pattern2(strbuf, source), (p >= 0)) {
790                 strcpy(workbuf, &strbuf[p + strlen(source)]);
791                 strcpy(&strbuf[p], dest);
792                 strcat(strbuf, workbuf);
793         }
794 }
795
796
797 void do_help_subst(char *buffer)
798 {
799         char buf2[16];
800
801         help_subst(buffer, "^nodename", config.c_nodename);
802         help_subst(buffer, "^humannode", config.c_humannode);
803         help_subst(buffer, "^fqdn", config.c_fqdn);
804         help_subst(buffer, "^username", CC->user.fullname);
805         snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
806         help_subst(buffer, "^usernum", buf2);
807         help_subst(buffer, "^sysadm", config.c_sysadm);
808         help_subst(buffer, "^variantname", CITADEL);
809         snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
810         help_subst(buffer, "^maxsessions", buf2);
811         help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
812 }
813
814
815
816 /*
817  * memfmout()  -  Citadel text formatter and paginator.
818  *           Although the original purpose of this routine was to format
819  *           text to the reader's screen width, all we're really using it
820  *           for here is to format text out to 80 columns before sending it
821  *           to the client.  The client software may reformat it again.
822  */
823 void memfmout(
824         char *mptr,             /* where are we going to get our text from? */
825         char subst,             /* nonzero if we should do substitutions */
826         char *nl)               /* string to terminate lines with */
827 {
828         int a, b, c;
829         int real = 0;
830         int old = 0;
831         cit_uint8_t ch;
832         char aaa[140];
833         char buffer[SIZ];
834         static int width = 80;
835
836         strcpy(aaa, "");
837         old = 255;
838         strcpy(buffer, "");
839         c = 1;                  /* c is the current pos */
840
841         do {
842                 if (subst) {
843                         while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
844                                 ch = *mptr++;
845                                 buffer[strlen(buffer) + 1] = 0;
846                                 buffer[strlen(buffer)] = ch;
847                         }
848
849                         if (buffer[0] == '^')
850                                 do_help_subst(buffer);
851
852                         buffer[strlen(buffer) + 1] = 0;
853                         a = buffer[0];
854                         strcpy(buffer, &buffer[1]);
855                 } else {
856                         ch = *mptr++;
857                 }
858
859                 old = real;
860                 real = ch;
861
862                 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
863                         ch = 32;
864                 }
865                 if (((old == 13) || (old == 10)) && (isspace(real))) {
866                         cprintf("%s", nl);
867                         c = 1;
868                 }
869                 if (ch > 32) {
870                         if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
871                                 cprintf("%s%s", nl, aaa);
872                                 c = strlen(aaa);
873                                 aaa[0] = 0;
874                         }
875                         b = strlen(aaa);
876                         aaa[b] = ch;
877                         aaa[b + 1] = 0;
878                 }
879                 if (ch == 32) {
880                         if ((strlen(aaa) + c) > (width - 5)) {
881                                 cprintf("%s", nl);
882                                 c = 1;
883                         }
884                         cprintf("%s ", aaa);
885                         ++c;
886                         c = c + strlen(aaa);
887                         strcpy(aaa, "");
888                 }
889                 if ((ch == 13) || (ch == 10)) {
890                         cprintf("%s%s", aaa, nl);
891                         c = 1;
892                         strcpy(aaa, "");
893                 }
894
895         } while (ch > 0);
896
897         cprintf("%s%s", aaa, nl);
898 }
899
900
901
902 /*
903  * Callback function for mime parser that simply lists the part
904  */
905 void list_this_part(char *name, char *filename, char *partnum, char *disp,
906                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
907                     void *cbuserdata)
908 {
909         struct ma_info *ma;
910         
911         ma = (struct ma_info *)cbuserdata;
912         if (ma->is_ma == 0) {
913                 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
914                         name, filename, partnum, disp, cbtype, (long)length);
915         }
916 }
917
918 /* 
919  * Callback function for multipart prefix
920  */
921 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
922                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
923                     void *cbuserdata)
924 {
925         struct ma_info *ma;
926         
927         ma = (struct ma_info *)cbuserdata;
928         if (!strcasecmp(cbtype, "multipart/alternative")) {
929                 ++ma->is_ma;
930         }
931
932         if (ma->is_ma == 0) {
933                 cprintf("pref=%s|%s\n", partnum, cbtype);
934         }
935 }
936
937 /* 
938  * Callback function for multipart sufffix
939  */
940 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
941                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
942                     void *cbuserdata)
943 {
944         struct ma_info *ma;
945         
946         ma = (struct ma_info *)cbuserdata;
947         if (ma->is_ma == 0) {
948                 cprintf("suff=%s|%s\n", partnum, cbtype);
949         }
950         if (!strcasecmp(cbtype, "multipart/alternative")) {
951                 --ma->is_ma;
952         }
953 }
954
955
956 /*
957  * Callback function for mime parser that opens a section for downloading
958  */
959 void mime_download(char *name, char *filename, char *partnum, char *disp,
960                    void *content, char *cbtype, char *cbcharset, size_t length,
961                    char *encoding, void *cbuserdata)
962 {
963
964         /* Silently go away if there's already a download open... */
965         if (CC->download_fp != NULL)
966                 return;
967
968         /* ...or if this is not the desired section */
969         if (strcasecmp(CC->download_desired_section, partnum))
970                 return;
971
972         CC->download_fp = tmpfile();
973         if (CC->download_fp == NULL)
974                 return;
975
976         fwrite(content, length, 1, CC->download_fp);
977         fflush(CC->download_fp);
978         rewind(CC->download_fp);
979
980         OpenCmdResult(filename, cbtype);
981 }
982
983
984
985 /*
986  * Callback function for mime parser that outputs a section all at once
987  */
988 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
989                    void *content, char *cbtype, char *cbcharset, size_t length,
990                    char *encoding, void *cbuserdata)
991 {
992         int *found_it = (int *)cbuserdata;
993
994         /* ...or if this is not the desired section */
995         if (strcasecmp(CC->download_desired_section, partnum))
996                 return;
997
998         *found_it = 1;
999
1000         cprintf("%d %d\n", BINARY_FOLLOWS, (int)length);
1001         client_write(content, length);
1002 }
1003
1004
1005
1006 /*
1007  * Load a message from disk into memory.
1008  * This is used by CtdlOutputMsg() and other fetch functions.
1009  *
1010  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1011  *       using the CtdlMessageFree() function.
1012  */
1013 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1014 {
1015         struct cdbdata *dmsgtext;
1016         struct CtdlMessage *ret = NULL;
1017         char *mptr;
1018         char *upper_bound;
1019         cit_uint8_t ch;
1020         cit_uint8_t field_header;
1021
1022         lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1023
1024         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1025         if (dmsgtext == NULL) {
1026                 return NULL;
1027         }
1028         mptr = dmsgtext->ptr;
1029         upper_bound = mptr + dmsgtext->len;
1030
1031         /* Parse the three bytes that begin EVERY message on disk.
1032          * The first is always 0xFF, the on-disk magic number.
1033          * The second is the anonymous/public type byte.
1034          * The third is the format type byte (vari, fixed, or MIME).
1035          */
1036         ch = *mptr++;
1037         if (ch != 255) {
1038                 lprintf(CTDL_ERR,
1039                         "Message %ld appears to be corrupted.\n",
1040                         msgnum);
1041                 cdb_free(dmsgtext);
1042                 return NULL;
1043         }
1044         ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1045         memset(ret, 0, sizeof(struct CtdlMessage));
1046
1047         ret->cm_magic = CTDLMESSAGE_MAGIC;
1048         ret->cm_anon_type = *mptr++;    /* Anon type byte */
1049         ret->cm_format_type = *mptr++;  /* Format type byte */
1050
1051         /*
1052          * The rest is zero or more arbitrary fields.  Load them in.
1053          * We're done when we encounter either a zero-length field or
1054          * have just processed the 'M' (message text) field.
1055          */
1056         do {
1057                 if (mptr >= upper_bound) {
1058                         break;
1059                 }
1060                 field_header = *mptr++;
1061                 ret->cm_fields[field_header] = strdup(mptr);
1062
1063                 while (*mptr++ != 0);   /* advance to next field */
1064
1065         } while ((mptr < upper_bound) && (field_header != 'M'));
1066
1067         cdb_free(dmsgtext);
1068
1069         /* Always make sure there's something in the msg text field.  If
1070          * it's NULL, the message text is most likely stored separately,
1071          * so go ahead and fetch that.  Failing that, just set a dummy
1072          * body so other code doesn't barf.
1073          */
1074         if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1075                 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1076                 if (dmsgtext != NULL) {
1077                         ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1078                         cdb_free(dmsgtext);
1079                 }
1080         }
1081         if (ret->cm_fields['M'] == NULL) {
1082                 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1083         }
1084
1085         /* Perform "before read" hooks (aborting if any return nonzero) */
1086         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1087                 CtdlFreeMessage(ret);
1088                 return NULL;
1089         }
1090
1091         return (ret);
1092 }
1093
1094
1095 /*
1096  * Returns 1 if the supplied pointer points to a valid Citadel message.
1097  * If the pointer is NULL or the magic number check fails, returns 0.
1098  */
1099 int is_valid_message(struct CtdlMessage *msg) {
1100         if (msg == NULL)
1101                 return 0;
1102         if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1103                 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1104                 return 0;
1105         }
1106         return 1;
1107 }
1108
1109
1110 /*
1111  * 'Destructor' for struct CtdlMessage
1112  */
1113 void CtdlFreeMessage(struct CtdlMessage *msg)
1114 {
1115         int i;
1116
1117         if (is_valid_message(msg) == 0) 
1118         {
1119                 if (msg != NULL) free (msg);
1120                 return;
1121         }
1122
1123         for (i = 0; i < 256; ++i)
1124                 if (msg->cm_fields[i] != NULL) {
1125                         free(msg->cm_fields[i]);
1126                 }
1127
1128         msg->cm_magic = 0;      /* just in case */
1129         free(msg);
1130 }
1131
1132
1133 /*
1134  * Pre callback function for multipart/alternative
1135  *
1136  * NOTE: this differs from the standard behavior for a reason.  Normally when
1137  *       displaying multipart/alternative you want to show the _last_ usable
1138  *       format in the message.  Here we show the _first_ one, because it's
1139  *       usually text/plain.  Since this set of functions is designed for text
1140  *       output to non-MIME-aware clients, this is the desired behavior.
1141  *
1142  */
1143 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1144                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1145                 void *cbuserdata)
1146 {
1147         struct ma_info *ma;
1148         
1149         ma = (struct ma_info *)cbuserdata;
1150         lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);  
1151         if (!strcasecmp(cbtype, "multipart/alternative")) {
1152                 ++ma->is_ma;
1153                 ma->did_print = 0;
1154         }
1155         if (!strcasecmp(cbtype, "message/rfc822")) {
1156                 ++ma->freeze;
1157         }
1158 }
1159
1160 /*
1161  * Post callback function for multipart/alternative
1162  */
1163 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1164                 void *content, char *cbtype, char *cbcharset, size_t length,
1165                 char *encoding, void *cbuserdata)
1166 {
1167         struct ma_info *ma;
1168         
1169         ma = (struct ma_info *)cbuserdata;
1170         lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype); 
1171         if (!strcasecmp(cbtype, "multipart/alternative")) {
1172                 --ma->is_ma;
1173                 ma->did_print = 0;
1174         }
1175         if (!strcasecmp(cbtype, "message/rfc822")) {
1176                 --ma->freeze;
1177         }
1178 }
1179
1180 /*
1181  * Inline callback function for mime parser that wants to display text
1182  */
1183 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1184                 void *content, char *cbtype, char *cbcharset, size_t length,
1185                 char *encoding, void *cbuserdata)
1186 {
1187         char *ptr;
1188         char *wptr;
1189         size_t wlen;
1190         struct ma_info *ma;
1191
1192         ma = (struct ma_info *)cbuserdata;
1193
1194         lprintf(CTDL_DEBUG,
1195                 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1196                 partnum, filename, cbtype, (long)length);
1197
1198         /*
1199          * If we're in the middle of a multipart/alternative scope and
1200          * we've already printed another section, skip this one.
1201          */     
1202         if ( (ma->is_ma) && (ma->did_print) ) {
1203                 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1204                         partnum, cbtype);
1205                 return;
1206         }
1207         ma->did_print = 1;
1208
1209         if ( (!strcasecmp(cbtype, "text/plain")) 
1210            || (IsEmptyStr(cbtype)) ) {
1211                 wptr = content;
1212                 if (length > 0) {
1213                         client_write(wptr, length);
1214                         if (wptr[length-1] != '\n') {
1215                                 cprintf("\n");
1216                         }
1217                 }
1218                 return;
1219         }
1220
1221         if (!strcasecmp(cbtype, "text/html")) {
1222                 ptr = html_to_ascii(content, length, 80, 0);
1223                 wlen = strlen(ptr);
1224                 client_write(ptr, wlen);
1225                 if (ptr[wlen-1] != '\n') {
1226                         cprintf("\n");
1227                 }
1228                 free(ptr);
1229                 return;
1230         }
1231
1232         if (ma->use_fo_hooks) {
1233                 if (PerformFixedOutputHooks(cbtype, content, length)) {
1234                 /* above function returns nonzero if it handled the part */
1235                         return;
1236                 }
1237         }
1238
1239         if (strncasecmp(cbtype, "multipart/", 10)) {
1240                 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1241                         partnum, filename, cbtype, (long)length);
1242                 return;
1243         }
1244 }
1245
1246 /*
1247  * The client is elegant and sophisticated and wants to be choosy about
1248  * MIME content types, so figure out which multipart/alternative part
1249  * we're going to send.
1250  *
1251  * We use a system of weights.  When we find a part that matches one of the
1252  * MIME types we've declared as preferential, we can store it in ma->chosen_part
1253  * and then set ma->chosen_pref to that MIME type's position in our preference
1254  * list.  If we then hit another match, we only replace the first match if
1255  * the preference value is lower.
1256  */
1257 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1258                 void *content, char *cbtype, char *cbcharset, size_t length,
1259                 char *encoding, void *cbuserdata)
1260 {
1261         char buf[1024];
1262         int i;
1263         struct ma_info *ma;
1264         
1265         ma = (struct ma_info *)cbuserdata;
1266
1267         if (ma->is_ma > 0) {
1268                 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1269                         extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1270                         if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1271                                 if (i < ma->chosen_pref) {
1272                                         safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1273                                         ma->chosen_pref = i;
1274                                 }
1275                         }
1276                 }
1277         }
1278 }
1279
1280 /*
1281  * Now that we've chosen our preferred part, output it.
1282  */
1283 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1284                 void *content, char *cbtype, char *cbcharset, size_t length,
1285                 char *encoding, void *cbuserdata)
1286 {
1287         int i;
1288         char buf[128];
1289         int add_newline = 0;
1290         char *text_content;
1291         struct ma_info *ma;
1292         
1293         ma = (struct ma_info *)cbuserdata;
1294
1295         /* This is not the MIME part you're looking for... */
1296         if (strcasecmp(partnum, ma->chosen_part)) return;
1297
1298         /* If the content-type of this part is in our preferred formats
1299          * list, we can simply output it verbatim.
1300          */
1301         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1302                 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1303                 if (!strcasecmp(buf, cbtype)) {
1304                         /* Yeah!  Go!  W00t!! */
1305
1306                         text_content = (char *)content;
1307                         if (text_content[length-1] != '\n') {
1308                                 ++add_newline;
1309                         }
1310                         cprintf("Content-type: %s", cbtype);
1311                         if (!IsEmptyStr(cbcharset)) {
1312                                 cprintf("; charset=%s", cbcharset);
1313                         }
1314                         cprintf("\nContent-length: %d\n",
1315                                 (int)(length + add_newline) );
1316                         if (!IsEmptyStr(encoding)) {
1317                                 cprintf("Content-transfer-encoding: %s\n", encoding);
1318                         }
1319                         else {
1320                                 cprintf("Content-transfer-encoding: 7bit\n");
1321                         }
1322                         cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1323                         cprintf("\n");
1324                         client_write(content, length);
1325                         if (add_newline) cprintf("\n");
1326                         return;
1327                 }
1328         }
1329
1330         /* No translations required or possible: output as text/plain */
1331         cprintf("Content-type: text/plain\n\n");
1332         fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1333                         length, encoding, cbuserdata);
1334 }
1335
1336
1337 struct encapmsg {
1338         char desired_section[64];
1339         char *msg;
1340         size_t msglen;
1341 };
1342
1343
1344 /*
1345  * Callback function for
1346  */
1347 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1348                    void *content, char *cbtype, char *cbcharset, size_t length,
1349                    char *encoding, void *cbuserdata)
1350 {
1351         struct encapmsg *encap;
1352
1353         encap = (struct encapmsg *)cbuserdata;
1354
1355         /* Only proceed if this is the desired section... */
1356         if (!strcasecmp(encap->desired_section, partnum)) {
1357                 encap->msglen = length;
1358                 encap->msg = malloc(length + 2);
1359                 memcpy(encap->msg, content, length);
1360                 return;
1361         }
1362
1363 }
1364
1365
1366
1367
1368 /*
1369  * Get a message off disk.  (returns om_* values found in msgbase.h)
1370  * 
1371  */
1372 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
1373                 int mode,               /* how would you like that message? */
1374                 int headers_only,       /* eschew the message body? */
1375                 int do_proto,           /* do Citadel protocol responses? */
1376                 int crlf,               /* Use CRLF newlines instead of LF? */
1377                 char *section           /* NULL or a message/rfc822 section */
1378 ) {
1379         struct CtdlMessage *TheMessage = NULL;
1380         int retcode = om_no_such_msg;
1381         struct encapmsg encap;
1382
1383         lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n", 
1384                 msg_num, mode,
1385                 (section ? section : "<>")
1386         );
1387
1388         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1389                 if (do_proto) cprintf("%d Not logged in.\n",
1390                         ERROR + NOT_LOGGED_IN);
1391                 return(om_not_logged_in);
1392         }
1393
1394         /* FIXME: check message id against msglist for this room */
1395
1396         /*
1397          * Fetch the message from disk.  If we're in any sort of headers
1398          * only mode, request that we don't even bother loading the body
1399          * into memory.
1400          */
1401         if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1402                 TheMessage = CtdlFetchMessage(msg_num, 0);
1403         }
1404         else {
1405                 TheMessage = CtdlFetchMessage(msg_num, 1);
1406         }
1407
1408         if (TheMessage == NULL) {
1409                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1410                         ERROR + MESSAGE_NOT_FOUND, msg_num);
1411                 return(om_no_such_msg);
1412         }
1413
1414         /* Here is the weird form of this command, to process only an
1415          * encapsulated message/rfc822 section.
1416          */
1417         if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1418                 memset(&encap, 0, sizeof encap);
1419                 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1420                 mime_parser(TheMessage->cm_fields['M'],
1421                         NULL,
1422                         *extract_encapsulated_message,
1423                         NULL, NULL, (void *)&encap, 0
1424                 );
1425                 CtdlFreeMessage(TheMessage);
1426                 TheMessage = NULL;
1427
1428                 if (encap.msg) {
1429                         encap.msg[encap.msglen] = 0;
1430                         TheMessage = convert_internet_message(encap.msg);
1431                         encap.msg = NULL;       /* no free() here, TheMessage owns it now */
1432
1433                         /* Now we let it fall through to the bottom of this
1434                          * function, because TheMessage now contains the
1435                          * encapsulated message instead of the top-level
1436                          * message.  Isn't that neat?
1437                          */
1438
1439                 }
1440                 else {
1441                         if (do_proto) cprintf("%d msg %ld has no part %s\n",
1442                                 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1443                         retcode = om_no_such_msg;
1444                 }
1445
1446         }
1447
1448         /* Ok, output the message now */
1449         retcode = CtdlOutputPreLoadedMsg(
1450                 TheMessage, mode,
1451                 headers_only, do_proto, crlf);
1452         CtdlFreeMessage(TheMessage);
1453
1454         return(retcode);
1455 }
1456
1457
1458 /*
1459  * Get a message off disk.  (returns om_* values found in msgbase.h)
1460  * 
1461  */
1462 int CtdlOutputPreLoadedMsg(
1463                 struct CtdlMessage *TheMessage,
1464                 int mode,               /* how would you like that message? */
1465                 int headers_only,       /* eschew the message body? */
1466                 int do_proto,           /* do Citadel protocol responses? */
1467                 int crlf                /* Use CRLF newlines instead of LF? */
1468 ) {
1469         int i, k;
1470         char buf[SIZ];
1471         cit_uint8_t ch;
1472         char allkeys[30];
1473         char display_name[256];
1474         char *mptr;
1475         char *nl;       /* newline string */
1476         int suppress_f = 0;
1477         int subject_found = 0;
1478         struct ma_info ma;
1479
1480         /* Buffers needed for RFC822 translation.  These are all filled
1481          * using functions that are bounds-checked, and therefore we can
1482          * make them substantially smaller than SIZ.
1483          */
1484         char suser[100];
1485         char luser[100];
1486         char fuser[100];
1487         char snode[100];
1488         char lnode[100];
1489         char mid[100];
1490         char datestamp[100];
1491
1492         lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1493                 ((TheMessage == NULL) ? "NULL" : "not null"),
1494                 mode, headers_only, do_proto, crlf);
1495
1496         strcpy(mid, "unknown");
1497         nl = (crlf ? "\r\n" : "\n");
1498
1499         if (!is_valid_message(TheMessage)) {
1500                 lprintf(CTDL_ERR,
1501                         "ERROR: invalid preloaded message for output\n");
1502                 return(om_no_such_msg);
1503         }
1504
1505         /* Are we downloading a MIME component? */
1506         if (mode == MT_DOWNLOAD) {
1507                 if (TheMessage->cm_format_type != FMT_RFC822) {
1508                         if (do_proto)
1509                                 cprintf("%d This is not a MIME message.\n",
1510                                 ERROR + ILLEGAL_VALUE);
1511                 } else if (CC->download_fp != NULL) {
1512                         if (do_proto) cprintf(
1513                                 "%d You already have a download open.\n",
1514                                 ERROR + RESOURCE_BUSY);
1515                 } else {
1516                         /* Parse the message text component */
1517                         mptr = TheMessage->cm_fields['M'];
1518                         mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1519                         /* If there's no file open by this time, the requested
1520                          * section wasn't found, so print an error
1521                          */
1522                         if (CC->download_fp == NULL) {
1523                                 if (do_proto) cprintf(
1524                                         "%d Section %s not found.\n",
1525                                         ERROR + FILE_NOT_FOUND,
1526                                         CC->download_desired_section);
1527                         }
1528                 }
1529                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1530         }
1531
1532         /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1533          * in a single server operation instead of opening a download file.
1534          */
1535         if (mode == MT_SPEW_SECTION) {
1536                 if (TheMessage->cm_format_type != FMT_RFC822) {
1537                         if (do_proto)
1538                                 cprintf("%d This is not a MIME message.\n",
1539                                 ERROR + ILLEGAL_VALUE);
1540                 } else {
1541                         /* Parse the message text component */
1542                         int found_it = 0;
1543
1544                         mptr = TheMessage->cm_fields['M'];
1545                         mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1546                         /* If section wasn't found, print an error
1547                          */
1548                         if (!found_it) {
1549                                 if (do_proto) cprintf(
1550                                         "%d Section %s not found.\n",
1551                                         ERROR + FILE_NOT_FOUND,
1552                                         CC->download_desired_section);
1553                         }
1554                 }
1555                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1556         }
1557
1558         /* now for the user-mode message reading loops */
1559         if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1560
1561         /* Does the caller want to skip the headers? */
1562         if (headers_only == HEADERS_NONE) goto START_TEXT;
1563
1564         /* Tell the client which format type we're using. */
1565         if ( (mode == MT_CITADEL) && (do_proto) ) {
1566                 cprintf("type=%d\n", TheMessage->cm_format_type);
1567         }
1568
1569         /* nhdr=yes means that we're only displaying headers, no body */
1570         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1571            && (mode == MT_CITADEL)
1572            && (do_proto)
1573            ) {
1574                 cprintf("nhdr=yes\n");
1575         }
1576
1577         /* begin header processing loop for Citadel message format */
1578
1579         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1580
1581                 safestrncpy(display_name, "<unknown>", sizeof display_name);
1582                 if (TheMessage->cm_fields['A']) {
1583                         strcpy(buf, TheMessage->cm_fields['A']);
1584                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1585                                 safestrncpy(display_name, "****", sizeof display_name);
1586                         }
1587                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1588                                 safestrncpy(display_name, "anonymous", sizeof display_name);
1589                         }
1590                         else {
1591                                 safestrncpy(display_name, buf, sizeof display_name);
1592                         }
1593                         if ((is_room_aide())
1594                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1595                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1596                                 size_t tmp = strlen(display_name);
1597                                 snprintf(&display_name[tmp],
1598                                          sizeof display_name - tmp,
1599                                          " [%s]", buf);
1600                         }
1601                 }
1602
1603                 /* Don't show Internet address for users on the
1604                  * local Citadel network.
1605                  */
1606                 suppress_f = 0;
1607                 if (TheMessage->cm_fields['N'] != NULL)
1608                    if (!IsEmptyStr(TheMessage->cm_fields['N']))
1609                       if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1610                         suppress_f = 1;
1611                 }
1612                 
1613                 /* Now spew the header fields in the order we like them. */
1614                 safestrncpy(allkeys, FORDER, sizeof allkeys);
1615                 for (i=0; i<strlen(allkeys); ++i) {
1616                         k = (int) allkeys[i];
1617                         if (k != 'M') {
1618                                 if ( (TheMessage->cm_fields[k] != NULL)
1619                                    && (msgkeys[k] != NULL) ) {
1620                                         if (k == 'A') {
1621                                                 if (do_proto) cprintf("%s=%s\n",
1622                                                         msgkeys[k],
1623                                                         display_name);
1624                                         }
1625                                         else if ((k == 'F') && (suppress_f)) {
1626                                                 /* do nothing */
1627                                         }
1628                                         /* Masquerade display name if needed */
1629                                         else {
1630                                                 if (do_proto) cprintf("%s=%s\n",
1631                                                         msgkeys[k],
1632                                                         TheMessage->cm_fields[k]
1633                                         );
1634                                         }
1635                                 }
1636                         }
1637                 }
1638
1639         }
1640
1641         /* begin header processing loop for RFC822 transfer format */
1642
1643         strcpy(suser, "");
1644         strcpy(luser, "");
1645         strcpy(fuser, "");
1646         strcpy(snode, NODENAME);
1647         strcpy(lnode, HUMANNODE);
1648         if (mode == MT_RFC822) {
1649                 for (i = 0; i < 256; ++i) {
1650                         if (TheMessage->cm_fields[i]) {
1651                                 mptr = TheMessage->cm_fields[i];
1652
1653                                 if (i == 'A') {
1654                                         safestrncpy(luser, mptr, sizeof luser);
1655                                         safestrncpy(suser, mptr, sizeof suser);
1656                                 }
1657                                 else if (i == 'Y') {
1658                                         cprintf("CC: %s%s", mptr, nl);
1659                                 }
1660                                 else if (i == 'P') {
1661                                         cprintf("Return-Path: %s%s", mptr, nl);
1662                                 }
1663                                 else if (i == 'V') {
1664                                         cprintf("Envelope-To: %s%s", mptr, nl);
1665                                 }
1666                                 else if (i == 'U') {
1667                                         cprintf("Subject: %s%s", mptr, nl);
1668                                         subject_found = 1;
1669                                 }
1670                                 else if (i == 'I')
1671                                         safestrncpy(mid, mptr, sizeof mid);
1672                                 else if (i == 'H')
1673                                         safestrncpy(lnode, mptr, sizeof lnode);
1674                                 else if (i == 'F')
1675                                         safestrncpy(fuser, mptr, sizeof fuser);
1676                                 /* else if (i == 'O')
1677                                         cprintf("X-Citadel-Room: %s%s",
1678                                                 mptr, nl); */
1679                                 else if (i == 'N')
1680                                         safestrncpy(snode, mptr, sizeof snode);
1681                                 else if (i == 'R')
1682                                 {
1683                                         if (haschar(mptr, '@') == 0)
1684                                         {
1685                                                 cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl);
1686                                         }
1687                                         else
1688                                         {
1689                                                 cprintf("To: %s%s", mptr, nl);
1690                                         }
1691                                 }
1692                                 else if (i == 'T') {
1693                                         datestring(datestamp, sizeof datestamp,
1694                                                 atol(mptr), DATESTRING_RFC822);
1695                                         cprintf("Date: %s%s", datestamp, nl);
1696                                 }
1697                         }
1698                 }
1699                 if (subject_found == 0) {
1700                         cprintf("Subject: (no subject)%s", nl);
1701                 }
1702         }
1703
1704         for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1705                 suser[i] = tolower(suser[i]);
1706                 if (!isalnum(suser[i])) suser[i]='_';
1707         }
1708
1709         if (mode == MT_RFC822) {
1710                 if (!strcasecmp(snode, NODENAME)) {
1711                         safestrncpy(snode, FQDN, sizeof snode);
1712                 }
1713
1714                 /* Construct a fun message id */
1715                 cprintf("Message-ID: <%s", mid);
1716                 if (strchr(mid, '@')==NULL) {
1717                         cprintf("@%s", snode);
1718                 }
1719                 cprintf(">%s", nl);
1720
1721                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1722                         cprintf("From: \"----\" <x@x.org>%s", nl);
1723                 }
1724                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1725                         cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1726                 }
1727                 else if (!IsEmptyStr(fuser)) {
1728                         cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1729                 }
1730                 else {
1731                         cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1732                 }
1733
1734                 cprintf("Organization: %s%s", lnode, nl);
1735
1736                 /* Blank line signifying RFC822 end-of-headers */
1737                 if (TheMessage->cm_format_type != FMT_RFC822) {
1738                         cprintf("%s", nl);
1739                 }
1740         }
1741
1742         /* end header processing loop ... at this point, we're in the text */
1743 START_TEXT:
1744         if (headers_only == HEADERS_FAST) goto DONE;
1745         mptr = TheMessage->cm_fields['M'];
1746
1747         /* Tell the client about the MIME parts in this message */
1748         if (TheMessage->cm_format_type == FMT_RFC822) {
1749                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1750                         memset(&ma, 0, sizeof(struct ma_info));
1751                         mime_parser(mptr, NULL,
1752                                 (do_proto ? *list_this_part : NULL),
1753                                 (do_proto ? *list_this_pref : NULL),
1754                                 (do_proto ? *list_this_suff : NULL),
1755                                 (void *)&ma, 0);
1756                 }
1757                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1758                         char *start_of_text = NULL;
1759                         start_of_text = strstr(mptr, "\n\r\n");
1760                         if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1761                         if (start_of_text == NULL) start_of_text = mptr;
1762                         ++start_of_text;
1763                         start_of_text = strstr(start_of_text, "\n");
1764                         ++start_of_text;
1765
1766                         char outbuf[1024];
1767                         int outlen = 0;
1768                         int nllen = strlen(nl);
1769                         while (ch=*mptr, ch!=0) {
1770                                 if (ch==13) {
1771                                         /* do nothing */
1772                                 }
1773                                 else {
1774                                         if (
1775                                                 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1776                                            ||   ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1777                                            ||   ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1778                                         ) {
1779                                                 if (ch == 10) {
1780                                                         sprintf(&outbuf[outlen], "%s", nl);
1781                                                         outlen += nllen;
1782                                                 }
1783                                                 else {
1784                                                         outbuf[outlen++] = ch;
1785                                                 }
1786                                         }
1787                                 }
1788                                 ++mptr;
1789                                 if (outlen > 1000) {
1790                                         client_write(outbuf, outlen);
1791                                         outlen = 0;
1792                                 }
1793                         }
1794                         if (outlen > 0) {
1795                                 client_write(outbuf, outlen);
1796                                 outlen = 0;
1797                         }
1798
1799                         goto DONE;
1800                 }
1801         }
1802
1803         if (headers_only == HEADERS_ONLY) {
1804                 goto DONE;
1805         }
1806
1807         /* signify start of msg text */
1808         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1809                 if (do_proto) cprintf("text\n");
1810         }
1811
1812         /* If the format type on disk is 1 (fixed-format), then we want
1813          * everything to be output completely literally ... regardless of
1814          * what message transfer format is in use.
1815          */
1816         if (TheMessage->cm_format_type == FMT_FIXED) {
1817                 if (mode == MT_MIME) {
1818                         cprintf("Content-type: text/plain\n\n");
1819                 }
1820                 strcpy(buf, "");
1821                 while (ch = *mptr++, ch > 0) {
1822                         if (ch == 13)
1823                                 ch = 10;
1824                         if ((ch == 10) || (strlen(buf) > 250)) {
1825                                 cprintf("%s%s", buf, nl);
1826                                 strcpy(buf, "");
1827                         } else {
1828                                 buf[strlen(buf) + 1] = 0;
1829                                 buf[strlen(buf)] = ch;
1830                         }
1831                 }
1832                 if (!IsEmptyStr(buf))
1833                         cprintf("%s%s", buf, nl);
1834         }
1835
1836         /* If the message on disk is format 0 (Citadel vari-format), we
1837          * output using the formatter at 80 columns.  This is the final output
1838          * form if the transfer format is RFC822, but if the transfer format
1839          * is Citadel proprietary, it'll still work, because the indentation
1840          * for new paragraphs is correct and the client will reformat the
1841          * message to the reader's screen width.
1842          */
1843         if (TheMessage->cm_format_type == FMT_CITADEL) {
1844                 if (mode == MT_MIME) {
1845                         cprintf("Content-type: text/x-citadel-variformat\n\n");
1846                 }
1847                 memfmout(mptr, 0, nl);
1848         }
1849
1850         /* If the message on disk is format 4 (MIME), we've gotta hand it
1851          * off to the MIME parser.  The client has already been told that
1852          * this message is format 1 (fixed format), so the callback function
1853          * we use will display those parts as-is.
1854          */
1855         if (TheMessage->cm_format_type == FMT_RFC822) {
1856                 memset(&ma, 0, sizeof(struct ma_info));
1857
1858                 if (mode == MT_MIME) {
1859                         ma.use_fo_hooks = 0;
1860                         strcpy(ma.chosen_part, "1");
1861                         ma.chosen_pref = 9999;
1862                         mime_parser(mptr, NULL,
1863                                 *choose_preferred, *fixed_output_pre,
1864                                 *fixed_output_post, (void *)&ma, 0);
1865                         mime_parser(mptr, NULL,
1866                                 *output_preferred, NULL, NULL, (void *)&ma, 0);
1867                 }
1868                 else {
1869                         ma.use_fo_hooks = 1;
1870                         mime_parser(mptr, NULL,
1871                                 *fixed_output, *fixed_output_pre,
1872                                 *fixed_output_post, (void *)&ma, 0);
1873                 }
1874
1875         }
1876
1877 DONE:   /* now we're done */
1878         if (do_proto) cprintf("000\n");
1879         return(om_ok);
1880 }
1881
1882
1883
1884 /*
1885  * display a message (mode 0 - Citadel proprietary)
1886  */
1887 void cmd_msg0(char *cmdbuf)
1888 {
1889         long msgid;
1890         int headers_only = HEADERS_ALL;
1891
1892         msgid = extract_long(cmdbuf, 0);
1893         headers_only = extract_int(cmdbuf, 1);
1894
1895         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1896         return;
1897 }
1898
1899
1900 /*
1901  * display a message (mode 2 - RFC822)
1902  */
1903 void cmd_msg2(char *cmdbuf)
1904 {
1905         long msgid;
1906         int headers_only = HEADERS_ALL;
1907
1908         msgid = extract_long(cmdbuf, 0);
1909         headers_only = extract_int(cmdbuf, 1);
1910
1911         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1912 }
1913
1914
1915
1916 /* 
1917  * display a message (mode 3 - IGnet raw format - internal programs only)
1918  */
1919 void cmd_msg3(char *cmdbuf)
1920 {
1921         long msgnum;
1922         struct CtdlMessage *msg = NULL;
1923         struct ser_ret smr;
1924
1925         if (CC->internal_pgm == 0) {
1926                 cprintf("%d This command is for internal programs only.\n",
1927                         ERROR + HIGHER_ACCESS_REQUIRED);
1928                 return;
1929         }
1930
1931         msgnum = extract_long(cmdbuf, 0);
1932         msg = CtdlFetchMessage(msgnum, 1);
1933         if (msg == NULL) {
1934                 cprintf("%d Message %ld not found.\n", 
1935                         ERROR + MESSAGE_NOT_FOUND, msgnum);
1936                 return;
1937         }
1938
1939         serialize_message(&smr, msg);
1940         CtdlFreeMessage(msg);
1941
1942         if (smr.len == 0) {
1943                 cprintf("%d Unable to serialize message\n",
1944                         ERROR + INTERNAL_ERROR);
1945                 return;
1946         }
1947
1948         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1949         client_write((char *)smr.ser, (int)smr.len);
1950         free(smr.ser);
1951 }
1952
1953
1954
1955 /* 
1956  * Display a message using MIME content types
1957  */
1958 void cmd_msg4(char *cmdbuf)
1959 {
1960         long msgid;
1961         char section[64];
1962
1963         msgid = extract_long(cmdbuf, 0);
1964         extract_token(section, cmdbuf, 1, '|', sizeof section);
1965         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1966 }
1967
1968
1969
1970 /* 
1971  * Client tells us its preferred message format(s)
1972  */
1973 void cmd_msgp(char *cmdbuf)
1974 {
1975         safestrncpy(CC->preferred_formats, cmdbuf,
1976                         sizeof(CC->preferred_formats));
1977         cprintf("%d ok\n", CIT_OK);
1978 }
1979
1980
1981 /*
1982  * Open a component of a MIME message as a download file 
1983  */
1984 void cmd_opna(char *cmdbuf)
1985 {
1986         long msgid;
1987         char desired_section[128];
1988
1989         msgid = extract_long(cmdbuf, 0);
1990         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1991         safestrncpy(CC->download_desired_section, desired_section,
1992                 sizeof CC->download_desired_section);
1993         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1994 }                       
1995
1996
1997 /*
1998  * Open a component of a MIME message and transmit it all at once
1999  */
2000 void cmd_dlat(char *cmdbuf)
2001 {
2002         long msgid;
2003         char desired_section[128];
2004
2005         msgid = extract_long(cmdbuf, 0);
2006         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2007         safestrncpy(CC->download_desired_section, desired_section,
2008                 sizeof CC->download_desired_section);
2009         CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL);
2010 }                       
2011
2012
2013 /*
2014  * Save one or more message pointers into a specified room
2015  * (Returns 0 for success, nonzero for failure)
2016  * roomname may be NULL to use the current room
2017  *
2018  * Note that the 'supplied_msg' field may be set to NULL, in which case
2019  * the message will be fetched from disk, by number, if we need to perform
2020  * replication checks.  This adds an additional database read, so if the
2021  * caller already has the message in memory then it should be supplied.  (Obviously
2022  * this mode of operation only works if we're saving a single message.)
2023  */
2024 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2025                                 int do_repl_check, struct CtdlMessage *supplied_msg)
2026 {
2027         int i, j, unique;
2028         char hold_rm[ROOMNAMELEN];
2029         struct cdbdata *cdbfr;
2030         int num_msgs;
2031         long *msglist;
2032         long highest_msg = 0L;
2033
2034         long msgid = 0;
2035         struct CtdlMessage *msg = NULL;
2036
2037         long *msgs_to_be_merged = NULL;
2038         int num_msgs_to_be_merged = 0;
2039
2040         lprintf(CTDL_DEBUG,
2041                 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2042                 roomname, num_newmsgs, do_repl_check);
2043
2044         strcpy(hold_rm, CC->room.QRname);
2045
2046         /* Sanity checks */
2047         if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2048         if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2049         if (num_newmsgs > 1) supplied_msg = NULL;
2050
2051         /* Now the regular stuff */
2052         if (lgetroom(&CC->room,
2053            ((roomname != NULL) ? roomname : CC->room.QRname) )
2054            != 0) {
2055                 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
2056                 return(ERROR + ROOM_NOT_FOUND);
2057         }
2058
2059
2060         msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2061         num_msgs_to_be_merged = 0;
2062
2063
2064         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2065         if (cdbfr == NULL) {
2066                 msglist = NULL;
2067                 num_msgs = 0;
2068         } else {
2069                 msglist = (long *) cdbfr->ptr;
2070                 cdbfr->ptr = NULL;      /* CtdlSaveMsgPointerInRoom() now owns this memory */
2071                 num_msgs = cdbfr->len / sizeof(long);
2072                 cdb_free(cdbfr);
2073         }
2074
2075
2076         /* Create a list of msgid's which were supplied by the caller, but do
2077          * not already exist in the target room.  It is absolutely taboo to
2078          * have more than one reference to the same message in a room.
2079          */
2080         for (i=0; i<num_newmsgs; ++i) {
2081                 unique = 1;
2082                 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2083                         if (msglist[j] == newmsgidlist[i]) {
2084                                 unique = 0;
2085                         }
2086                 }
2087                 if (unique) {
2088                         msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2089                 }
2090         }
2091
2092         lprintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2093
2094         /*
2095          * Now merge the new messages
2096          */
2097         msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2098         if (msglist == NULL) {
2099                 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2100         }
2101         memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2102         num_msgs += num_msgs_to_be_merged;
2103
2104         /* Sort the message list, so all the msgid's are in order */
2105         num_msgs = sort_msglist(msglist, num_msgs);
2106
2107         /* Determine the highest message number */
2108         highest_msg = msglist[num_msgs - 1];
2109
2110         /* Write it back to disk. */
2111         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2112                   msglist, (int)(num_msgs * sizeof(long)));
2113
2114         /* Free up the memory we used. */
2115         free(msglist);
2116
2117         /* Update the highest-message pointer and unlock the room. */
2118         CC->room.QRhighest = highest_msg;
2119         lputroom(&CC->room);
2120
2121         /* Perform replication checks if necessary */
2122         if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2123                 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2124
2125                 for (i=0; i<num_msgs_to_be_merged; ++i) {
2126                         msgid = msgs_to_be_merged[i];
2127         
2128                         if (supplied_msg != NULL) {
2129                                 msg = supplied_msg;
2130                         }
2131                         else {
2132                                 msg = CtdlFetchMessage(msgid, 0);
2133                         }
2134         
2135                         if (msg != NULL) {
2136                                 ReplicationChecks(msg);
2137                 
2138                                 /* If the message has an Exclusive ID, index that... */
2139                                 if (msg->cm_fields['E'] != NULL) {
2140                                         index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2141                                 }
2142
2143                                 /* Free up the memory we may have allocated */
2144                                 if (msg != supplied_msg) {
2145                                         CtdlFreeMessage(msg);
2146                                 }
2147                         }
2148         
2149                 }
2150         }
2151
2152         else {
2153                 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2154         }
2155
2156         /* Submit this room for processing by hooks */
2157         PerformRoomHooks(&CC->room);
2158
2159         /* Go back to the room we were in before we wandered here... */
2160         getroom(&CC->room, hold_rm);
2161
2162         /* Bump the reference count for all messages which were merged */
2163         for (i=0; i<num_msgs_to_be_merged; ++i) {
2164                 AdjRefCount(msgs_to_be_merged[i], +1);
2165         }
2166
2167         /* Free up memory... */
2168         if (msgs_to_be_merged != NULL) {
2169                 free(msgs_to_be_merged);
2170         }
2171
2172         /* Return success. */
2173         return (0);
2174 }
2175
2176
2177 /*
2178  * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2179  * a single message.
2180  */
2181 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2182                         int do_repl_check, struct CtdlMessage *supplied_msg)
2183 {
2184         return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2185 }
2186
2187
2188
2189
2190 /*
2191  * Message base operation to save a new message to the message store
2192  * (returns new message number)
2193  *
2194  * This is the back end for CtdlSubmitMsg() and should not be directly
2195  * called by server-side modules.
2196  *
2197  */
2198 long send_message(struct CtdlMessage *msg) {
2199         long newmsgid;
2200         long retval;
2201         char msgidbuf[256];
2202         struct ser_ret smr;
2203         int is_bigmsg = 0;
2204         char *holdM = NULL;
2205
2206         /* Get a new message number */
2207         newmsgid = get_new_message_number();
2208         snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2209
2210         /* Generate an ID if we don't have one already */
2211         if (msg->cm_fields['I']==NULL) {
2212                 msg->cm_fields['I'] = strdup(msgidbuf);
2213         }
2214
2215         /* If the message is big, set its body aside for storage elsewhere */
2216         if (msg->cm_fields['M'] != NULL) {
2217                 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2218                         is_bigmsg = 1;
2219                         holdM = msg->cm_fields['M'];
2220                         msg->cm_fields['M'] = NULL;
2221                 }
2222         }
2223
2224         /* Serialize our data structure for storage in the database */  
2225         serialize_message(&smr, msg);
2226
2227         if (is_bigmsg) {
2228                 msg->cm_fields['M'] = holdM;
2229         }
2230
2231         if (smr.len == 0) {
2232                 cprintf("%d Unable to serialize message\n",
2233                         ERROR + INTERNAL_ERROR);
2234                 return (-1L);
2235         }
2236
2237         /* Write our little bundle of joy into the message base */
2238         if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2239                       smr.ser, smr.len) < 0) {
2240                 lprintf(CTDL_ERR, "Can't store message\n");
2241                 retval = 0L;
2242         } else {
2243                 if (is_bigmsg) {
2244                         cdb_store(CDB_BIGMSGS,
2245                                 &newmsgid,
2246                                 (int)sizeof(long),
2247                                 holdM,
2248                                 (strlen(holdM) + 1)
2249                         );
2250                 }
2251                 retval = newmsgid;
2252         }
2253
2254         /* Free the memory we used for the serialized message */
2255         free(smr.ser);
2256
2257         /* Return the *local* message ID to the caller
2258          * (even if we're storing an incoming network message)
2259          */
2260         return(retval);
2261 }
2262
2263
2264
2265 /*
2266  * Serialize a struct CtdlMessage into the format used on disk and network.
2267  * 
2268  * This function loads up a "struct ser_ret" (defined in server.h) which
2269  * contains the length of the serialized message and a pointer to the
2270  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
2271  */
2272 void serialize_message(struct ser_ret *ret,             /* return values */
2273                         struct CtdlMessage *msg)        /* unserialized msg */
2274 {
2275         size_t wlen, fieldlen;
2276         int i;
2277         static char *forder = FORDER;
2278
2279         /*
2280          * Check for valid message format
2281          */
2282         if (is_valid_message(msg) == 0) {
2283                 lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2284                 ret->len = 0;
2285                 ret->ser = NULL;
2286                 return;
2287         }
2288
2289         ret->len = 3;
2290         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2291                 ret->len = ret->len +
2292                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
2293
2294         ret->ser = malloc(ret->len);
2295         if (ret->ser == NULL) {
2296                 lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2297                         (long)ret->len, strerror(errno));
2298                 ret->len = 0;
2299                 ret->ser = NULL;
2300                 return;
2301         }
2302
2303         ret->ser[0] = 0xFF;
2304         ret->ser[1] = msg->cm_anon_type;
2305         ret->ser[2] = msg->cm_format_type;
2306         wlen = 3;
2307
2308         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2309                 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2310                 ret->ser[wlen++] = (char)forder[i];
2311                 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2312                 wlen = wlen + fieldlen + 1;
2313         }
2314         if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2315                 (long)ret->len, (long)wlen);
2316
2317         return;
2318 }
2319
2320
2321
2322 /*
2323  * Check to see if any messages already exist in the current room which
2324  * carry the same Exclusive ID as this one.  If any are found, delete them.
2325  */
2326 void ReplicationChecks(struct CtdlMessage *msg) {
2327         long old_msgnum = (-1L);
2328
2329         if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2330
2331         lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2332                 CC->room.QRname);
2333
2334         /* No exclusive id?  Don't do anything. */
2335         if (msg == NULL) return;
2336         if (msg->cm_fields['E'] == NULL) return;
2337         if (IsEmptyStr(msg->cm_fields['E'])) return;
2338         /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2339                 msg->cm_fields['E'], CC->room.QRname);*/
2340
2341         old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2342         if (old_msgnum > 0L) {
2343                 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2344                 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2345         }
2346 }
2347
2348
2349
2350 /*
2351  * Save a message to disk and submit it into the delivery system.
2352  */
2353 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
2354                 struct recptypes *recps,        /* recipients (if mail) */
2355                 char *force                     /* force a particular room? */
2356 ) {
2357         char submit_filename[128];
2358         char generated_timestamp[32];
2359         char hold_rm[ROOMNAMELEN];
2360         char actual_rm[ROOMNAMELEN];
2361         char force_room[ROOMNAMELEN];
2362         char content_type[SIZ];                 /* We have to learn this */
2363         char recipient[SIZ];
2364         long newmsgid;
2365         char *mptr = NULL;
2366         struct ctdluser userbuf;
2367         int a, i;
2368         struct MetaData smi;
2369         FILE *network_fp = NULL;
2370         static int seqnum = 1;
2371         struct CtdlMessage *imsg = NULL;
2372         char *instr = NULL;
2373         size_t instr_alloc = 0;
2374         struct ser_ret smr;
2375         char *hold_R, *hold_D;
2376         char *collected_addresses = NULL;
2377         struct addresses_to_be_filed *aptr = NULL;
2378         char *saved_rfc822_version = NULL;
2379         int qualified_for_journaling = 0;
2380
2381         lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2382         if (is_valid_message(msg) == 0) return(-1);     /* self check */
2383
2384         /* If this message has no timestamp, we take the liberty of
2385          * giving it one, right now.
2386          */
2387         if (msg->cm_fields['T'] == NULL) {
2388                 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2389                 msg->cm_fields['T'] = strdup(generated_timestamp);
2390         }
2391
2392         /* If this message has no path, we generate one.
2393          */
2394         if (msg->cm_fields['P'] == NULL) {
2395                 if (msg->cm_fields['A'] != NULL) {
2396                         msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2397                         for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2398                                 if (isspace(msg->cm_fields['P'][a])) {
2399                                         msg->cm_fields['P'][a] = ' ';
2400                                 }
2401                         }
2402                 }
2403                 else {
2404                         msg->cm_fields['P'] = strdup("unknown");
2405                 }
2406         }
2407
2408         if (force == NULL) {
2409                 strcpy(force_room, "");
2410         }
2411         else {
2412                 strcpy(force_room, force);
2413         }
2414
2415         /* Learn about what's inside, because it's what's inside that counts */
2416         if (msg->cm_fields['M'] == NULL) {
2417                 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2418                 return(-2);
2419         }
2420
2421         switch (msg->cm_format_type) {
2422         case 0:
2423                 strcpy(content_type, "text/x-citadel-variformat");
2424                 break;
2425         case 1:
2426                 strcpy(content_type, "text/plain");
2427                 break;
2428         case 4:
2429                 strcpy(content_type, "text/plain");
2430                 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2431                 if (mptr != NULL) {
2432                         safestrncpy(content_type, &mptr[13], sizeof content_type);
2433                         striplt(content_type);
2434                         for (a = 0; a < strlen(content_type); ++a) {
2435                                 if ((content_type[a] == ';')
2436                                     || (content_type[a] == ' ')
2437                                     || (content_type[a] == 13)
2438                                     || (content_type[a] == 10)) {
2439                                         content_type[a] = 0;
2440                                 }
2441                         }
2442                 }
2443         }
2444
2445         /* Goto the correct room */
2446         lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2447         strcpy(hold_rm, CC->room.QRname);
2448         strcpy(actual_rm, CC->room.QRname);
2449         if (recps != NULL) {
2450                 strcpy(actual_rm, SENTITEMS);
2451         }
2452
2453         /* If the user is a twit, move to the twit room for posting */
2454         if (TWITDETECT) {
2455                 if (CC->user.axlevel == 2) {
2456                         strcpy(hold_rm, actual_rm);
2457                         strcpy(actual_rm, config.c_twitroom);
2458                         lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2459                 }
2460         }
2461
2462         /* ...or if this message is destined for Aide> then go there. */
2463         if (!IsEmptyStr(force_room)) {
2464                 strcpy(actual_rm, force_room);
2465         }
2466
2467         lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2468         if (strcasecmp(actual_rm, CC->room.QRname)) {
2469                 /* getroom(&CC->room, actual_rm); */
2470                 usergoto(actual_rm, 0, 1, NULL, NULL);
2471         }
2472
2473         /*
2474          * If this message has no O (room) field, generate one.
2475          */
2476         if (msg->cm_fields['O'] == NULL) {
2477                 msg->cm_fields['O'] = strdup(CC->room.QRname);
2478         }
2479
2480         /* Perform "before save" hooks (aborting if any return nonzero) */
2481         lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2482         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2483
2484         /*
2485          * If this message has an Exclusive ID, and the room is replication
2486          * checking enabled, then do replication checks.
2487          */
2488         if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2489                 ReplicationChecks(msg);
2490         }
2491
2492         /* Save it to disk */
2493         lprintf(CTDL_DEBUG, "Saving to disk\n");
2494         newmsgid = send_message(msg);
2495         if (newmsgid <= 0L) return(-5);
2496
2497         /* Write a supplemental message info record.  This doesn't have to
2498          * be a critical section because nobody else knows about this message
2499          * yet.
2500          */
2501         lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2502         memset(&smi, 0, sizeof(struct MetaData));
2503         smi.meta_msgnum = newmsgid;
2504         smi.meta_refcount = 0;
2505         safestrncpy(smi.meta_content_type, content_type,
2506                         sizeof smi.meta_content_type);
2507
2508         /*
2509          * Measure how big this message will be when rendered as RFC822.
2510          * We do this for two reasons:
2511          * 1. We need the RFC822 length for the new metadata record, so the
2512          *    POP and IMAP services don't have to calculate message lengths
2513          *    while the user is waiting (multiplied by potentially hundreds
2514          *    or thousands of messages).
2515          * 2. If journaling is enabled, we will need an RFC822 version of the
2516          *    message to attach to the journalized copy.
2517          */
2518         if (CC->redirect_buffer != NULL) {
2519                 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2520                 abort();
2521         }
2522         CC->redirect_buffer = malloc(SIZ);
2523         CC->redirect_len = 0;
2524         CC->redirect_alloc = SIZ;
2525         CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2526         smi.meta_rfc822_length = CC->redirect_len;
2527         saved_rfc822_version = CC->redirect_buffer;
2528         CC->redirect_buffer = NULL;
2529         CC->redirect_len = 0;
2530         CC->redirect_alloc = 0;
2531
2532         PutMetaData(&smi);
2533
2534         /* Now figure out where to store the pointers */
2535         lprintf(CTDL_DEBUG, "Storing pointers\n");
2536
2537         /* If this is being done by the networker delivering a private
2538          * message, we want to BYPASS saving the sender's copy (because there
2539          * is no local sender; it would otherwise go to the Trashcan).
2540          */
2541         if ((!CC->internal_pgm) || (recps == NULL)) {
2542                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2543                         lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2544                         CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2545                 }
2546         }
2547
2548         /* For internet mail, drop a copy in the outbound queue room */
2549         if (recps != NULL)
2550          if (recps->num_internet > 0) {
2551                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2552         }
2553
2554         /* If other rooms are specified, drop them there too. */
2555         if (recps != NULL)
2556          if (recps->num_room > 0)
2557           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2558                 extract_token(recipient, recps->recp_room, i,
2559                                         '|', sizeof recipient);
2560                 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2561                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2562         }
2563
2564         /* Bump this user's messages posted counter. */
2565         lprintf(CTDL_DEBUG, "Updating user\n");
2566         lgetuser(&CC->user, CC->curr_user);
2567         CC->user.posted = CC->user.posted + 1;
2568         lputuser(&CC->user);
2569
2570         /* If this is private, local mail, make a copy in the
2571          * recipient's mailbox and bump the reference count.
2572          */
2573         if (recps != NULL)
2574          if (recps->num_local > 0)
2575           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2576                 extract_token(recipient, recps->recp_local, i,
2577                                         '|', sizeof recipient);
2578                 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2579                         recipient);
2580                 if (getuser(&userbuf, recipient) == 0) {
2581                         // Add a flag so the Funambol module knows its mail
2582                         msg->cm_fields['W'] = strdup(recipient);
2583                         MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2584                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2585                         BumpNewMailCounter(userbuf.usernum);
2586                         if (!IsEmptyStr(config.c_funambol_host)) {
2587                         /* Generate a instruction message for the Funambol notification
2588                          * server, in the same style as the SMTP queue
2589                          */
2590                            instr_alloc = 1024;
2591                            instr = malloc(instr_alloc);
2592                            snprintf(instr, instr_alloc,
2593                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2594                         "bounceto|%s@%s\n",
2595                         SPOOLMIME, newmsgid, (long)time(NULL),
2596                         msg->cm_fields['A'], msg->cm_fields['N']
2597                         );
2598
2599                            imsg = malloc(sizeof(struct CtdlMessage));
2600                            memset(imsg, 0, sizeof(struct CtdlMessage));
2601                            imsg->cm_magic = CTDLMESSAGE_MAGIC;
2602                            imsg->cm_anon_type = MES_NORMAL;
2603                            imsg->cm_format_type = FMT_RFC822;
2604                            imsg->cm_fields['A'] = strdup("Citadel");
2605                            imsg->cm_fields['J'] = strdup("do not journal");
2606                            imsg->cm_fields['M'] = instr;        /* imsg owns this memory now */
2607                            imsg->cm_fields['W'] = strdup(recipient);
2608                            CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM);
2609                            CtdlFreeMessage(imsg);
2610                         }
2611                 }
2612                 else {
2613                         lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2614                         CtdlSaveMsgPointerInRoom(config.c_aideroom,
2615                                 newmsgid, 0, msg);
2616                 }
2617         }
2618
2619         /* Perform "after save" hooks */
2620         lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2621         PerformMessageHooks(msg, EVT_AFTERSAVE);
2622
2623         /* For IGnet mail, we have to save a new copy into the spooler for
2624          * each recipient, with the R and D fields set to the recipient and
2625          * destination-node.  This has two ugly side effects: all other
2626          * recipients end up being unlisted in this recipient's copy of the
2627          * message, and it has to deliver multiple messages to the same
2628          * node.  We'll revisit this again in a year or so when everyone has
2629          * a network spool receiver that can handle the new style messages.
2630          */
2631         if (recps != NULL)
2632          if (recps->num_ignet > 0)
2633           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2634                 extract_token(recipient, recps->recp_ignet, i,
2635                                 '|', sizeof recipient);
2636
2637                 hold_R = msg->cm_fields['R'];
2638                 hold_D = msg->cm_fields['D'];
2639                 msg->cm_fields['R'] = malloc(SIZ);
2640                 msg->cm_fields['D'] = malloc(128);
2641                 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2642                 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2643                 
2644                 serialize_message(&smr, msg);
2645                 if (smr.len > 0) {
2646                         snprintf(submit_filename, sizeof submit_filename,
2647                                          "%s/netmail.%04lx.%04x.%04x",
2648                                          ctdl_netin_dir,
2649                                          (long) getpid(), CC->cs_pid, ++seqnum);
2650                         network_fp = fopen(submit_filename, "wb+");
2651                         if (network_fp != NULL) {
2652                                 fwrite(smr.ser, smr.len, 1, network_fp);
2653                                 fclose(network_fp);
2654                         }
2655                         free(smr.ser);
2656                 }
2657
2658                 free(msg->cm_fields['R']);
2659                 free(msg->cm_fields['D']);
2660                 msg->cm_fields['R'] = hold_R;
2661                 msg->cm_fields['D'] = hold_D;
2662         }
2663
2664         /* Go back to the room we started from */
2665         lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2666         if (strcasecmp(hold_rm, CC->room.QRname))
2667                 usergoto(hold_rm, 0, 1, NULL, NULL);
2668
2669         /* For internet mail, generate delivery instructions.
2670          * Yes, this is recursive.  Deal with it.  Infinite recursion does
2671          * not happen because the delivery instructions message does not
2672          * contain a recipient.
2673          */
2674         if (recps != NULL)
2675          if (recps->num_internet > 0) {
2676                 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2677                 instr_alloc = 1024;
2678                 instr = malloc(instr_alloc);
2679                 snprintf(instr, instr_alloc,
2680                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2681                         "bounceto|%s@%s\n",
2682                         SPOOLMIME, newmsgid, (long)time(NULL),
2683                         msg->cm_fields['A'], msg->cm_fields['N']
2684                 );
2685
2686                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2687                         size_t tmp = strlen(instr);
2688                         extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2689                         if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2690                                 instr_alloc = instr_alloc * 2;
2691                                 instr = realloc(instr, instr_alloc);
2692                         }
2693                         snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2694                 }
2695
2696                 imsg = malloc(sizeof(struct CtdlMessage));
2697                 memset(imsg, 0, sizeof(struct CtdlMessage));
2698                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2699                 imsg->cm_anon_type = MES_NORMAL;
2700                 imsg->cm_format_type = FMT_RFC822;
2701                 imsg->cm_fields['A'] = strdup("Citadel");
2702                 imsg->cm_fields['J'] = strdup("do not journal");
2703                 imsg->cm_fields['M'] = instr;   /* imsg owns this memory now */
2704                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2705                 CtdlFreeMessage(imsg);
2706         }
2707
2708         /*
2709          * Any addresses to harvest for someone's address book?
2710          */
2711         if ( (CC->logged_in) && (recps != NULL) ) {
2712                 collected_addresses = harvest_collected_addresses(msg);
2713         }
2714
2715         if (collected_addresses != NULL) {
2716                 begin_critical_section(S_ATBF);
2717                 aptr = (struct addresses_to_be_filed *)
2718                         malloc(sizeof(struct addresses_to_be_filed));
2719                 aptr->next = atbf;
2720                 MailboxName(actual_rm, sizeof actual_rm,
2721                         &CC->user, USERCONTACTSROOM);
2722                 aptr->roomname = strdup(actual_rm);
2723                 aptr->collected_addresses = collected_addresses;
2724                 atbf = aptr;
2725                 end_critical_section(S_ATBF);
2726         }
2727
2728         /*
2729          * Determine whether this message qualifies for journaling.
2730          */
2731         if (msg->cm_fields['J'] != NULL) {
2732                 qualified_for_journaling = 0;
2733         }
2734         else {
2735                 if (recps == NULL) {
2736                         qualified_for_journaling = config.c_journal_pubmsgs;
2737                 }
2738                 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2739                         qualified_for_journaling = config.c_journal_email;
2740                 }
2741                 else {
2742                         qualified_for_journaling = config.c_journal_pubmsgs;
2743                 }
2744         }
2745
2746         /*
2747          * Do we have to perform journaling?  If so, hand off the saved
2748          * RFC822 version will be handed off to the journaler for background
2749          * submit.  Otherwise, we have to free the memory ourselves.
2750          */
2751         if (saved_rfc822_version != NULL) {
2752                 if (qualified_for_journaling) {
2753                         JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2754                 }
2755                 else {
2756                         free(saved_rfc822_version);
2757                 }
2758         }
2759
2760         /* Done. */
2761         return(newmsgid);
2762 }
2763
2764
2765
2766
2767
2768 /*
2769  * Convenience function for generating small administrative messages.
2770  */
2771 void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text, 
2772                         int format_type, char *subject)
2773 {
2774         struct CtdlMessage *msg;
2775         struct recptypes *recp = NULL;
2776
2777         msg = malloc(sizeof(struct CtdlMessage));
2778         memset(msg, 0, sizeof(struct CtdlMessage));
2779         msg->cm_magic = CTDLMESSAGE_MAGIC;
2780         msg->cm_anon_type = MES_NORMAL;
2781         msg->cm_format_type = format_type;
2782
2783         if (from != NULL) {
2784                 msg->cm_fields['A'] = strdup(from);
2785         }
2786         else if (fromaddr != NULL) {
2787                 msg->cm_fields['A'] = strdup(fromaddr);
2788                 if (strchr(msg->cm_fields['A'], '@')) {
2789                         *strchr(msg->cm_fields['A'], '@') = 0;
2790                 }
2791         }
2792         else {
2793                 msg->cm_fields['A'] = strdup("Citadel");
2794         }
2795
2796         if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
2797         if (room != NULL) msg->cm_fields['O'] = strdup(room);
2798         msg->cm_fields['N'] = strdup(NODENAME);
2799         if (to != NULL) {
2800                 msg->cm_fields['R'] = strdup(to);
2801                 recp = validate_recipients(to);
2802         }
2803         if (subject != NULL) {
2804                 msg->cm_fields['U'] = strdup(subject);
2805         }
2806         msg->cm_fields['M'] = strdup(text);
2807
2808         CtdlSubmitMsg(msg, recp, room);
2809         CtdlFreeMessage(msg);
2810         if (recp != NULL) free_recipients(recp);
2811 }
2812
2813
2814
2815 /*
2816  * Back end function used by CtdlMakeMessage() and similar functions
2817  */
2818 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
2819                         size_t maxlen,          /* maximum message length */
2820                         char *exist,            /* if non-null, append to it;
2821                                                    exist is ALWAYS freed  */
2822                         int crlf                /* CRLF newlines instead of LF */
2823                         ) {
2824         char buf[1024];
2825         int linelen;
2826         size_t message_len = 0;
2827         size_t buffer_len = 0;
2828         char *ptr;
2829         char *m;
2830         int flushing = 0;
2831         int finished = 0;
2832         int dotdot = 0;
2833
2834         if (exist == NULL) {
2835                 m = malloc(4096);
2836                 m[0] = 0;
2837                 buffer_len = 4096;
2838                 message_len = 0;
2839         }
2840         else {
2841                 message_len = strlen(exist);
2842                 buffer_len = message_len + 4096;
2843                 m = realloc(exist, buffer_len);
2844                 if (m == NULL) {
2845                         free(exist);
2846                         return m;
2847                 }
2848         }
2849
2850         /* Do we need to change leading ".." to "." for SMTP escaping? */
2851         if (!strcmp(terminator, ".")) {
2852                 dotdot = 1;
2853         }
2854
2855         /* flush the input if we have nowhere to store it */
2856         if (m == NULL) {
2857                 flushing = 1;
2858         }
2859
2860         /* read in the lines of message text one by one */
2861         do {
2862                 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2863                 if (!strcmp(buf, terminator)) finished = 1;
2864                 if (crlf) {
2865                         strcat(buf, "\r\n");
2866                 }
2867                 else {
2868                         strcat(buf, "\n");
2869                 }
2870
2871                 /* Unescape SMTP-style input of two dots at the beginning of the line */
2872                 if (dotdot) {
2873                         if (!strncmp(buf, "..", 2)) {
2874                                 strcpy(buf, &buf[1]);
2875                         }
2876                 }
2877
2878                 if ( (!flushing) && (!finished) ) {
2879                         /* Measure the line */
2880                         linelen = strlen(buf);
2881         
2882                         /* augment the buffer if we have to */
2883                         if ((message_len + linelen) >= buffer_len) {
2884                                 ptr = realloc(m, (buffer_len * 2) );
2885                                 if (ptr == NULL) {      /* flush if can't allocate */
2886                                         flushing = 1;
2887                                 } else {
2888                                         buffer_len = (buffer_len * 2);
2889                                         m = ptr;
2890                                         lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2891                                 }
2892                         }
2893         
2894                         /* Add the new line to the buffer.  NOTE: this loop must avoid
2895                         * using functions like strcat() and strlen() because they
2896                         * traverse the entire buffer upon every call, and doing that
2897                         * for a multi-megabyte message slows it down beyond usability.
2898                         */
2899                         strcpy(&m[message_len], buf);
2900                         message_len += linelen;
2901                 }
2902
2903                 /* if we've hit the max msg length, flush the rest */
2904                 if (message_len >= maxlen) flushing = 1;
2905
2906         } while (!finished);
2907         return(m);
2908 }
2909
2910
2911
2912
2913 /*
2914  * Build a binary message to be saved on disk.
2915  * (NOTE: if you supply 'preformatted_text', the buffer you give it
2916  * will become part of the message.  This means you are no longer
2917  * responsible for managing that memory -- it will be freed along with
2918  * the rest of the fields when CtdlFreeMessage() is called.)
2919  */
2920
2921 struct CtdlMessage *CtdlMakeMessage(
2922         struct ctdluser *author,        /* author's user structure */
2923         char *recipient,                /* NULL if it's not mail */
2924         char *recp_cc,                  /* NULL if it's not mail */
2925         char *room,                     /* room where it's going */
2926         int type,                       /* see MES_ types in header file */
2927         int format_type,                /* variformat, plain text, MIME... */
2928         char *fake_name,                /* who we're masquerading as */
2929         char *my_email,                 /* which of my email addresses to use (empty is ok) */
2930         char *subject,                  /* Subject (optional) */
2931         char *supplied_euid,            /* ...or NULL if this is irrelevant */
2932         char *preformatted_text         /* ...or NULL to read text from client */
2933 ) {
2934         char dest_node[256];
2935         char buf[1024];
2936         struct CtdlMessage *msg;
2937         int i;
2938
2939         msg = malloc(sizeof(struct CtdlMessage));
2940         memset(msg, 0, sizeof(struct CtdlMessage));
2941         msg->cm_magic = CTDLMESSAGE_MAGIC;
2942         msg->cm_anon_type = type;
2943         msg->cm_format_type = format_type;
2944
2945         /* Don't confuse the poor folks if it's not routed mail. */
2946         strcpy(dest_node, "");
2947
2948         striplt(recipient);
2949         striplt(recp_cc);
2950
2951         /* Path or Return-Path */
2952         if (my_email == NULL) my_email = "";
2953
2954         if (!IsEmptyStr(my_email)) {
2955                 msg->cm_fields['P'] = strdup(my_email);
2956         }
2957         else {
2958                 snprintf(buf, sizeof buf, "%s", author->fullname);
2959                 msg->cm_fields['P'] = strdup(buf);
2960         }
2961         for (i=0; (msg->cm_fields['P'][i]!=0); ++i) {
2962                 if (isspace(msg->cm_fields['P'][i])) {
2963                         msg->cm_fields['P'][i] = '_';
2964                 }
2965         }
2966
2967         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
2968         msg->cm_fields['T'] = strdup(buf);
2969
2970         if (fake_name[0])                                       /* author */
2971                 msg->cm_fields['A'] = strdup(fake_name);
2972         else
2973                 msg->cm_fields['A'] = strdup(author->fullname);
2974
2975         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
2976                 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2977         }
2978         else {
2979                 msg->cm_fields['O'] = strdup(CC->room.QRname);
2980         }
2981
2982         msg->cm_fields['N'] = strdup(NODENAME);         /* nodename */
2983         msg->cm_fields['H'] = strdup(HUMANNODE);                /* hnodename */
2984
2985         if (recipient[0] != 0) {
2986                 msg->cm_fields['R'] = strdup(recipient);
2987         }
2988         if (recp_cc[0] != 0) {
2989                 msg->cm_fields['Y'] = strdup(recp_cc);
2990         }
2991         if (dest_node[0] != 0) {
2992                 msg->cm_fields['D'] = strdup(dest_node);
2993         }
2994
2995         if (!IsEmptyStr(my_email)) {
2996                 msg->cm_fields['F'] = strdup(my_email);
2997         }
2998         else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
2999                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3000         }
3001
3002         if (subject != NULL) {
3003                 long length;
3004                 striplt(subject);
3005                 length = strlen(subject);
3006                 if (length > 0) {
3007                         long i;
3008                         long IsAscii;
3009                         IsAscii = -1;
3010                         i = 0;
3011                         while ((subject[i] != '\0') &&
3012                                (IsAscii = isascii(subject[i]) != 0 ))
3013                                 i++;
3014                         if (IsAscii != 0)
3015                                 msg->cm_fields['U'] = strdup(subject);
3016                         else /* ok, we've got utf8 in the string. */
3017                         {
3018                                 msg->cm_fields['U'] = rfc2047encode(subject, length);
3019                         }
3020
3021                 }
3022         }
3023
3024         if (supplied_euid != NULL) {
3025                 msg->cm_fields['E'] = strdup(supplied_euid);
3026         }
3027
3028         if (preformatted_text != NULL) {
3029                 msg->cm_fields['M'] = preformatted_text;
3030         }
3031         else {
3032                 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0);
3033         }
3034
3035         return(msg);
3036 }
3037
3038
3039 /*
3040  * Check to see whether we have permission to post a message in the current
3041  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3042  * returns 0 on success.
3043  */
3044 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
3045         int ra;
3046
3047         if (!(CC->logged_in)) {
3048                 snprintf(errmsgbuf, n, "Not logged in.");
3049                 return (ERROR + NOT_LOGGED_IN);
3050         }
3051
3052         if ((CC->user.axlevel < 2)
3053             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3054                 snprintf(errmsgbuf, n, "Need to be validated to enter "
3055                                 "(except in %s> to sysop)", MAILROOM);
3056                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3057         }
3058
3059         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3060         if (!(ra & UA_POSTALLOWED)) {
3061                 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3062                 return (ERROR + HIGHER_ACCESS_REQUIRED);
3063         }
3064
3065         strcpy(errmsgbuf, "Ok");
3066         return(0);
3067 }
3068
3069
3070 /*
3071  * Check to see if the specified user has Internet mail permission
3072  * (returns nonzero if permission is granted)
3073  */
3074 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3075
3076         /* Do not allow twits to send Internet mail */
3077         if (who->axlevel <= 2) return(0);
3078
3079         /* Globally enabled? */
3080         if (config.c_restrict == 0) return(1);
3081
3082         /* User flagged ok? */
3083         if (who->flags & US_INTERNET) return(2);
3084
3085         /* Aide level access? */
3086         if (who->axlevel >= 6) return(3);
3087
3088         /* No mail for you! */
3089         return(0);
3090 }
3091
3092
3093 /*
3094  * Validate recipients, count delivery types and errors, and handle aliasing
3095  * FIXME check for dupes!!!!!
3096  *
3097  * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses 
3098  * were specified, or the number of addresses found invalid.
3099  *
3100  * Caller needs to free the result using free_recipients()
3101  */
3102 struct recptypes *validate_recipients(char *supplied_recipients) {
3103         struct recptypes *ret;
3104         char *recipients = NULL;
3105         char this_recp[256];
3106         char this_recp_cooked[256];
3107         char append[SIZ];
3108         int num_recps = 0;
3109         int i, j;
3110         int mailtype;
3111         int invalid;
3112         struct ctdluser tempUS;
3113         struct ctdlroom tempQR;
3114         int in_quotes = 0;
3115
3116         /* Initialize */
3117         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3118         if (ret == NULL) return(NULL);
3119
3120         /* Set all strings to null and numeric values to zero */
3121         memset(ret, 0, sizeof(struct recptypes));
3122
3123         if (supplied_recipients == NULL) {
3124                 recipients = strdup("");
3125         }
3126         else {
3127                 recipients = strdup(supplied_recipients);
3128         }
3129
3130         /* Allocate some memory.  Yes, this allocates 500% more memory than we will
3131          * actually need, but it's healthier for the heap than doing lots of tiny
3132          * realloc() calls instead.
3133          */
3134
3135         ret->errormsg = malloc(strlen(recipients) + 1024);
3136         ret->recp_local = malloc(strlen(recipients) + 1024);
3137         ret->recp_internet = malloc(strlen(recipients) + 1024);
3138         ret->recp_ignet = malloc(strlen(recipients) + 1024);
3139         ret->recp_room = malloc(strlen(recipients) + 1024);
3140         ret->display_recp = malloc(strlen(recipients) + 1024);
3141
3142         ret->errormsg[0] = 0;
3143         ret->recp_local[0] = 0;
3144         ret->recp_internet[0] = 0;
3145         ret->recp_ignet[0] = 0;
3146         ret->recp_room[0] = 0;
3147         ret->display_recp[0] = 0;
3148
3149         ret->recptypes_magic = RECPTYPES_MAGIC;
3150
3151         /* Change all valid separator characters to commas */
3152         for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3153                 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3154                         recipients[i] = ',';
3155                 }
3156         }
3157
3158         /* Now start extracting recipients... */
3159
3160         while (!IsEmptyStr(recipients)) {
3161
3162                 for (i=0; i<=strlen(recipients); ++i) {
3163                         if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3164                         if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3165                                 safestrncpy(this_recp, recipients, i+1);
3166                                 this_recp[i] = 0;
3167                                 if (recipients[i] == ',') {
3168                                         strcpy(recipients, &recipients[i+1]);
3169                                 }
3170                                 else {
3171                                         strcpy(recipients, "");
3172                                 }
3173                                 break;
3174                         }
3175                 }
3176
3177                 striplt(this_recp);
3178                 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3179                 ++num_recps;
3180                 mailtype = alias(this_recp);
3181                 mailtype = alias(this_recp);
3182                 mailtype = alias(this_recp);
3183                 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3184                         if (this_recp[j]=='_') {
3185                                 this_recp_cooked[j] = ' ';
3186                         }
3187                         else {
3188                                 this_recp_cooked[j] = this_recp[j];
3189                         }
3190                 }
3191                 invalid = 0;
3192                 switch(mailtype) {
3193                         case MES_LOCAL:
3194                                 if (!strcasecmp(this_recp, "sysop")) {
3195                                         ++ret->num_room;
3196                                         strcpy(this_recp, config.c_aideroom);
3197                                         if (!IsEmptyStr(ret->recp_room)) {
3198                                                 strcat(ret->recp_room, "|");
3199                                         }
3200                                         strcat(ret->recp_room, this_recp);
3201                                 }
3202                                 else if (getuser(&tempUS, this_recp) == 0) {
3203                                         ++ret->num_local;
3204                                         strcpy(this_recp, tempUS.fullname);
3205                                         if (!IsEmptyStr(ret->recp_local)) {
3206                                                 strcat(ret->recp_local, "|");
3207                                         }
3208                                         strcat(ret->recp_local, this_recp);
3209                                 }
3210                                 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3211                                         ++ret->num_local;
3212                                         strcpy(this_recp, tempUS.fullname);
3213                                         if (!IsEmptyStr(ret->recp_local)) {
3214                                                 strcat(ret->recp_local, "|");
3215                                         }
3216                                         strcat(ret->recp_local, this_recp);
3217                                 }
3218                                 else if ( (!strncasecmp(this_recp, "room_", 5))
3219                                       && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3220                                         ++ret->num_room;
3221                                         if (!IsEmptyStr(ret->recp_room)) {
3222                                                 strcat(ret->recp_room, "|");
3223                                         }
3224                                         strcat(ret->recp_room, &this_recp_cooked[5]);
3225                                 }
3226                                 else {
3227                                         ++ret->num_error;
3228                                         invalid = 1;
3229                                 }
3230                                 break;
3231                         case MES_INTERNET:
3232                                 /* Yes, you're reading this correctly: if the target
3233                                  * domain points back to the local system or an attached
3234                                  * Citadel directory, the address is invalid.  That's
3235                                  * because if the address were valid, we would have
3236                                  * already translated it to a local address by now.
3237                                  */
3238                                 if (IsDirectory(this_recp, 0)) {
3239                                         ++ret->num_error;
3240                                         invalid = 1;
3241                                 }
3242                                 else {
3243                                         ++ret->num_internet;
3244                                         if (!IsEmptyStr(ret->recp_internet)) {
3245                                                 strcat(ret->recp_internet, "|");
3246                                         }
3247                                         strcat(ret->recp_internet, this_recp);
3248                                 }
3249                                 break;
3250                         case MES_IGNET:
3251                                 ++ret->num_ignet;
3252                                 if (!IsEmptyStr(ret->recp_ignet)) {
3253                                         strcat(ret->recp_ignet, "|");
3254                                 }
3255                                 strcat(ret->recp_ignet, this_recp);
3256                                 break;
3257                         case MES_ERROR:
3258                                 ++ret->num_error;
3259                                 invalid = 1;
3260                                 break;
3261                 }
3262                 if (invalid) {
3263                         if (IsEmptyStr(ret->errormsg)) {
3264                                 snprintf(append, sizeof append,
3265                                          "Invalid recipient: %s",
3266                                          this_recp);
3267                         }
3268                         else {
3269                                 snprintf(append, sizeof append, ", %s", this_recp);
3270                         }
3271                         if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
3272                                 strcat(ret->errormsg, append);
3273                         }
3274                 }
3275                 else {
3276                         if (IsEmptyStr(ret->display_recp)) {
3277                                 strcpy(append, this_recp);
3278                         }
3279                         else {
3280                                 snprintf(append, sizeof append, ", %s", this_recp);
3281                         }
3282                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3283                                 strcat(ret->display_recp, append);
3284                         }
3285                 }
3286         }
3287
3288         if ((ret->num_local + ret->num_internet + ret->num_ignet +
3289            ret->num_room + ret->num_error) == 0) {
3290                 ret->num_error = (-1);
3291                 strcpy(ret->errormsg, "No recipients specified.");
3292         }
3293
3294         lprintf(CTDL_DEBUG, "validate_recipients()\n");
3295         lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3296         lprintf(CTDL_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
3297         lprintf(CTDL_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3298         lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3299         lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3300
3301         free(recipients);
3302         return(ret);
3303 }
3304
3305
3306 /*
3307  * Destructor for struct recptypes
3308  */
3309 void free_recipients(struct recptypes *valid) {
3310
3311         if (valid == NULL) {
3312                 return;
3313         }
3314
3315         if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3316                 lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3317                 abort();
3318         }
3319
3320         if (valid->errormsg != NULL)            free(valid->errormsg);
3321         if (valid->recp_local != NULL)          free(valid->recp_local);
3322         if (valid->recp_internet != NULL)       free(valid->recp_internet);
3323         if (valid->recp_ignet != NULL)          free(valid->recp_ignet);
3324         if (valid->recp_room != NULL)           free(valid->recp_room);
3325         if (valid->display_recp != NULL)        free(valid->display_recp);
3326         free(valid);
3327 }
3328
3329
3330
3331 /*
3332  * message entry  -  mode 0 (normal)
3333  */
3334 void cmd_ent0(char *entargs)
3335 {
3336         int post = 0;
3337         char recp[SIZ];
3338         char cc[SIZ];
3339         char bcc[SIZ];
3340         char supplied_euid[128];
3341         int anon_flag = 0;
3342         int format_type = 0;
3343         char newusername[256];
3344         char newuseremail[256];
3345         struct CtdlMessage *msg;
3346         int anonymous = 0;
3347         char errmsg[SIZ];
3348         int err = 0;
3349         struct recptypes *valid = NULL;
3350         struct recptypes *valid_to = NULL;
3351         struct recptypes *valid_cc = NULL;
3352         struct recptypes *valid_bcc = NULL;
3353         char subject[SIZ];
3354         int subject_required = 0;
3355         int do_confirm = 0;
3356         long msgnum;
3357         int i, j;
3358         char buf[256];
3359         int newuseremail_ok = 0;
3360
3361         unbuffer_output();
3362
3363         post = extract_int(entargs, 0);
3364         extract_token(recp, entargs, 1, '|', sizeof recp);
3365         anon_flag = extract_int(entargs, 2);
3366         format_type = extract_int(entargs, 3);
3367         extract_token(subject, entargs, 4, '|', sizeof subject);
3368         extract_token(newusername, entargs, 5, '|', sizeof newusername);
3369         do_confirm = extract_int(entargs, 6);
3370         extract_token(cc, entargs, 7, '|', sizeof cc);
3371         extract_token(bcc, entargs, 8, '|', sizeof bcc);
3372         switch(CC->room.QRdefaultview) {
3373                 case VIEW_NOTES:
3374                 case VIEW_WIKI:
3375                         extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3376                         break;
3377                 default:
3378                         supplied_euid[0] = 0;
3379                         break;
3380         }
3381         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3382
3383         /* first check to make sure the request is valid. */
3384
3385         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3386         if (err)
3387         {
3388                 cprintf("%d %s\n", err, errmsg);
3389                 return;
3390         }
3391
3392         /* Check some other permission type things. */
3393
3394         if (IsEmptyStr(newusername)) {
3395                 strcpy(newusername, CC->user.fullname);
3396         }
3397         if (  (CC->user.axlevel < 6)
3398            && (strcasecmp(newusername, CC->user.fullname))
3399            && (strcasecmp(newusername, CC->cs_inet_fn))
3400         ) {     
3401                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3402                         ERROR + HIGHER_ACCESS_REQUIRED,
3403                         newusername
3404                 );
3405                 return;
3406         }
3407
3408
3409         if (IsEmptyStr(newuseremail)) {
3410                 newuseremail_ok = 1;
3411         }
3412
3413         if (!IsEmptyStr(newuseremail)) {
3414                 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3415                         newuseremail_ok = 1;
3416                 }
3417                 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3418                         j = num_tokens(CC->cs_inet_other_emails, '|');
3419                         for (i=0; i<j; ++i) {
3420                                 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3421                                 if (!strcasecmp(newuseremail, buf)) {
3422                                         newuseremail_ok = 1;
3423                                 }
3424                         }
3425                 }
3426         }
3427
3428         if (!newuseremail_ok) {
3429                 cprintf("%d You don't have permission to author messages as '%s'.\n",
3430                         ERROR + HIGHER_ACCESS_REQUIRED,
3431                         newuseremail
3432                 );
3433                 return;
3434         }
3435
3436         CC->cs_flags |= CS_POSTING;
3437
3438         /* In mailbox rooms we have to behave a little differently --
3439          * make sure the user has specified at least one recipient.  Then
3440          * validate the recipient(s).  We do this for the Mail> room, as
3441          * well as any room which has the "Mailbox" view set.
3442          */
3443
3444         if (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3445            || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3446         ) {
3447                 if (CC->user.axlevel < 2) {
3448                         strcpy(recp, "sysop");
3449                         strcpy(cc, "");
3450                         strcpy(bcc, "");
3451                 }
3452
3453                 valid_to = validate_recipients(recp);
3454                 if (valid_to->num_error > 0) {
3455                         cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3456                         free_recipients(valid_to);
3457                         return;
3458                 }
3459
3460                 valid_cc = validate_recipients(cc);
3461                 if (valid_cc->num_error > 0) {
3462                         cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3463                         free_recipients(valid_to);
3464                         free_recipients(valid_cc);
3465                         return;
3466                 }
3467
3468                 valid_bcc = validate_recipients(bcc);
3469                 if (valid_bcc->num_error > 0) {
3470                         cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3471                         free_recipients(valid_to);
3472                         free_recipients(valid_cc);
3473                         free_recipients(valid_bcc);
3474                         return;
3475                 }
3476
3477                 /* Recipient required, but none were specified */
3478                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3479                         free_recipients(valid_to);
3480                         free_recipients(valid_cc);
3481                         free_recipients(valid_bcc);
3482                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3483                         return;
3484                 }
3485
3486                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3487                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3488                                 cprintf("%d You do not have permission "
3489                                         "to send Internet mail.\n",
3490                                         ERROR + HIGHER_ACCESS_REQUIRED);
3491                                 free_recipients(valid_to);
3492                                 free_recipients(valid_cc);
3493                                 free_recipients(valid_bcc);
3494                                 return;
3495                         }
3496                 }
3497
3498                 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)
3499                    && (CC->user.axlevel < 4) ) {
3500                         cprintf("%d Higher access required for network mail.\n",
3501                                 ERROR + HIGHER_ACCESS_REQUIRED);
3502                         free_recipients(valid_to);
3503                         free_recipients(valid_cc);
3504                         free_recipients(valid_bcc);
3505                         return;
3506                 }
3507         
3508                 if ((RESTRICT_INTERNET == 1)
3509                     && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3510                     && ((CC->user.flags & US_INTERNET) == 0)
3511                     && (!CC->internal_pgm)) {
3512                         cprintf("%d You don't have access to Internet mail.\n",
3513                                 ERROR + HIGHER_ACCESS_REQUIRED);
3514                         free_recipients(valid_to);
3515                         free_recipients(valid_cc);
3516                         free_recipients(valid_bcc);
3517                         return;
3518                 }
3519
3520         }
3521
3522         /* Is this a room which has anonymous-only or anonymous-option? */
3523         anonymous = MES_NORMAL;
3524         if (CC->room.QRflags & QR_ANONONLY) {
3525                 anonymous = MES_ANONONLY;
3526         }
3527         if (CC->room.QRflags & QR_ANONOPT) {
3528                 if (anon_flag == 1) {   /* only if the user requested it */
3529                         anonymous = MES_ANONOPT;
3530                 }
3531         }
3532
3533         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3534                 recp[0] = 0;
3535         }
3536
3537         /* Recommend to the client that the use of a message subject is
3538          * strongly recommended in this room, if either the SUBJECTREQ flag
3539          * is set, or if there is one or more Internet email recipients.
3540          */
3541         if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3542         if (valid_to) if (valid_to->num_internet > 0) subject_required = 1;
3543         if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1;
3544         if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1;
3545
3546         /* If we're only checking the validity of the request, return
3547          * success without creating the message.
3548          */
3549         if (post == 0) {
3550                 cprintf("%d %s|%d\n", CIT_OK,
3551                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
3552                         subject_required);
3553                 free_recipients(valid_to);
3554                 free_recipients(valid_cc);
3555                 free_recipients(valid_bcc);
3556                 return;
3557         }
3558
3559         /* We don't need these anymore because we'll do it differently below */
3560         free_recipients(valid_to);
3561         free_recipients(valid_cc);
3562         free_recipients(valid_bcc);
3563
3564         /* Read in the message from the client. */
3565         if (do_confirm) {
3566                 cprintf("%d send message\n", START_CHAT_MODE);
3567         } else {
3568                 cprintf("%d send message\n", SEND_LISTING);
3569         }
3570
3571         msg = CtdlMakeMessage(&CC->user, recp, cc,
3572                 CC->room.QRname, anonymous, format_type,
3573                 newusername, newuseremail, subject,
3574                 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3575                 NULL);
3576
3577         /* Put together one big recipients struct containing to/cc/bcc all in
3578          * one.  This is for the envelope.
3579          */
3580         char *all_recps = malloc(SIZ * 3);
3581         strcpy(all_recps, recp);
3582         if (!IsEmptyStr(cc)) {
3583                 if (!IsEmptyStr(all_recps)) {
3584                         strcat(all_recps, ",");
3585                 }
3586                 strcat(all_recps, cc);
3587         }
3588         if (!IsEmptyStr(bcc)) {
3589                 if (!IsEmptyStr(all_recps)) {
3590                         strcat(all_recps, ",");
3591                 }
3592                 strcat(all_recps, bcc);
3593         }
3594         if (!IsEmptyStr(all_recps)) {
3595                 valid = validate_recipients(all_recps);
3596         }
3597         else {
3598                 valid = NULL;
3599         }
3600         free(all_recps);
3601
3602         if (msg != NULL) {
3603                 msgnum = CtdlSubmitMsg(msg, valid, "");
3604
3605                 if (do_confirm) {
3606                         cprintf("%ld\n", msgnum);
3607                         if (msgnum >= 0L) {
3608                                 cprintf("Message accepted.\n");
3609                         }
3610                         else {
3611                                 cprintf("Internal error.\n");
3612                         }
3613                         if (msg->cm_fields['E'] != NULL) {
3614                                 cprintf("%s\n", msg->cm_fields['E']);
3615                         } else {
3616                                 cprintf("\n");
3617                         }
3618                         cprintf("000\n");
3619                 }
3620
3621                 CtdlFreeMessage(msg);
3622         }
3623         if (valid != NULL) {
3624                 free_recipients(valid);
3625         }
3626         return;
3627 }
3628
3629
3630
3631 /*
3632  * API function to delete messages which match a set of criteria
3633  * (returns the actual number of messages deleted)
3634  */
3635 int CtdlDeleteMessages(char *room_name,         /* which room */
3636                         long *dmsgnums,         /* array of msg numbers to be deleted */
3637                         int num_dmsgnums,       /* number of msgs to be deleted, or 0 for "any" */
3638                         char *content_type      /* or "" for any.  regular expressions expected. */
3639 )
3640 {
3641         struct ctdlroom qrbuf;
3642         struct cdbdata *cdbfr;
3643         long *msglist = NULL;
3644         long *dellist = NULL;
3645         int num_msgs = 0;
3646         int i, j;
3647         int num_deleted = 0;
3648         int delete_this;
3649         struct MetaData smi;
3650         regex_t re;
3651         regmatch_t pm;
3652         int need_to_free_re = 0;
3653
3654         if (content_type) if (!IsEmptyStr(content_type)) {
3655                 regcomp(&re, content_type, 0);
3656                 need_to_free_re = 1;
3657         }
3658         lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
3659                 room_name, num_dmsgnums, content_type);
3660
3661         /* get room record, obtaining a lock... */
3662         if (lgetroom(&qrbuf, room_name) != 0) {
3663                 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3664                         room_name);
3665                 if (need_to_free_re) regfree(&re);
3666                 return (0);     /* room not found */
3667         }
3668         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3669
3670         if (cdbfr != NULL) {
3671                 dellist = malloc(cdbfr->len);
3672                 msglist = (long *) cdbfr->ptr;
3673                 cdbfr->ptr = NULL;      /* CtdlDeleteMessages() now owns this memory */
3674                 num_msgs = cdbfr->len / sizeof(long);
3675                 cdb_free(cdbfr);
3676         }
3677         if (num_msgs > 0) {
3678                 for (i = 0; i < num_msgs; ++i) {
3679                         delete_this = 0x00;
3680
3681                         /* Set/clear a bit for each criterion */
3682
3683                         /* 0 messages in the list or a null list means that we are
3684                          * interested in deleting any messages which meet the other criteria.
3685                          */
3686                         if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
3687                                 delete_this |= 0x01;
3688                         }
3689                         else {
3690                                 for (j=0; j<num_dmsgnums; ++j) {
3691                                         if (msglist[i] == dmsgnums[j]) {
3692                                                 delete_this |= 0x01;
3693                                         }
3694                                 }
3695                         }
3696
3697                         if (IsEmptyStr(content_type)) {
3698                                 delete_this |= 0x02;
3699                         } else {
3700                                 GetMetaData(&smi, msglist[i]);
3701                                 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3702                                         delete_this |= 0x02;
3703                                 }
3704                         }
3705
3706                         /* Delete message only if all bits are set */
3707                         if (delete_this == 0x03) {
3708                                 dellist[num_deleted++] = msglist[i];
3709                                 msglist[i] = 0L;
3710                         }
3711                 }
3712
3713                 num_msgs = sort_msglist(msglist, num_msgs);
3714                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3715                           msglist, (int)(num_msgs * sizeof(long)));
3716
3717                 qrbuf.QRhighest = msglist[num_msgs - 1];
3718         }
3719         lputroom(&qrbuf);
3720
3721         /* Go through the messages we pulled out of the index, and decrement
3722          * their reference counts by 1.  If this is the only room the message
3723          * was in, the reference count will reach zero and the message will
3724          * automatically be deleted from the database.  We do this in a
3725          * separate pass because there might be plug-in hooks getting called,
3726          * and we don't want that happening during an S_ROOMS critical
3727          * section.
3728          */
3729         if (num_deleted) for (i=0; i<num_deleted; ++i) {
3730                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3731                 AdjRefCount(dellist[i], -1);
3732         }
3733
3734         /* Now free the memory we used, and go away. */
3735         if (msglist != NULL) free(msglist);
3736         if (dellist != NULL) free(dellist);
3737         lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3738         if (need_to_free_re) regfree(&re);
3739         return (num_deleted);
3740 }
3741
3742
3743
3744 /*
3745  * Check whether the current user has permission to delete messages from
3746  * the current room (returns 1 for yes, 0 for no)
3747  */
3748 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3749         int ra;
3750         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3751         if (ra & UA_DELETEALLOWED) return(1);
3752         return(0);
3753 }
3754
3755
3756
3757
3758 /*
3759  * Delete message from current room
3760  */
3761 void cmd_dele(char *args)
3762 {
3763         int num_deleted;
3764         int i;
3765         char msgset[SIZ];
3766         char msgtok[32];
3767         long *msgs;
3768         int num_msgs = 0;
3769
3770         extract_token(msgset, args, 0, '|', sizeof msgset);
3771         num_msgs = num_tokens(msgset, ',');
3772         if (num_msgs < 1) {
3773                 cprintf("%d Nothing to do.\n", CIT_OK);
3774                 return;
3775         }
3776
3777         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3778                 cprintf("%d Higher access required.\n",
3779                         ERROR + HIGHER_ACCESS_REQUIRED);
3780                 return;
3781         }
3782
3783         /*
3784          * Build our message set to be moved/copied
3785          */
3786         msgs = malloc(num_msgs * sizeof(long));
3787         for (i=0; i<num_msgs; ++i) {
3788                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3789                 msgs[i] = atol(msgtok);
3790         }
3791
3792         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3793         free(msgs);
3794
3795         if (num_deleted) {
3796                 cprintf("%d %d message%s deleted.\n", CIT_OK,
3797                         num_deleted, ((num_deleted != 1) ? "s" : ""));
3798         } else {
3799                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
3800         }
3801 }
3802
3803
3804 /*
3805  * Back end API function for moves and deletes (multiple messages)
3806  */
3807 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
3808         int err;
3809
3810         err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
3811         if (err != 0) return(err);
3812
3813         return(0);
3814 }
3815
3816
3817
3818
3819 /*
3820  * move or copy a message to another room
3821  */
3822 void cmd_move(char *args)
3823 {
3824         char msgset[SIZ];
3825         char msgtok[32];
3826         long *msgs;
3827         int num_msgs = 0;
3828
3829         char targ[ROOMNAMELEN];
3830         struct ctdlroom qtemp;
3831         int err;
3832         int is_copy = 0;
3833         int ra;
3834         int permit = 0;
3835         int i;
3836
3837         extract_token(msgset, args, 0, '|', sizeof msgset);
3838         num_msgs = num_tokens(msgset, ',');
3839         if (num_msgs < 1) {
3840                 cprintf("%d Nothing to do.\n", CIT_OK);
3841                 return;
3842         }
3843
3844         extract_token(targ, args, 1, '|', sizeof targ);
3845         convert_room_name_macros(targ, sizeof targ);
3846         targ[ROOMNAMELEN - 1] = 0;
3847         is_copy = extract_int(args, 2);
3848
3849         if (getroom(&qtemp, targ) != 0) {
3850                 cprintf("%d '%s' does not exist.\n",
3851                         ERROR + ROOM_NOT_FOUND, targ);
3852                 return;
3853         }
3854
3855         getuser(&CC->user, CC->curr_user);
3856         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3857
3858         /* Check for permission to perform this operation.
3859          * Remember: "CC->room" is source, "qtemp" is target.
3860          */
3861         permit = 0;
3862
3863         /* Aides can move/copy */
3864         if (CC->user.axlevel >= 6) permit = 1;
3865
3866         /* Room aides can move/copy */
3867         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3868
3869         /* Permit move/copy from personal rooms */
3870         if ((CC->room.QRflags & QR_MAILBOX)
3871            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3872
3873         /* Permit only copy from public to personal room */
3874         if ( (is_copy)
3875            && (!(CC->room.QRflags & QR_MAILBOX))
3876            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3877
3878         /* Permit message removal from collaborative delete rooms */
3879         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
3880
3881         /* User must have access to target room */
3882         if (!(ra & UA_KNOWN))  permit = 0;
3883
3884         if (!permit) {
3885                 cprintf("%d Higher access required.\n",
3886                         ERROR + HIGHER_ACCESS_REQUIRED);
3887                 return;
3888         }
3889
3890         /*
3891          * Build our message set to be moved/copied
3892          */
3893         msgs = malloc(num_msgs * sizeof(long));
3894         for (i=0; i<num_msgs; ++i) {
3895                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
3896                 msgs[i] = atol(msgtok);
3897         }
3898
3899         /*
3900          * Do the copy
3901          */
3902         err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
3903         if (err != 0) {
3904                 cprintf("%d Cannot store message(s) in %s: error %d\n",
3905                         err, targ, err);
3906                 free(msgs);
3907                 return;
3908         }
3909
3910         /* Now delete the message from the source room,
3911          * if this is a 'move' rather than a 'copy' operation.
3912          */
3913         if (is_copy == 0) {
3914                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
3915         }
3916         free(msgs);
3917
3918         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3919 }
3920
3921
3922
3923 /*
3924  * GetMetaData()  -  Get the supplementary record for a message
3925  */
3926 void GetMetaData(struct MetaData *smibuf, long msgnum)
3927 {
3928
3929         struct cdbdata *cdbsmi;
3930         long TheIndex;
3931
3932         memset(smibuf, 0, sizeof(struct MetaData));
3933         smibuf->meta_msgnum = msgnum;
3934         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
3935
3936         /* Use the negative of the message number for its supp record index */
3937         TheIndex = (0L - msgnum);
3938
3939         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3940         if (cdbsmi == NULL) {
3941                 return;         /* record not found; go with defaults */
3942         }
3943         memcpy(smibuf, cdbsmi->ptr,
3944                ((cdbsmi->len > sizeof(struct MetaData)) ?
3945                 sizeof(struct MetaData) : cdbsmi->len));
3946         cdb_free(cdbsmi);
3947         return;
3948 }
3949
3950
3951 /*
3952  * PutMetaData()  -  (re)write supplementary record for a message
3953  */
3954 void PutMetaData(struct MetaData *smibuf)
3955 {
3956         long TheIndex;
3957
3958         /* Use the negative of the message number for the metadata db index */
3959         TheIndex = (0L - smibuf->meta_msgnum);
3960
3961         cdb_store(CDB_MSGMAIN,
3962                   &TheIndex, (int)sizeof(long),
3963                   smibuf, (int)sizeof(struct MetaData));
3964
3965 }
3966
3967 /*
3968  * AdjRefCount  -  submit an adjustment to the reference count for a message.
3969  *                 (These are just queued -- we actually process them later.)
3970  */
3971 void AdjRefCount(long msgnum, int incr)
3972 {
3973         struct arcq new_arcq;
3974
3975         begin_critical_section(S_SUPPMSGMAIN);
3976         if (arcfp == NULL) {
3977                 arcfp = fopen(file_arcq, "ab+");
3978         }
3979         end_critical_section(S_SUPPMSGMAIN);
3980
3981         /* msgnum < 0 means that we're trying to close the file */
3982         if (msgnum < 0) {
3983                 lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
3984                 begin_critical_section(S_SUPPMSGMAIN);
3985                 if (arcfp != NULL) {
3986                         fclose(arcfp);
3987                         arcfp = NULL;
3988                 }
3989                 end_critical_section(S_SUPPMSGMAIN);
3990                 return;
3991         }
3992
3993         /*
3994          * If we can't open the queue, perform the operation synchronously.
3995          */
3996         if (arcfp == NULL) {
3997                 TDAP_AdjRefCount(msgnum, incr);
3998                 return;
3999         }
4000
4001         new_arcq.arcq_msgnum = msgnum;
4002         new_arcq.arcq_delta = incr;
4003         fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4004         fflush(arcfp);
4005
4006         return;
4007 }
4008
4009
4010 /*
4011  * TDAP_ProcessAdjRefCountQueue()
4012  *
4013  * Process the queue of message count adjustments that was created by calls
4014  * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4015  * for each one.  This should be an "off hours" operation.
4016  */
4017 int TDAP_ProcessAdjRefCountQueue(void)
4018 {
4019         char file_arcq_temp[PATH_MAX];
4020         int r;
4021         FILE *fp;
4022         struct arcq arcq_rec;
4023         int num_records_processed = 0;
4024
4025         snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq);
4026
4027         begin_critical_section(S_SUPPMSGMAIN);
4028         if (arcfp != NULL) {
4029                 fclose(arcfp);
4030                 arcfp = NULL;
4031         }
4032
4033         r = link(file_arcq, file_arcq_temp);
4034         if (r != 0) {
4035                 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4036                 end_critical_section(S_SUPPMSGMAIN);
4037                 return(num_records_processed);
4038         }
4039
4040         unlink(file_arcq);
4041         end_critical_section(S_SUPPMSGMAIN);
4042
4043         fp = fopen(file_arcq_temp, "rb");
4044         if (fp == NULL) {
4045                 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4046                 return(num_records_processed);
4047         }
4048
4049         while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4050                 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4051                 ++num_records_processed;
4052         }
4053
4054         fclose(fp);
4055         r = unlink(file_arcq_temp);
4056         if (r != 0) {
4057                 lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4058         }
4059
4060         return(num_records_processed);
4061 }
4062
4063
4064
4065 /*
4066  * TDAP_AdjRefCount  -  adjust the reference count for a message.
4067  *                      This one does it "for real" because it's called by
4068  *                      the autopurger function that processes the queue
4069  *                      created by AdjRefCount().   If a message's reference
4070  *                      count becomes zero, we also delete the message from
4071  *                      disk and de-index it.
4072  */
4073 void TDAP_AdjRefCount(long msgnum, int incr)
4074 {
4075
4076         struct MetaData smi;
4077         long delnum;
4078
4079         /* This is a *tight* critical section; please keep it that way, as
4080          * it may get called while nested in other critical sections.  
4081          * Complicating this any further will surely cause deadlock!
4082          */
4083         begin_critical_section(S_SUPPMSGMAIN);
4084         GetMetaData(&smi, msgnum);
4085         smi.meta_refcount += incr;
4086         PutMetaData(&smi);
4087         end_critical_section(S_SUPPMSGMAIN);
4088         lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n",
4089                 msgnum, incr, smi.meta_refcount);
4090
4091         /* If the reference count is now zero, delete the message
4092          * (and its supplementary record as well).
4093          */
4094         if (smi.meta_refcount == 0) {
4095                 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4096                 
4097                 /* Call delete hooks with NULL room to show it has gone altogether */
4098                 PerformDeleteHooks(NULL, msgnum);
4099
4100                 /* Remove from message base */
4101                 delnum = msgnum;
4102                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4103                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4104
4105                 /* Remove metadata record */
4106                 delnum = (0L - msgnum);
4107                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4108         }
4109
4110 }
4111
4112 /*
4113  * Write a generic object to this room
4114  *
4115  * Note: this could be much more efficient.  Right now we use two temporary
4116  * files, and still pull the message into memory as with all others.
4117  */
4118 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
4119                         char *content_type,     /* MIME type of this object */
4120                         char *tempfilename,     /* Where to fetch it from */
4121                         struct ctdluser *is_mailbox,    /* Mailbox room? */
4122                         int is_binary,          /* Is encoding necessary? */
4123                         int is_unique,          /* Del others of this type? */
4124                         unsigned int flags      /* Internal save flags */
4125                         )
4126 {
4127
4128         FILE *fp;
4129         struct ctdlroom qrbuf;
4130         char roomname[ROOMNAMELEN];
4131         struct CtdlMessage *msg;
4132
4133         char *raw_message = NULL;
4134         char *encoded_message = NULL;
4135         off_t raw_length = 0;
4136
4137         if (is_mailbox != NULL) {
4138                 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4139         }
4140         else {
4141                 safestrncpy(roomname, req_room, sizeof(roomname));
4142         }
4143
4144         fp = fopen(tempfilename, "rb");
4145         if (fp == NULL) {
4146                 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
4147                         tempfilename, strerror(errno));
4148                 return;
4149         }
4150         fseek(fp, 0L, SEEK_END);
4151         raw_length = ftell(fp);
4152         rewind(fp);
4153         lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4154
4155         raw_message = malloc((size_t)raw_length + 2);
4156         fread(raw_message, (size_t)raw_length, 1, fp);
4157         fclose(fp);
4158
4159         if (is_binary) {
4160                 encoded_message = malloc((size_t)
4161                         (((raw_length * 134) / 100) + 4096 ) );
4162         }
4163         else {
4164                 encoded_message = malloc((size_t)(raw_length + 4096));
4165         }
4166
4167         sprintf(encoded_message, "Content-type: %s\n", content_type);
4168
4169         if (is_binary) {
4170                 sprintf(&encoded_message[strlen(encoded_message)],
4171                         "Content-transfer-encoding: base64\n\n"
4172                 );
4173         }
4174         else {
4175                 sprintf(&encoded_message[strlen(encoded_message)],
4176                         "Content-transfer-encoding: 7bit\n\n"
4177                 );
4178         }
4179
4180         if (is_binary) {
4181                 CtdlEncodeBase64(
4182                         &encoded_message[strlen(encoded_message)],
4183                         raw_message,
4184                         (int)raw_length
4185                 );
4186         }
4187         else {
4188                 raw_message[raw_length] = 0;
4189                 memcpy(
4190                         &encoded_message[strlen(encoded_message)],
4191                         raw_message,
4192                         (int)(raw_length+1)
4193                 );
4194         }
4195
4196         free(raw_message);
4197
4198         lprintf(CTDL_DEBUG, "Allocating\n");
4199         msg = malloc(sizeof(struct CtdlMessage));
4200         memset(msg, 0, sizeof(struct CtdlMessage));
4201         msg->cm_magic = CTDLMESSAGE_MAGIC;
4202         msg->cm_anon_type = MES_NORMAL;
4203         msg->cm_format_type = 4;
4204         msg->cm_fields['A'] = strdup(CC->user.fullname);
4205         msg->cm_fields['O'] = strdup(req_room);
4206         msg->cm_fields['N'] = strdup(config.c_nodename);
4207         msg->cm_fields['H'] = strdup(config.c_humannode);
4208         msg->cm_flags = flags;
4209         
4210         msg->cm_fields['M'] = encoded_message;
4211
4212         /* Create the requested room if we have to. */
4213         if (getroom(&qrbuf, roomname) != 0) {
4214                 create_room(roomname, 
4215                         ( (is_mailbox != NULL) ? 5 : 3 ),
4216                         "", 0, 1, 0, VIEW_BBS);
4217         }
4218         /* If the caller specified this object as unique, delete all
4219          * other objects of this type that are currently in the room.
4220          */
4221         if (is_unique) {
4222                 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4223                         CtdlDeleteMessages(roomname, NULL, 0, content_type)
4224                 );
4225         }
4226         /* Now write the data */
4227         CtdlSubmitMsg(msg, NULL, roomname);
4228         CtdlFreeMessage(msg);
4229 }
4230
4231
4232
4233
4234
4235
4236 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4237         config_msgnum = msgnum;
4238 }
4239
4240
4241 char *CtdlGetSysConfig(char *sysconfname) {
4242         char hold_rm[ROOMNAMELEN];
4243         long msgnum;
4244         char *conf;
4245         struct CtdlMessage *msg;
4246         char buf[SIZ];
4247         
4248         strcpy(hold_rm, CC->room.QRname);
4249         if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4250                 getroom(&CC->room, hold_rm);
4251                 return NULL;
4252         }
4253
4254
4255         /* We want the last (and probably only) config in this room */
4256         begin_critical_section(S_CONFIG);
4257         config_msgnum = (-1L);
4258         CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4259                 CtdlGetSysConfigBackend, NULL);
4260         msgnum = config_msgnum;
4261         end_critical_section(S_CONFIG);
4262
4263         if (msgnum < 0L) {
4264                 conf = NULL;
4265         }
4266         else {
4267                 msg = CtdlFetchMessage(msgnum, 1);
4268                 if (msg != NULL) {
4269                         conf = strdup(msg->cm_fields['M']);
4270                         CtdlFreeMessage(msg);
4271                 }
4272                 else {
4273                         conf = NULL;
4274                 }
4275         }
4276
4277         getroom(&CC->room, hold_rm);
4278
4279         if (conf != NULL) do {
4280                 extract_token(buf, conf, 0, '\n', sizeof buf);
4281                 strcpy(conf, &conf[strlen(buf)+1]);
4282         } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4283
4284         return(conf);
4285 }
4286
4287 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4288         char temp[PATH_MAX];
4289         FILE *fp;
4290
4291         CtdlMakeTempFileName(temp, sizeof temp);
4292
4293         fp = fopen(temp, "w");
4294         if (fp == NULL) return;
4295         fprintf(fp, "%s", sysconfdata);
4296         fclose(fp);
4297
4298         /* this handy API function does all the work for us */
4299         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
4300         unlink(temp);
4301 }
4302
4303
4304 /*
4305  * Determine whether a given Internet address belongs to the current user
4306  */
4307 int CtdlIsMe(char *addr, int addr_buf_len)
4308 {
4309         struct recptypes *recp;
4310         int i;
4311
4312         recp = validate_recipients(addr);
4313         if (recp == NULL) return(0);
4314
4315         if (recp->num_local == 0) {
4316                 free_recipients(recp);
4317                 return(0);
4318         }
4319
4320         for (i=0; i<recp->num_local; ++i) {
4321                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4322                 if (!strcasecmp(addr, CC->user.fullname)) {
4323                         free_recipients(recp);
4324                         return(1);
4325                 }
4326         }
4327
4328         free_recipients(recp);
4329         return(0);
4330 }
4331
4332
4333 /*
4334  * Citadel protocol command to do the same
4335  */
4336 void cmd_isme(char *argbuf) {
4337         char addr[256];
4338
4339         if (CtdlAccessCheck(ac_logged_in)) return;
4340         extract_token(addr, argbuf, 0, '|', sizeof addr);
4341
4342         if (CtdlIsMe(addr, sizeof addr)) {
4343                 cprintf("%d %s\n", CIT_OK, addr);
4344         }
4345         else {
4346                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4347         }
4348
4349 }