d55ddd1c3cb275cc2e30024f6255af31d54da811
[citadel.git] / citadel / msgbase.c
1 /*
2  * $Id$
3  *
4  * Implements the message store.
5  *
6  */
7
8 #ifdef DLL_EXPORT
9 #define IN_LIBCIT
10 #endif
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # if HAVE_SYS_TIME_H
23 #  include <sys/time.h>
24 # else
25 #  include <time.h>
26 # endif
27 #endif
28
29
30 #include <ctype.h>
31 #include <string.h>
32 #include <limits.h>
33 #include <errno.h>
34 #include <stdarg.h>
35 #include <sys/stat.h>
36 #include "citadel.h"
37 #include "server.h"
38 #include "serv_extensions.h"
39 #include "database.h"
40 #include "msgbase.h"
41 #include "support.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
44 #include "room_ops.h"
45 #include "user_ops.h"
46 #include "file_ops.h"
47 #include "config.h"
48 #include "control.h"
49 #include "tools.h"
50 #include "mime_parser.h"
51 #include "html.h"
52 #include "genstamp.h"
53 #include "internet_addressing.h"
54 #include "serv_fulltext.h"
55
56 long config_msgnum;
57
58
59 /* 
60  * This really belongs in serv_network.c, but I don't know how to export
61  * symbols between modules.
62  */
63 struct FilterList *filterlist = NULL;
64
65
66 /*
67  * These are the four-character field headers we use when outputting
68  * messages in Citadel format (as opposed to RFC822 format).
69  */
70 char *msgkeys[] = {
71         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
72         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
73         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
74         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
75         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
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, 
80         "from",
81         NULL, NULL, NULL,
82         "exti",
83         "rfca",
84         NULL, 
85         "hnod",
86         "msgn",
87         NULL, NULL, NULL,
88         "text",
89         "node",
90         "room",
91         "path",
92         NULL,
93         "rcpt",
94         "spec",
95         "time",
96         "subj",
97         NULL,
98         NULL,
99         NULL,
100         NULL,
101         NULL
102 };
103
104 /*
105  * This function is self explanatory.
106  * (What can I say, I'm in a weird mood today...)
107  */
108 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
109 {
110         int i;
111
112         for (i = 0; i < strlen(name); ++i) {
113                 if (name[i] == '@') {
114                         while (isspace(name[i - 1]) && i > 0) {
115                                 strcpy(&name[i - 1], &name[i]);
116                                 --i;
117                         }
118                         while (isspace(name[i + 1])) {
119                                 strcpy(&name[i + 1], &name[i + 2]);
120                         }
121                 }
122         }
123 }
124
125
126 /*
127  * Aliasing for network mail.
128  * (Error messages have been commented out, because this is a server.)
129  */
130 int alias(char *name)
131 {                               /* process alias and routing info for mail */
132         FILE *fp;
133         int a, i;
134         char aaa[SIZ], bbb[SIZ];
135         char *ignetcfg = NULL;
136         char *ignetmap = NULL;
137         int at = 0;
138         char node[64];
139         char testnode[64];
140         char buf[SIZ];
141
142         striplt(name);
143         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
144
145         fp = fopen("network/mail.aliases", "r");
146         if (fp == NULL) {
147                 fp = fopen("/dev/null", "r");
148         }
149         if (fp == NULL) {
150                 return (MES_ERROR);
151         }
152         strcpy(aaa, "");
153         strcpy(bbb, "");
154         while (fgets(aaa, sizeof aaa, fp) != NULL) {
155                 while (isspace(name[0]))
156                         strcpy(name, &name[1]);
157                 aaa[strlen(aaa) - 1] = 0;
158                 strcpy(bbb, "");
159                 for (a = 0; a < strlen(aaa); ++a) {
160                         if (aaa[a] == ',') {
161                                 strcpy(bbb, &aaa[a + 1]);
162                                 aaa[a] = 0;
163                         }
164                 }
165                 if (!strcasecmp(name, aaa))
166                         strcpy(name, bbb);
167         }
168         fclose(fp);
169
170         /* Hit the Global Address Book */
171         if (CtdlDirectoryLookup(aaa, name) == 0) {
172                 strcpy(name, aaa);
173         }
174
175         lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
176
177         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
178         for (a=0; a<strlen(name); ++a) {
179                 if (name[a] == '@') {
180                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
181                                 name[a] = 0;
182                                 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
183                         }
184                 }
185         }
186
187         /* determine local or remote type, see citadel.h */
188         at = haschar(name, '@');
189         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
190         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
191         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
192
193         /* figure out the delivery mode */
194         extract_token(node, name, 1, '@', sizeof node);
195
196         /* If there are one or more dots in the nodename, we assume that it
197          * is an FQDN and will attempt SMTP delivery to the Internet.
198          */
199         if (haschar(node, '.') > 0) {
200                 return(MES_INTERNET);
201         }
202
203         /* Otherwise we look in the IGnet maps for a valid Citadel node.
204          * Try directly-connected nodes first...
205          */
206         ignetcfg = CtdlGetSysConfig(IGNETCFG);
207         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
208                 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
209                 extract_token(testnode, buf, 0, '|', sizeof testnode);
210                 if (!strcasecmp(node, testnode)) {
211                         free(ignetcfg);
212                         return(MES_IGNET);
213                 }
214         }
215         free(ignetcfg);
216
217         /*
218          * Then try nodes that are two or more hops away.
219          */
220         ignetmap = CtdlGetSysConfig(IGNETMAP);
221         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
222                 extract_token(buf, ignetmap, i, '\n', sizeof buf);
223                 extract_token(testnode, buf, 0, '|', sizeof testnode);
224                 if (!strcasecmp(node, testnode)) {
225                         free(ignetmap);
226                         return(MES_IGNET);
227                 }
228         }
229         free(ignetmap);
230
231         /* If we get to this point it's an invalid node name */
232         return (MES_ERROR);
233 }
234
235
236 void get_mm(void)
237 {
238         FILE *fp;
239
240         fp = fopen("citadel.control", "r");
241         if (fp == NULL) {
242                 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
243                         strerror(errno));
244                 exit(errno);
245         }
246         fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
247         fclose(fp);
248 }
249
250
251
252 void simple_listing(long msgnum, void *userdata)
253 {
254         cprintf("%ld\n", msgnum);
255 }
256
257
258
259 /* Determine if a given message matches the fields in a message template.
260  * Return 0 for a successful match.
261  */
262 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
263         int i;
264
265         /* If there aren't any fields in the template, all messages will
266          * match.
267          */
268         if (template == NULL) return(0);
269
270         /* Null messages are bogus. */
271         if (msg == NULL) return(1);
272
273         for (i='A'; i<='Z'; ++i) {
274                 if (template->cm_fields[i] != NULL) {
275                         if (msg->cm_fields[i] == NULL) {
276                                 return 1;
277                         }
278                         if (strcasecmp(msg->cm_fields[i],
279                                 template->cm_fields[i])) return 1;
280                 }
281         }
282
283         /* All compares succeeded: we have a match! */
284         return 0;
285 }
286
287
288
289 /*
290  * Retrieve the "seen" message list for the current room.
291  */
292 void CtdlGetSeen(char *buf, int which_set) {
293         struct visit vbuf;
294
295         /* Learn about the user and room in question */
296         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
297
298         if (which_set == ctdlsetseen_seen)
299                 safestrncpy(buf, vbuf.v_seen, SIZ);
300         if (which_set == ctdlsetseen_answered)
301                 safestrncpy(buf, vbuf.v_answered, SIZ);
302 }
303
304
305
306 /*
307  * Manipulate the "seen msgs" string (or other message set strings)
308  */
309 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set,
310                 struct ctdluser *which_user, struct ctdlroom *which_room) {
311         struct cdbdata *cdbfr;
312         int i, j;
313         int is_seen = 0;
314         int was_seen = 0;
315         long lo = (-1L);
316         long hi = (-1L);
317         long t = (-1L);
318         int trimming = 0;
319         struct visit vbuf;
320         long *msglist;
321         int num_msgs = 0;
322         char vset[SIZ];
323         char *is_set;   /* actually an array of booleans */
324         int num_sets;
325         int s;
326         char setstr[SIZ], lostr[SIZ], histr[SIZ];
327         size_t tmp;
328
329         lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
330                 target_msgnum, target_setting, which_set);
331
332         /* Learn about the user and room in question */
333         CtdlGetRelationship(&vbuf,
334                 ((which_user != NULL) ? which_user : &CC->user),
335                 ((which_room != NULL) ? which_room : &CC->room)
336         );
337
338         /* Load the message list */
339         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
340         if (cdbfr != NULL) {
341                 msglist = malloc(cdbfr->len);
342                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
343                 num_msgs = cdbfr->len / sizeof(long);
344                 cdb_free(cdbfr);
345         } else {
346                 return; /* No messages at all?  No further action. */
347         }
348
349         is_set = malloc(num_msgs * sizeof(char));
350         memset(is_set, 0, (num_msgs * sizeof(char)) );
351
352         /* Decide which message set we're manipulating */
353         switch(which_set) {
354                 case ctdlsetseen_seen:
355                         safestrncpy(vset, vbuf.v_seen, sizeof vset);
356                         break;
357                 case ctdlsetseen_answered:
358                         safestrncpy(vset, vbuf.v_answered, sizeof vset);
359                         break;
360         }
361
362         /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
363
364         /* Translate the existing sequence set into an array of booleans */
365         num_sets = num_tokens(vset, ',');
366         for (s=0; s<num_sets; ++s) {
367                 extract_token(setstr, vset, s, ',', sizeof setstr);
368
369                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
370                 if (num_tokens(setstr, ':') >= 2) {
371                         extract_token(histr, setstr, 1, ':', sizeof histr);
372                         if (!strcmp(histr, "*")) {
373                                 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
374                         }
375                 }
376                 else {
377                         strcpy(histr, lostr);
378                 }
379                 lo = atol(lostr);
380                 hi = atol(histr);
381
382                 for (i = 0; i < num_msgs; ++i) {
383                         if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
384                                 is_set[i] = 1;
385                         }
386                 }
387         }
388
389         /* Now translate the array of booleans back into a sequence set */
390         strcpy(vset, "");
391         lo = (-1L);
392         hi = (-1L);
393
394         for (i=0; i<num_msgs; ++i) {
395                 is_seen = 0;
396
397                 if (msglist[i] == target_msgnum) {
398                         is_seen = target_setting;
399                 }
400                 else {
401                         is_seen = is_set[i];
402                 }
403
404                 if (is_seen) {
405                         if (lo < 0L) lo = msglist[i];
406                         hi = msglist[i];
407                 }
408
409                 if (  ((is_seen == 0) && (was_seen == 1))
410                    || ((is_seen == 1) && (i == num_msgs-1)) ) {
411
412                         /* begin trim-o-matic code */
413                         j=9;
414                         trimming = 0;
415                         while ( (strlen(vset) + 20) > sizeof vset) {
416                                 remove_token(vset, 0, ',');
417                                 trimming = 1;
418                                 if (j--) break; /* loop no more than 9 times */
419                         }
420                         if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
421                                 t = atol(vset);
422                                 if (t<2) t=2;
423                                 --t;
424                                 snprintf(lostr, sizeof lostr,
425                                         "1:%ld,%s", t, vset);
426                                 safestrncpy(vset, lostr, sizeof vset);
427                         }
428                         /* end trim-o-matic code */
429
430                         tmp = strlen(vset);
431                         if (tmp > 0) {
432                                 strcat(vset, ",");
433                                 ++tmp;
434                         }
435                         if (lo == hi) {
436                                 snprintf(&vset[tmp], (sizeof vset) - tmp,
437                                          "%ld", lo);
438                         }
439                         else {
440                                 snprintf(&vset[tmp], (sizeof vset) - tmp,
441                                          "%ld:%ld", lo, hi);
442                         }
443                         lo = (-1L);
444                         hi = (-1L);
445                 }
446                 was_seen = is_seen;
447         }
448
449         /* Decide which message set we're manipulating */
450         switch (which_set) {
451                 case ctdlsetseen_seen:
452                         safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
453                         break;
454                 case ctdlsetseen_answered:
455                         safestrncpy(vbuf.v_answered, vset,
456                                                 sizeof vbuf.v_answered);
457                         break;
458         }
459         free(is_set);
460
461         /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
462         free(msglist);
463         CtdlSetRelationship(&vbuf,
464                 ((which_user != NULL) ? which_user : &CC->user),
465                 ((which_room != NULL) ? which_room : &CC->room)
466         );
467 }
468
469
470 /*
471  * API function to perform an operation for each qualifying message in the
472  * current room.  (Returns the number of messages processed.)
473  */
474 int CtdlForEachMessage(int mode, long ref,
475                         char *content_type,
476                         struct CtdlMessage *compare,
477                         void (*CallBack) (long, void *),
478                         void *userdata)
479 {
480
481         int a;
482         struct visit vbuf;
483         struct cdbdata *cdbfr;
484         long *msglist = NULL;
485         int num_msgs = 0;
486         int num_processed = 0;
487         long thismsg;
488         struct MetaData smi;
489         struct CtdlMessage *msg;
490         int is_seen;
491         long lastold = 0L;
492         int printed_lastold = 0;
493
494         /* Learn about the user and room in question */
495         get_mm();
496         getuser(&CC->user, CC->curr_user);
497         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
498
499         /* Load the message list */
500         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
501         if (cdbfr != NULL) {
502                 msglist = malloc(cdbfr->len);
503                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
504                 num_msgs = cdbfr->len / sizeof(long);
505                 cdb_free(cdbfr);
506         } else {
507                 return 0;       /* No messages at all?  No further action. */
508         }
509
510
511         /*
512          * Now begin the traversal.
513          */
514         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
515
516                 /* If the caller is looking for a specific MIME type, filter
517                  * out all messages which are not of the type requested.
518                  */
519                 if (content_type != NULL) if (strlen(content_type) > 0) {
520
521                         /* This call to GetMetaData() sits inside this loop
522                          * so that we only do the extra database read per msg
523                          * if we need to.  Doing the extra read all the time
524                          * really kills the server.  If we ever need to use
525                          * metadata for another search criterion, we need to
526                          * move the read somewhere else -- but still be smart
527                          * enough to only do the read if the caller has
528                          * specified something that will need it.
529                          */
530                         GetMetaData(&smi, msglist[a]);
531
532                         if (strcasecmp(smi.meta_content_type, content_type)) {
533                                 msglist[a] = 0L;
534                         }
535                 }
536         }
537
538         num_msgs = sort_msglist(msglist, num_msgs);
539
540         /* If a template was supplied, filter out the messages which
541          * don't match.  (This could induce some delays!)
542          */
543         if (num_msgs > 0) {
544                 if (compare != NULL) {
545                         for (a = 0; a < num_msgs; ++a) {
546                                 msg = CtdlFetchMessage(msglist[a], 1);
547                                 if (msg != NULL) {
548                                         if (CtdlMsgCmp(msg, compare)) {
549                                                 msglist[a] = 0L;
550                                         }
551                                         CtdlFreeMessage(msg);
552                                 }
553                         }
554                 }
555         }
556
557         
558         /*
559          * Now iterate through the message list, according to the
560          * criteria supplied by the caller.
561          */
562         if (num_msgs > 0)
563                 for (a = 0; a < num_msgs; ++a) {
564                         thismsg = msglist[a];
565                         is_seen = is_msg_in_sequence_set(vbuf.v_seen, thismsg);
566                         if (is_seen) lastold = thismsg;
567                         if ((thismsg > 0L)
568                             && (
569
570                                        (mode == MSGS_ALL)
571                                        || ((mode == MSGS_OLD) && (is_seen))
572                                        || ((mode == MSGS_NEW) && (!is_seen))
573                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
574                                    || ((mode == MSGS_FIRST) && (a < ref))
575                                 || ((mode == MSGS_GT) && (thismsg > ref))
576                                 || ((mode == MSGS_EQ) && (thismsg == ref))
577                             )
578                             ) {
579                                 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
580                                         if (CallBack)
581                                                 CallBack(lastold, userdata);
582                                         printed_lastold = 1;
583                                         ++num_processed;
584                                 }
585                                 if (CallBack) CallBack(thismsg, userdata);
586                                 ++num_processed;
587                         }
588                 }
589         free(msglist);          /* Clean up */
590         return num_processed;
591 }
592
593
594
595 /*
596  * cmd_msgs()  -  get list of message #'s in this room
597  *              implements the MSGS server command using CtdlForEachMessage()
598  */
599 void cmd_msgs(char *cmdbuf)
600 {
601         int mode = 0;
602         char which[16];
603         char buf[256];
604         char tfield[256];
605         char tvalue[256];
606         int cm_ref = 0;
607         int i;
608         int with_template = 0;
609         struct CtdlMessage *template = NULL;
610
611         extract_token(which, cmdbuf, 0, '|', sizeof which);
612         cm_ref = extract_int(cmdbuf, 1);
613         with_template = extract_int(cmdbuf, 2);
614
615         mode = MSGS_ALL;
616         strcat(which, "   ");
617         if (!strncasecmp(which, "OLD", 3))
618                 mode = MSGS_OLD;
619         else if (!strncasecmp(which, "NEW", 3))
620                 mode = MSGS_NEW;
621         else if (!strncasecmp(which, "FIRST", 5))
622                 mode = MSGS_FIRST;
623         else if (!strncasecmp(which, "LAST", 4))
624                 mode = MSGS_LAST;
625         else if (!strncasecmp(which, "GT", 2))
626                 mode = MSGS_GT;
627
628         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
629                 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
630                 return;
631         }
632
633         if (with_template) {
634                 unbuffer_output();
635                 cprintf("%d Send template then receive message list\n",
636                         START_CHAT_MODE);
637                 template = (struct CtdlMessage *)
638                         malloc(sizeof(struct CtdlMessage));
639                 memset(template, 0, sizeof(struct CtdlMessage));
640                 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
641                         extract_token(tfield, buf, 0, '|', sizeof tfield);
642                         extract_token(tvalue, buf, 1, '|', sizeof tvalue);
643                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
644                                 if (!strcasecmp(tfield, msgkeys[i])) {
645                                         template->cm_fields[i] =
646                                                 strdup(tvalue);
647                                 }
648                         }
649                 }
650                 buffer_output();
651         }
652         else {
653                 cprintf("%d Message list...\n", LISTING_FOLLOWS);
654         }
655
656         CtdlForEachMessage(mode, cm_ref,
657                 NULL, template, simple_listing, NULL);
658         if (template != NULL) CtdlFreeMessage(template);
659         cprintf("000\n");
660 }
661
662
663
664
665 /* 
666  * help_subst()  -  support routine for help file viewer
667  */
668 void help_subst(char *strbuf, char *source, char *dest)
669 {
670         char workbuf[SIZ];
671         int p;
672
673         while (p = pattern2(strbuf, source), (p >= 0)) {
674                 strcpy(workbuf, &strbuf[p + strlen(source)]);
675                 strcpy(&strbuf[p], dest);
676                 strcat(strbuf, workbuf);
677         }
678 }
679
680
681 void do_help_subst(char *buffer)
682 {
683         char buf2[16];
684
685         help_subst(buffer, "^nodename", config.c_nodename);
686         help_subst(buffer, "^humannode", config.c_humannode);
687         help_subst(buffer, "^fqdn", config.c_fqdn);
688         help_subst(buffer, "^username", CC->user.fullname);
689         snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
690         help_subst(buffer, "^usernum", buf2);
691         help_subst(buffer, "^sysadm", config.c_sysadm);
692         help_subst(buffer, "^variantname", CITADEL);
693         snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
694         help_subst(buffer, "^maxsessions", buf2);
695         help_subst(buffer, "^bbsdir", CTDLDIR);
696 }
697
698
699
700 /*
701  * memfmout()  -  Citadel text formatter and paginator.
702  *           Although the original purpose of this routine was to format
703  *           text to the reader's screen width, all we're really using it
704  *           for here is to format text out to 80 columns before sending it
705  *           to the client.  The client software may reformat it again.
706  */
707 void memfmout(
708         int width,              /* screen width to use */
709         char *mptr,             /* where are we going to get our text from? */
710         char subst,             /* nonzero if we should do substitutions */
711         char *nl)               /* string to terminate lines with */
712 {
713         int a, b, c;
714         int real = 0;
715         int old = 0;
716         cit_uint8_t ch;
717         char aaa[140];
718         char buffer[SIZ];
719
720         strcpy(aaa, "");
721         old = 255;
722         strcpy(buffer, "");
723         c = 1;                  /* c is the current pos */
724
725         do {
726                 if (subst) {
727                         while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
728                                 ch = *mptr++;
729                                 buffer[strlen(buffer) + 1] = 0;
730                                 buffer[strlen(buffer)] = ch;
731                         }
732
733                         if (buffer[0] == '^')
734                                 do_help_subst(buffer);
735
736                         buffer[strlen(buffer) + 1] = 0;
737                         a = buffer[0];
738                         strcpy(buffer, &buffer[1]);
739                 } else {
740                         ch = *mptr++;
741                 }
742
743                 old = real;
744                 real = ch;
745
746                 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
747                         ch = 32;
748                 if (((old == 13) || (old == 10)) && (isspace(real))) {
749                         cprintf("%s", nl);
750                         c = 1;
751                 }
752                 if (ch > 126)
753                         continue;
754
755                 if (ch > 32) {
756                         if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
757                                 cprintf("%s%s", nl, aaa);
758                                 c = strlen(aaa);
759                                 aaa[0] = 0;
760                         }
761                         b = strlen(aaa);
762                         aaa[b] = ch;
763                         aaa[b + 1] = 0;
764                 }
765                 if (ch == 32) {
766                         if ((strlen(aaa) + c) > (width - 5)) {
767                                 cprintf("%s", nl);
768                                 c = 1;
769                         }
770                         cprintf("%s ", aaa);
771                         ++c;
772                         c = c + strlen(aaa);
773                         strcpy(aaa, "");
774                 }
775                 if ((ch == 13) || (ch == 10)) {
776                         cprintf("%s%s", aaa, nl);
777                         c = 1;
778                         strcpy(aaa, "");
779                 }
780
781         } while (ch > 0);
782
783         cprintf("%s%s", aaa, nl);
784 }
785
786
787
788 /*
789  * Callback function for mime parser that simply lists the part
790  */
791 void list_this_part(char *name, char *filename, char *partnum, char *disp,
792                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
793                     void *cbuserdata)
794 {
795
796         cprintf("part=%s|%s|%s|%s|%s|%ld\n",
797                 name, filename, partnum, disp, cbtype, (long)length);
798 }
799
800 /* 
801  * Callback function for multipart prefix
802  */
803 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
804                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
805                     void *cbuserdata)
806 {
807         cprintf("pref=%s|%s\n", partnum, cbtype);
808 }
809
810 /* 
811  * Callback function for multipart sufffix
812  */
813 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
814                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
815                     void *cbuserdata)
816 {
817         cprintf("suff=%s|%s\n", partnum, cbtype);
818 }
819
820
821 /*
822  * Callback function for mime parser that opens a section for downloading
823  */
824 void mime_download(char *name, char *filename, char *partnum, char *disp,
825                    void *content, char *cbtype, char *cbcharset, size_t length,
826                    char *encoding, void *cbuserdata)
827 {
828
829         /* Silently go away if there's already a download open... */
830         if (CC->download_fp != NULL)
831                 return;
832
833         /* ...or if this is not the desired section */
834         if (strcasecmp(CC->download_desired_section, partnum))
835                 return;
836
837         CC->download_fp = tmpfile();
838         if (CC->download_fp == NULL)
839                 return;
840
841         fwrite(content, length, 1, CC->download_fp);
842         fflush(CC->download_fp);
843         rewind(CC->download_fp);
844
845         OpenCmdResult(filename, cbtype);
846 }
847
848
849
850 /*
851  * Load a message from disk into memory.
852  * This is used by CtdlOutputMsg() and other fetch functions.
853  *
854  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
855  *       using the CtdlMessageFree() function.
856  */
857 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
858 {
859         struct cdbdata *dmsgtext;
860         struct CtdlMessage *ret = NULL;
861         char *mptr;
862         char *upper_bound;
863         cit_uint8_t ch;
864         cit_uint8_t field_header;
865
866         lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
867
868         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
869         if (dmsgtext == NULL) {
870                 return NULL;
871         }
872         mptr = dmsgtext->ptr;
873         upper_bound = mptr + dmsgtext->len;
874
875         /* Parse the three bytes that begin EVERY message on disk.
876          * The first is always 0xFF, the on-disk magic number.
877          * The second is the anonymous/public type byte.
878          * The third is the format type byte (vari, fixed, or MIME).
879          */
880         ch = *mptr++;
881         if (ch != 255) {
882                 lprintf(CTDL_ERR,
883                         "Message %ld appears to be corrupted.\n",
884                         msgnum);
885                 cdb_free(dmsgtext);
886                 return NULL;
887         }
888         ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
889         memset(ret, 0, sizeof(struct CtdlMessage));
890
891         ret->cm_magic = CTDLMESSAGE_MAGIC;
892         ret->cm_anon_type = *mptr++;    /* Anon type byte */
893         ret->cm_format_type = *mptr++;  /* Format type byte */
894
895         /*
896          * The rest is zero or more arbitrary fields.  Load them in.
897          * We're done when we encounter either a zero-length field or
898          * have just processed the 'M' (message text) field.
899          */
900         do {
901                 if (mptr >= upper_bound) {
902                         break;
903                 }
904                 field_header = *mptr++;
905                 ret->cm_fields[field_header] = strdup(mptr);
906
907                 while (*mptr++ != 0);   /* advance to next field */
908
909         } while ((mptr < upper_bound) && (field_header != 'M'));
910
911         cdb_free(dmsgtext);
912
913         /* Always make sure there's something in the msg text field.  If
914          * it's NULL, the message text is most likely stored separately,
915          * so go ahead and fetch that.  Failing that, just set a dummy
916          * body so other code doesn't barf.
917          */
918         if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
919                 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
920                 if (dmsgtext != NULL) {
921                         ret->cm_fields['M'] = strdup(dmsgtext->ptr);
922                         cdb_free(dmsgtext);
923                 }
924         }
925         if (ret->cm_fields['M'] == NULL) {
926                 ret->cm_fields['M'] = strdup("<no text>\n");
927         }
928
929         /* Perform "before read" hooks (aborting if any return nonzero) */
930         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
931                 CtdlFreeMessage(ret);
932                 return NULL;
933         }
934
935         return (ret);
936 }
937
938
939 /*
940  * Returns 1 if the supplied pointer points to a valid Citadel message.
941  * If the pointer is NULL or the magic number check fails, returns 0.
942  */
943 int is_valid_message(struct CtdlMessage *msg) {
944         if (msg == NULL)
945                 return 0;
946         if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
947                 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
948                 return 0;
949         }
950         return 1;
951 }
952
953
954 /*
955  * 'Destructor' for struct CtdlMessage
956  */
957 void CtdlFreeMessage(struct CtdlMessage *msg)
958 {
959         int i;
960
961         if (is_valid_message(msg) == 0) return;
962
963         for (i = 0; i < 256; ++i)
964                 if (msg->cm_fields[i] != NULL) {
965                         free(msg->cm_fields[i]);
966                 }
967
968         msg->cm_magic = 0;      /* just in case */
969         free(msg);
970 }
971
972
973 /*
974  * Pre callback function for multipart/alternative
975  *
976  * NOTE: this differs from the standard behavior for a reason.  Normally when
977  *       displaying multipart/alternative you want to show the _last_ usable
978  *       format in the message.  Here we show the _first_ one, because it's
979  *       usually text/plain.  Since this set of functions is designed for text
980  *       output to non-MIME-aware clients, this is the desired behavior.
981  *
982  */
983 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
984                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
985                 void *cbuserdata)
986 {
987         struct ma_info *ma;
988         
989         ma = (struct ma_info *)cbuserdata;
990         lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);  
991         if (!strcasecmp(cbtype, "multipart/alternative")) {
992                 ++ma->is_ma;
993                 ma->did_print = 0;
994                 return;
995         }
996 }
997
998 /*
999  * Post callback function for multipart/alternative
1000  */
1001 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1002                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1003                 void *cbuserdata)
1004 {
1005         struct ma_info *ma;
1006         
1007         ma = (struct ma_info *)cbuserdata;
1008         lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype); 
1009         if (!strcasecmp(cbtype, "multipart/alternative")) {
1010                 --ma->is_ma;
1011                 ma->did_print = 0;
1012         return;
1013         }
1014 }
1015
1016 /*
1017  * Inline callback function for mime parser that wants to display text
1018  */
1019 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1020                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1021                 void *cbuserdata)
1022         {
1023                 char *ptr;
1024                 char *wptr;
1025                 size_t wlen;
1026                 struct ma_info *ma;
1027         
1028                 ma = (struct ma_info *)cbuserdata;
1029
1030                 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);      
1031
1032                 /*
1033                  * If we're in the middle of a multipart/alternative scope and
1034                  * we've already printed another section, skip this one.
1035                  */     
1036                 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1037                         lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1038                         return;
1039                 }
1040                 ma->did_print = 1;
1041         
1042                 if ( (!strcasecmp(cbtype, "text/plain")) 
1043                    || (strlen(cbtype)==0) ) {
1044                         wptr = content;
1045                         if (length > 0) {
1046                                 client_write(wptr, length);
1047                                 if (wptr[length-1] != '\n') {
1048                                         cprintf("\n");
1049                                 }
1050                         }
1051                 }
1052                 else if (!strcasecmp(cbtype, "text/html")) {
1053                         ptr = html_to_ascii(content, 80, 0);
1054                         wlen = strlen(ptr);
1055                         client_write(ptr, wlen);
1056                         if (ptr[wlen-1] != '\n') {
1057                                 cprintf("\n");
1058                         }
1059                         free(ptr);
1060                 }
1061                 else if (strncasecmp(cbtype, "multipart/", 10)) {
1062                         cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1063                                 partnum, filename, cbtype, (long)length);
1064                 }
1065         }
1066
1067 /*
1068  * The client is elegant and sophisticated and wants to be choosy about
1069  * MIME content types, so figure out which multipart/alternative part
1070  * we're going to send.
1071  */
1072 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1073                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1074                 void *cbuserdata)
1075 {
1076         char buf[1024];
1077         int i;
1078         struct ma_info *ma;
1079         
1080         ma = (struct ma_info *)cbuserdata;
1081
1082         if (ma->is_ma > 0) {
1083                 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1084                         extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1085                         if (!strcasecmp(buf, cbtype)) {
1086                                 strcpy(ma->chosen_part, partnum);
1087                         }
1088                 }
1089         }
1090 }
1091
1092 /*
1093  * Now that we've chosen our preferred part, output it.
1094  */
1095 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1096                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1097                 void *cbuserdata)
1098 {
1099         int i;
1100         char buf[128];
1101         int add_newline = 0;
1102         char *text_content;
1103         struct ma_info *ma;
1104         
1105         ma = (struct ma_info *)cbuserdata;
1106
1107         /* This is not the MIME part you're looking for... */
1108         if (strcasecmp(partnum, ma->chosen_part)) return;
1109
1110         /* If the content-type of this part is in our preferred formats
1111          * list, we can simply output it verbatim.
1112          */
1113         for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1114                 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1115                 if (!strcasecmp(buf, cbtype)) {
1116                         /* Yeah!  Go!  W00t!! */
1117
1118                         text_content = (char *)content;
1119                         if (text_content[length-1] != '\n') {
1120                                 ++add_newline;
1121                         }
1122
1123                         cprintf("Content-type: %s", cbtype);
1124                         if (strlen(cbcharset) > 0) {
1125                                 cprintf("; charset=%s", cbcharset);
1126                         }
1127                         cprintf("\nContent-length: %d\n",
1128                                 (int)(length + add_newline) );
1129                         if (strlen(encoding) > 0) {
1130                                 cprintf("Content-transfer-encoding: %s\n", encoding);
1131                         }
1132                         else {
1133                                 cprintf("Content-transfer-encoding: 7bit\n");
1134                         }
1135                         cprintf("\n");
1136                         client_write(content, length);
1137                         if (add_newline) cprintf("\n");
1138                         return;
1139                 }
1140         }
1141
1142         /* No translations required or possible: output as text/plain */
1143         cprintf("Content-type: text/plain\n\n");
1144         fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1145                         length, encoding, cbuserdata);
1146 }
1147
1148
1149 /*
1150  * Get a message off disk.  (returns om_* values found in msgbase.h)
1151  * 
1152  */
1153 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
1154                 int mode,               /* how would you like that message? */
1155                 int headers_only,       /* eschew the message body? */
1156                 int do_proto,           /* do Citadel protocol responses? */
1157                 int crlf                /* Use CRLF newlines instead of LF? */
1158 ) {
1159         struct CtdlMessage *TheMessage = NULL;
1160         int retcode = om_no_such_msg;
1161
1162         lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n", 
1163                 msg_num, mode);
1164
1165         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1166                 if (do_proto) cprintf("%d Not logged in.\n",
1167                         ERROR + NOT_LOGGED_IN);
1168                 return(om_not_logged_in);
1169         }
1170
1171         /* FIXME: check message id against msglist for this room */
1172
1173         /*
1174          * Fetch the message from disk.  If we're in any sort of headers
1175          * only mode, request that we don't even bother loading the body
1176          * into memory.
1177          */
1178         if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1179                 TheMessage = CtdlFetchMessage(msg_num, 0);
1180         }
1181         else {
1182                 TheMessage = CtdlFetchMessage(msg_num, 1);
1183         }
1184
1185         if (TheMessage == NULL) {
1186                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1187                         ERROR + MESSAGE_NOT_FOUND, msg_num);
1188                 return(om_no_such_msg);
1189         }
1190         
1191         retcode = CtdlOutputPreLoadedMsg(
1192                         TheMessage, msg_num, mode,
1193                         headers_only, do_proto, crlf);
1194
1195         CtdlFreeMessage(TheMessage);
1196
1197         return(retcode);
1198 }
1199
1200
1201 /*
1202  * Get a message off disk.  (returns om_* values found in msgbase.h)
1203  * 
1204  */
1205 int CtdlOutputPreLoadedMsg(
1206                 struct CtdlMessage *TheMessage,
1207                 long msg_num,
1208                 int mode,               /* how would you like that message? */
1209                 int headers_only,       /* eschew the message body? */
1210                 int do_proto,           /* do Citadel protocol responses? */
1211                 int crlf                /* Use CRLF newlines instead of LF? */
1212 ) {
1213         int i, k;
1214         char buf[SIZ];
1215         cit_uint8_t ch;
1216         char allkeys[30];
1217         char display_name[256];
1218         char *mptr;
1219         char *nl;       /* newline string */
1220         int suppress_f = 0;
1221         int subject_found = 0;
1222         struct ma_info *ma;
1223
1224         /* Buffers needed for RFC822 translation.  These are all filled
1225          * using functions that are bounds-checked, and therefore we can
1226          * make them substantially smaller than SIZ.
1227          */
1228         char suser[100];
1229         char luser[100];
1230         char fuser[100];
1231         char snode[100];
1232         char lnode[100];
1233         char mid[100];
1234         char datestamp[100];
1235
1236         lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1237                 ((TheMessage == NULL) ? "NULL" : "not null"),
1238                 msg_num,
1239                 mode, headers_only, do_proto, crlf);
1240
1241         snprintf(mid, sizeof mid, "%ld", msg_num);
1242         nl = (crlf ? "\r\n" : "\n");
1243
1244         if (!is_valid_message(TheMessage)) {
1245                 lprintf(CTDL_ERR,
1246                         "ERROR: invalid preloaded message for output\n");
1247                 return(om_no_such_msg);
1248         }
1249
1250         /* Are we downloading a MIME component? */
1251         if (mode == MT_DOWNLOAD) {
1252                 if (TheMessage->cm_format_type != FMT_RFC822) {
1253                         if (do_proto)
1254                                 cprintf("%d This is not a MIME message.\n",
1255                                 ERROR + ILLEGAL_VALUE);
1256                 } else if (CC->download_fp != NULL) {
1257                         if (do_proto) cprintf(
1258                                 "%d You already have a download open.\n",
1259                                 ERROR + RESOURCE_BUSY);
1260                 } else {
1261                         /* Parse the message text component */
1262                         mptr = TheMessage->cm_fields['M'];
1263                         ma = malloc(sizeof(struct ma_info));
1264                         memset(ma, 0, sizeof(struct ma_info));
1265                         mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1266                         free(ma);
1267                         /* If there's no file open by this time, the requested
1268                          * section wasn't found, so print an error
1269                          */
1270                         if (CC->download_fp == NULL) {
1271                                 if (do_proto) cprintf(
1272                                         "%d Section %s not found.\n",
1273                                         ERROR + FILE_NOT_FOUND,
1274                                         CC->download_desired_section);
1275                         }
1276                 }
1277                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1278         }
1279
1280         /* now for the user-mode message reading loops */
1281         if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1282
1283         /* Does the caller want to skip the headers? */
1284         if (headers_only == HEADERS_NONE) goto START_TEXT;
1285
1286         /* Tell the client which format type we're using. */
1287         if ( (mode == MT_CITADEL) && (do_proto) ) {
1288                 cprintf("type=%d\n", TheMessage->cm_format_type);
1289         }
1290
1291         /* nhdr=yes means that we're only displaying headers, no body */
1292         if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1293            && (mode == MT_CITADEL)
1294            && (do_proto)
1295            ) {
1296                 cprintf("nhdr=yes\n");
1297         }
1298
1299         /* begin header processing loop for Citadel message format */
1300
1301         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1302
1303                 safestrncpy(display_name, "<unknown>", sizeof display_name);
1304                 if (TheMessage->cm_fields['A']) {
1305                         strcpy(buf, TheMessage->cm_fields['A']);
1306                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1307                                 safestrncpy(display_name, "****", sizeof display_name);
1308                         }
1309                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1310                                 safestrncpy(display_name, "anonymous", sizeof display_name);
1311                         }
1312                         else {
1313                                 safestrncpy(display_name, buf, sizeof display_name);
1314                         }
1315                         if ((is_room_aide())
1316                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1317                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1318                                 size_t tmp = strlen(display_name);
1319                                 snprintf(&display_name[tmp],
1320                                          sizeof display_name - tmp,
1321                                          " [%s]", buf);
1322                         }
1323                 }
1324
1325                 /* Don't show Internet address for users on the
1326                  * local Citadel network.
1327                  */
1328                 suppress_f = 0;
1329                 if (TheMessage->cm_fields['N'] != NULL)
1330                    if (strlen(TheMessage->cm_fields['N']) > 0)
1331                       if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1332                         suppress_f = 1;
1333                 }
1334                 
1335                 /* Now spew the header fields in the order we like them. */
1336                 safestrncpy(allkeys, FORDER, sizeof allkeys);
1337                 for (i=0; i<strlen(allkeys); ++i) {
1338                         k = (int) allkeys[i];
1339                         if (k != 'M') {
1340                                 if ( (TheMessage->cm_fields[k] != NULL)
1341                                    && (msgkeys[k] != NULL) ) {
1342                                         if (k == 'A') {
1343                                                 if (do_proto) cprintf("%s=%s\n",
1344                                                         msgkeys[k],
1345                                                         display_name);
1346                                         }
1347                                         else if ((k == 'F') && (suppress_f)) {
1348                                                 /* do nothing */
1349                                         }
1350                                         /* Masquerade display name if needed */
1351                                         else {
1352                                                 if (do_proto) cprintf("%s=%s\n",
1353                                                         msgkeys[k],
1354                                                         TheMessage->cm_fields[k]
1355                                         );
1356                                         }
1357                                 }
1358                         }
1359                 }
1360
1361         }
1362
1363         /* begin header processing loop for RFC822 transfer format */
1364
1365         strcpy(suser, "");
1366         strcpy(luser, "");
1367         strcpy(fuser, "");
1368         strcpy(snode, NODENAME);
1369         strcpy(lnode, HUMANNODE);
1370         if (mode == MT_RFC822) {
1371                 for (i = 0; i < 256; ++i) {
1372                         if (TheMessage->cm_fields[i]) {
1373                                 mptr = TheMessage->cm_fields[i];
1374
1375                                 if (i == 'A') {
1376                                         safestrncpy(luser, mptr, sizeof luser);
1377                                         safestrncpy(suser, mptr, sizeof suser);
1378                                 }
1379                                 else if (i == 'U') {
1380                                         cprintf("Subject: %s%s", mptr, nl);
1381                                         subject_found = 1;
1382                                 }
1383                                 else if (i == 'I')
1384                                         safestrncpy(mid, mptr, sizeof mid);
1385                                 else if (i == 'H')
1386                                         safestrncpy(lnode, mptr, sizeof lnode);
1387                                 else if (i == 'F')
1388                                         safestrncpy(fuser, mptr, sizeof fuser);
1389                                 /* else if (i == 'O')
1390                                         cprintf("X-Citadel-Room: %s%s",
1391                                                 mptr, nl); */
1392                                 else if (i == 'N')
1393                                         safestrncpy(snode, mptr, sizeof snode);
1394                                 else if (i == 'R')
1395                                         cprintf("To: %s%s", mptr, nl);
1396                                 else if (i == 'T') {
1397                                         datestring(datestamp, sizeof datestamp,
1398                                                 atol(mptr), DATESTRING_RFC822);
1399                                         cprintf("Date: %s%s", datestamp, nl);
1400                                 }
1401                         }
1402                 }
1403                 if (subject_found == 0) {
1404                         cprintf("Subject: (no subject)%s", nl);
1405                 }
1406         }
1407
1408         for (i=0; i<strlen(suser); ++i) {
1409                 suser[i] = tolower(suser[i]);
1410                 if (!isalnum(suser[i])) suser[i]='_';
1411         }
1412
1413         if (mode == MT_RFC822) {
1414                 if (!strcasecmp(snode, NODENAME)) {
1415                         safestrncpy(snode, FQDN, sizeof snode);
1416                 }
1417
1418                 /* Construct a fun message id */
1419                 cprintf("Message-ID: <%s", mid);
1420                 if (strchr(mid, '@')==NULL) {
1421                         cprintf("@%s", snode);
1422                 }
1423                 cprintf(">%s", nl);
1424
1425                 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1426                         // cprintf("From: x@x.org (----)%s", nl);
1427                         cprintf("From: \"----\" <x@x.org>%s", nl);
1428                 }
1429                 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1430                         // cprintf("From: x@x.org (anonymous)%s", nl);
1431                         cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1432                 }
1433                 else if (strlen(fuser) > 0) {
1434                         // cprintf("From: %s (%s)%s", fuser, luser, nl);
1435                         cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1436                 }
1437                 else {
1438                         // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1439                         cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1440                 }
1441
1442                 cprintf("Organization: %s%s", lnode, nl);
1443
1444                 /* Blank line signifying RFC822 end-of-headers */
1445                 if (TheMessage->cm_format_type != FMT_RFC822) {
1446                         cprintf("%s", nl);
1447                 }
1448         }
1449
1450         /* end header processing loop ... at this point, we're in the text */
1451 START_TEXT:
1452         if (headers_only == HEADERS_FAST) goto DONE;
1453         mptr = TheMessage->cm_fields['M'];
1454
1455         /* Tell the client about the MIME parts in this message */
1456         if (TheMessage->cm_format_type == FMT_RFC822) {
1457                 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1458                         mime_parser(mptr, NULL,
1459                                 (do_proto ? *list_this_part : NULL),
1460                                 (do_proto ? *list_this_pref : NULL),
1461                                 (do_proto ? *list_this_suff : NULL),
1462                                 NULL, 0);
1463                 }
1464                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1465                         /* FIXME ... we have to put some code in here to avoid
1466                          * printing duplicate header information when both
1467                          * Citadel and RFC822 headers exist.  Preference should
1468                          * probably be given to the RFC822 headers.
1469                          */
1470                         int done_rfc822_hdrs = 0;
1471                         while (ch=*(mptr++), ch!=0) {
1472                                 if (ch==13) {
1473                                         /* do nothing */
1474                                 }
1475                                 else if (ch==10) {
1476                                         if (!done_rfc822_hdrs) {
1477                                                 if (headers_only != HEADERS_NONE) {
1478                                                         cprintf("%s", nl);
1479                                                 }
1480                                         }
1481                                         else {
1482                                                 if (headers_only != HEADERS_ONLY) {
1483                                                         cprintf("%s", nl);
1484                                                 }
1485                                         }
1486                                         if ((*(mptr) == 13) || (*(mptr) == 10)) {
1487                                                 done_rfc822_hdrs = 1;
1488                                         }
1489                                 }
1490                                 else {
1491                                         if (done_rfc822_hdrs) {
1492                                                 if (headers_only != HEADERS_NONE) {
1493                                                         cprintf("%c", ch);
1494                                                 }
1495                                         }
1496                                         else {
1497                                                 if (headers_only != HEADERS_ONLY) {
1498                                                         cprintf("%c", ch);
1499                                                 }
1500                                         }
1501                                         if ((*mptr == 13) || (*mptr == 10)) {
1502                                                 done_rfc822_hdrs = 1;
1503                                         }
1504                                 }
1505                         }
1506                         goto DONE;
1507                 }
1508         }
1509
1510         if (headers_only == HEADERS_ONLY) {
1511                 goto DONE;
1512         }
1513
1514         /* signify start of msg text */
1515         if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1516                 if (do_proto) cprintf("text\n");
1517         }
1518
1519         /* If the format type on disk is 1 (fixed-format), then we want
1520          * everything to be output completely literally ... regardless of
1521          * what message transfer format is in use.
1522          */
1523         if (TheMessage->cm_format_type == FMT_FIXED) {
1524                 if (mode == MT_MIME) {
1525                         cprintf("Content-type: text/plain\n\n");
1526                 }
1527                 strcpy(buf, "");
1528                 while (ch = *mptr++, ch > 0) {
1529                         if (ch == 13)
1530                                 ch = 10;
1531                         if ((ch == 10) || (strlen(buf) > 250)) {
1532                                 cprintf("%s%s", buf, nl);
1533                                 strcpy(buf, "");
1534                         } else {
1535                                 buf[strlen(buf) + 1] = 0;
1536                                 buf[strlen(buf)] = ch;
1537                         }
1538                 }
1539                 if (strlen(buf) > 0)
1540                         cprintf("%s%s", buf, nl);
1541         }
1542
1543         /* If the message on disk is format 0 (Citadel vari-format), we
1544          * output using the formatter at 80 columns.  This is the final output
1545          * form if the transfer format is RFC822, but if the transfer format
1546          * is Citadel proprietary, it'll still work, because the indentation
1547          * for new paragraphs is correct and the client will reformat the
1548          * message to the reader's screen width.
1549          */
1550         if (TheMessage->cm_format_type == FMT_CITADEL) {
1551                 if (mode == MT_MIME) {
1552                         cprintf("Content-type: text/x-citadel-variformat\n\n");
1553                 }
1554                 memfmout(80, mptr, 0, nl);
1555         }
1556
1557         /* If the message on disk is format 4 (MIME), we've gotta hand it
1558          * off to the MIME parser.  The client has already been told that
1559          * this message is format 1 (fixed format), so the callback function
1560          * we use will display those parts as-is.
1561          */
1562         if (TheMessage->cm_format_type == FMT_RFC822) {
1563                 ma = malloc(sizeof(struct ma_info));
1564                 memset(ma, 0, sizeof(struct ma_info));
1565
1566                 if (mode == MT_MIME) {
1567                         strcpy(ma->chosen_part, "1");
1568                         mime_parser(mptr, NULL,
1569                                 *choose_preferred, *fixed_output_pre,
1570                                 *fixed_output_post, (void *)ma, 0);
1571                         mime_parser(mptr, NULL,
1572                                 *output_preferred, NULL, NULL, (void *)ma, 0);
1573                 }
1574                 else {
1575                         mime_parser(mptr, NULL,
1576                                 *fixed_output, *fixed_output_pre,
1577                                 *fixed_output_post, (void *)ma, 0);
1578                 }
1579
1580                 free(ma);
1581         }
1582
1583 DONE:   /* now we're done */
1584         if (do_proto) cprintf("000\n");
1585         return(om_ok);
1586 }
1587
1588
1589
1590 /*
1591  * display a message (mode 0 - Citadel proprietary)
1592  */
1593 void cmd_msg0(char *cmdbuf)
1594 {
1595         long msgid;
1596         int headers_only = HEADERS_ALL;
1597
1598         msgid = extract_long(cmdbuf, 0);
1599         headers_only = extract_int(cmdbuf, 1);
1600
1601         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1602         return;
1603 }
1604
1605
1606 /*
1607  * display a message (mode 2 - RFC822)
1608  */
1609 void cmd_msg2(char *cmdbuf)
1610 {
1611         long msgid;
1612         int headers_only = HEADERS_ALL;
1613
1614         msgid = extract_long(cmdbuf, 0);
1615         headers_only = extract_int(cmdbuf, 1);
1616
1617         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1618 }
1619
1620
1621
1622 /* 
1623  * display a message (mode 3 - IGnet raw format - internal programs only)
1624  */
1625 void cmd_msg3(char *cmdbuf)
1626 {
1627         long msgnum;
1628         struct CtdlMessage *msg;
1629         struct ser_ret smr;
1630
1631         if (CC->internal_pgm == 0) {
1632                 cprintf("%d This command is for internal programs only.\n",
1633                         ERROR + HIGHER_ACCESS_REQUIRED);
1634                 return;
1635         }
1636
1637         msgnum = extract_long(cmdbuf, 0);
1638         msg = CtdlFetchMessage(msgnum, 1);
1639         if (msg == NULL) {
1640                 cprintf("%d Message %ld not found.\n", 
1641                         ERROR + MESSAGE_NOT_FOUND, msgnum);
1642                 return;
1643         }
1644
1645         serialize_message(&smr, msg);
1646         CtdlFreeMessage(msg);
1647
1648         if (smr.len == 0) {
1649                 cprintf("%d Unable to serialize message\n",
1650                         ERROR + INTERNAL_ERROR);
1651                 return;
1652         }
1653
1654         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1655         client_write((char *)smr.ser, (int)smr.len);
1656         free(smr.ser);
1657 }
1658
1659
1660
1661 /* 
1662  * Display a message using MIME content types
1663  */
1664 void cmd_msg4(char *cmdbuf)
1665 {
1666         long msgid;
1667
1668         msgid = extract_long(cmdbuf, 0);
1669         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1670 }
1671
1672
1673
1674 /* 
1675  * Client tells us its preferred message format(s)
1676  */
1677 void cmd_msgp(char *cmdbuf)
1678 {
1679         safestrncpy(CC->preferred_formats, cmdbuf,
1680                         sizeof(CC->preferred_formats));
1681         cprintf("%d ok\n", CIT_OK);
1682 }
1683
1684
1685 /*
1686  * Open a component of a MIME message as a download file 
1687  */
1688 void cmd_opna(char *cmdbuf)
1689 {
1690         long msgid;
1691         char desired_section[128];
1692
1693         msgid = extract_long(cmdbuf, 0);
1694         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1695         safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1696         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1697 }                       
1698
1699
1700 /*
1701  * Save a message pointer into a specified room
1702  * (Returns 0 for success, nonzero for failure)
1703  * roomname may be NULL to use the current room
1704  */
1705 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1706         int i;
1707         char hold_rm[ROOMNAMELEN];
1708         struct cdbdata *cdbfr;
1709         int num_msgs;
1710         long *msglist;
1711         long highest_msg = 0L;
1712         struct CtdlMessage *msg = NULL;
1713
1714         lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1715                 roomname, msgid, flags);
1716
1717         strcpy(hold_rm, CC->room.QRname);
1718
1719         /* We may need to check to see if this message is real */
1720         if (  (flags & SM_VERIFY_GOODNESS)
1721            || (flags & SM_DO_REPL_CHECK)
1722            ) {
1723                 msg = CtdlFetchMessage(msgid, 1);
1724                 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1725         }
1726
1727         /* Perform replication checks if necessary */
1728         if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1729
1730                 if (getroom(&CC->room,
1731                    ((roomname != NULL) ? roomname : CC->room.QRname) )
1732                    != 0) {
1733                         lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1734                         if (msg != NULL) CtdlFreeMessage(msg);
1735                         return(ERROR + ROOM_NOT_FOUND);
1736                 }
1737
1738                 if (ReplicationChecks(msg) != 0) {
1739                         getroom(&CC->room, hold_rm);
1740                         if (msg != NULL) CtdlFreeMessage(msg);
1741                         lprintf(CTDL_DEBUG,
1742                                 "Did replication, and newer exists\n");
1743                         return(0);
1744                 }
1745         }
1746
1747         /* Now the regular stuff */
1748         if (lgetroom(&CC->room,
1749            ((roomname != NULL) ? roomname : CC->room.QRname) )
1750            != 0) {
1751                 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1752                 if (msg != NULL) CtdlFreeMessage(msg);
1753                 return(ERROR + ROOM_NOT_FOUND);
1754         }
1755
1756         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1757         if (cdbfr == NULL) {
1758                 msglist = NULL;
1759                 num_msgs = 0;
1760         } else {
1761                 msglist = malloc(cdbfr->len);
1762                 if (msglist == NULL)
1763                         lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1764                 num_msgs = cdbfr->len / sizeof(long);
1765                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1766                 cdb_free(cdbfr);
1767         }
1768
1769
1770         /* Make sure the message doesn't already exist in this room.  It
1771          * is absolutely taboo to have more than one reference to the same
1772          * message in a room.
1773          */
1774         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1775                 if (msglist[i] == msgid) {
1776                         lputroom(&CC->room);    /* unlock the room */
1777                         getroom(&CC->room, hold_rm);
1778                         if (msg != NULL) CtdlFreeMessage(msg);
1779                         free(msglist);
1780                         return(ERROR + ALREADY_EXISTS);
1781                 }
1782         }
1783
1784         /* Now add the new message */
1785         ++num_msgs;
1786         msglist = realloc(msglist, (num_msgs * sizeof(long)));
1787
1788         if (msglist == NULL) {
1789                 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1790         }
1791         msglist[num_msgs - 1] = msgid;
1792
1793         /* Sort the message list, so all the msgid's are in order */
1794         num_msgs = sort_msglist(msglist, num_msgs);
1795
1796         /* Determine the highest message number */
1797         highest_msg = msglist[num_msgs - 1];
1798
1799         /* Write it back to disk. */
1800         cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1801                   msglist, (int)(num_msgs * sizeof(long)));
1802
1803         /* Free up the memory we used. */
1804         free(msglist);
1805
1806         /* Update the highest-message pointer and unlock the room. */
1807         CC->room.QRhighest = highest_msg;
1808         lputroom(&CC->room);
1809         getroom(&CC->room, hold_rm);
1810
1811         /* Bump the reference count for this message. */
1812         if ((flags & SM_DONT_BUMP_REF)==0) {
1813                 AdjRefCount(msgid, +1);
1814         }
1815
1816         /* Return success. */
1817         if (msg != NULL) CtdlFreeMessage(msg);
1818         return (0);
1819 }
1820
1821
1822
1823 /*
1824  * Message base operation to save a new message to the message store
1825  * (returns new message number)
1826  *
1827  * This is the back end for CtdlSubmitMsg() and should not be directly
1828  * called by server-side modules.
1829  *
1830  */
1831 long send_message(struct CtdlMessage *msg) {
1832         long newmsgid;
1833         long retval;
1834         char msgidbuf[256];
1835         struct ser_ret smr;
1836         int is_bigmsg = 0;
1837         char *holdM = NULL;
1838
1839         /* Get a new message number */
1840         newmsgid = get_new_message_number();
1841         snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1842
1843         /* Generate an ID if we don't have one already */
1844         if (msg->cm_fields['I']==NULL) {
1845                 msg->cm_fields['I'] = strdup(msgidbuf);
1846         }
1847
1848         /* If the message is big, set its body aside for storage elsewhere */
1849         if (msg->cm_fields['M'] != NULL) {
1850                 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1851                         is_bigmsg = 1;
1852                         holdM = msg->cm_fields['M'];
1853                         msg->cm_fields['M'] = NULL;
1854                 }
1855         }
1856
1857         /* Serialize our data structure for storage in the database */  
1858         serialize_message(&smr, msg);
1859
1860         if (is_bigmsg) {
1861                 msg->cm_fields['M'] = holdM;
1862         }
1863
1864         if (smr.len == 0) {
1865                 cprintf("%d Unable to serialize message\n",
1866                         ERROR + INTERNAL_ERROR);
1867                 return (-1L);
1868         }
1869
1870         /* Write our little bundle of joy into the message base */
1871         if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1872                       smr.ser, smr.len) < 0) {
1873                 lprintf(CTDL_ERR, "Can't store message\n");
1874                 retval = 0L;
1875         } else {
1876                 if (is_bigmsg) {
1877                         cdb_store(CDB_BIGMSGS,
1878                                 &newmsgid,
1879                                 (int)sizeof(long),
1880                                 holdM,
1881                                 (strlen(holdM) + 1)
1882                         );
1883                 }
1884                 retval = newmsgid;
1885         }
1886
1887         /* Free the memory we used for the serialized message */
1888         free(smr.ser);
1889
1890         /* Return the *local* message ID to the caller
1891          * (even if we're storing an incoming network message)
1892          */
1893         return(retval);
1894 }
1895
1896
1897
1898 /*
1899  * Serialize a struct CtdlMessage into the format used on disk and network.
1900  * 
1901  * This function loads up a "struct ser_ret" (defined in server.h) which
1902  * contains the length of the serialized message and a pointer to the
1903  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
1904  */
1905 void serialize_message(struct ser_ret *ret,             /* return values */
1906                         struct CtdlMessage *msg)        /* unserialized msg */
1907 {
1908         size_t wlen;
1909         int i;
1910         static char *forder = FORDER;
1911
1912         if (is_valid_message(msg) == 0) return;         /* self check */
1913
1914         ret->len = 3;
1915         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1916                 ret->len = ret->len +
1917                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
1918
1919         lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1920         ret->ser = malloc(ret->len);
1921         if (ret->ser == NULL) {
1922                 ret->len = 0;
1923                 return;
1924         }
1925
1926         ret->ser[0] = 0xFF;
1927         ret->ser[1] = msg->cm_anon_type;
1928         ret->ser[2] = msg->cm_format_type;
1929         wlen = 3;
1930
1931         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1932                 ret->ser[wlen++] = (char)forder[i];
1933                 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1934                 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1935         }
1936         if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1937                 (long)ret->len, (long)wlen);
1938
1939         return;
1940 }
1941
1942
1943
1944 /*
1945  * Back end for the ReplicationChecks() function
1946  */
1947 void check_repl(long msgnum, void *userdata) {
1948         lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1949         CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1950 }
1951
1952
1953 /*
1954  * Check to see if any messages already exist which carry the same Exclusive ID
1955  * as this one.  If any are found, delete them.
1956  *
1957  */
1958 int ReplicationChecks(struct CtdlMessage *msg) {
1959         struct CtdlMessage *template;
1960         int abort_this = 0;
1961
1962         /* No exclusive id?  Don't do anything. */
1963         if (msg->cm_fields['E'] == NULL) return 0;
1964         if (strlen(msg->cm_fields['E']) == 0) return 0;
1965         lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1966
1967         template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1968         memset(template, 0, sizeof(struct CtdlMessage));
1969         template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1970
1971         CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1972
1973         CtdlFreeMessage(template);
1974         lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1975         return(abort_this);
1976 }
1977
1978
1979
1980
1981 /*
1982  * Save a message to disk and submit it into the delivery system.
1983  */
1984 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
1985                 struct recptypes *recps,        /* recipients (if mail) */
1986                 char *force                     /* force a particular room? */
1987 ) {
1988         char submit_filename[128];
1989         char generated_timestamp[32];
1990         char hold_rm[ROOMNAMELEN];
1991         char actual_rm[ROOMNAMELEN];
1992         char force_room[ROOMNAMELEN];
1993         char content_type[SIZ];                 /* We have to learn this */
1994         char recipient[SIZ];
1995         long newmsgid;
1996         char *mptr = NULL;
1997         struct ctdluser userbuf;
1998         int a, i;
1999         struct MetaData smi;
2000         FILE *network_fp = NULL;
2001         static int seqnum = 1;
2002         struct CtdlMessage *imsg = NULL;
2003         char *instr;
2004         struct ser_ret smr;
2005         char *hold_R, *hold_D;
2006
2007         lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2008         if (is_valid_message(msg) == 0) return(-1);     /* self check */
2009
2010         /* If this message has no timestamp, we take the liberty of
2011          * giving it one, right now.
2012          */
2013         if (msg->cm_fields['T'] == NULL) {
2014                 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2015                 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2016                 msg->cm_fields['T'] = strdup(generated_timestamp);
2017         }
2018
2019         /* If this message has no path, we generate one.
2020          */
2021         if (msg->cm_fields['P'] == NULL) {
2022                 lprintf(CTDL_DEBUG, "Generating path\n");
2023                 if (msg->cm_fields['A'] != NULL) {
2024                         msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2025                         for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2026                                 if (isspace(msg->cm_fields['P'][a])) {
2027                                         msg->cm_fields['P'][a] = ' ';
2028                                 }
2029                         }
2030                 }
2031                 else {
2032                         msg->cm_fields['P'] = strdup("unknown");
2033                 }
2034         }
2035
2036         if (force == NULL) {
2037                 strcpy(force_room, "");
2038         }
2039         else {
2040                 strcpy(force_room, force);
2041         }
2042
2043         /* Learn about what's inside, because it's what's inside that counts */
2044         lprintf(CTDL_DEBUG, "Learning what's inside\n");
2045         if (msg->cm_fields['M'] == NULL) {
2046                 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2047                 return(-2);
2048         }
2049
2050         switch (msg->cm_format_type) {
2051         case 0:
2052                 strcpy(content_type, "text/x-citadel-variformat");
2053                 break;
2054         case 1:
2055                 strcpy(content_type, "text/plain");
2056                 break;
2057         case 4:
2058                 strcpy(content_type, "text/plain");
2059                 mptr = bmstrstr(msg->cm_fields['M'],
2060                                 "Content-type: ", strncasecmp);
2061                 if (mptr != NULL) {
2062                         safestrncpy(content_type, &mptr[14], 
2063                                         sizeof content_type);
2064                         for (a = 0; a < strlen(content_type); ++a) {
2065                                 if ((content_type[a] == ';')
2066                                     || (content_type[a] == ' ')
2067                                     || (content_type[a] == 13)
2068                                     || (content_type[a] == 10)) {
2069                                         content_type[a] = 0;
2070                                 }
2071                         }
2072                 }
2073         }
2074
2075         /* Goto the correct room */
2076         lprintf(CTDL_DEBUG, "Selected room %s\n",
2077                 (recps) ? CC->room.QRname : SENTITEMS);
2078         strcpy(hold_rm, CC->room.QRname);
2079         strcpy(actual_rm, CC->room.QRname);
2080         if (recps != NULL) {
2081                 strcpy(actual_rm, SENTITEMS);
2082         }
2083
2084         /* If the user is a twit, move to the twit room for posting */
2085         lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2086                         (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2087         if (TWITDETECT) {
2088                 if (CC->user.axlevel == 2) {
2089                         strcpy(hold_rm, actual_rm);
2090                         strcpy(actual_rm, config.c_twitroom);
2091                 }
2092         }
2093
2094         /* ...or if this message is destined for Aide> then go there. */
2095         if (strlen(force_room) > 0) {
2096                 strcpy(actual_rm, force_room);
2097         }
2098
2099         lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2100         if (strcasecmp(actual_rm, CC->room.QRname)) {
2101                 /* getroom(&CC->room, actual_rm); */
2102                 usergoto(actual_rm, 0, 1, NULL, NULL);
2103         }
2104
2105         /*
2106          * If this message has no O (room) field, generate one.
2107          */
2108         if (msg->cm_fields['O'] == NULL) {
2109                 msg->cm_fields['O'] = strdup(CC->room.QRname);
2110         }
2111
2112         /* Perform "before save" hooks (aborting if any return nonzero) */
2113         lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2114         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2115
2116         /* If this message has an Exclusive ID, perform replication checks */
2117         lprintf(CTDL_DEBUG, "Performing replication checks\n");
2118         if (ReplicationChecks(msg) > 0) return(-4);
2119
2120         /* Save it to disk */
2121         lprintf(CTDL_DEBUG, "Saving to disk\n");
2122         newmsgid = send_message(msg);
2123         if (newmsgid <= 0L) return(-5);
2124
2125         /* Write a supplemental message info record.  This doesn't have to
2126          * be a critical section because nobody else knows about this message
2127          * yet.
2128          */
2129         lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2130         memset(&smi, 0, sizeof(struct MetaData));
2131         smi.meta_msgnum = newmsgid;
2132         smi.meta_refcount = 0;
2133         safestrncpy(smi.meta_content_type, content_type,
2134                         sizeof smi.meta_content_type);
2135
2136         /* As part of the new metadata record, measure how
2137          * big this message will be when displayed as RFC822.
2138          * Both POP and IMAP use this, and it's best to just take the hit now
2139          * instead of having to potentially measure thousands of messages when
2140          * a mailbox is opened later.
2141          */
2142
2143         if (CC->redirect_buffer != NULL) {
2144                 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2145                 abort();
2146         }
2147         CC->redirect_buffer = malloc(SIZ);
2148         CC->redirect_len = 0;
2149         CC->redirect_alloc = SIZ;
2150         CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2151         smi.meta_rfc822_length = CC->redirect_len;
2152         free(CC->redirect_buffer);
2153         CC->redirect_buffer = NULL;
2154         CC->redirect_len = 0;
2155         CC->redirect_alloc = 0;
2156
2157         PutMetaData(&smi);
2158
2159         /* Now figure out where to store the pointers */
2160         lprintf(CTDL_DEBUG, "Storing pointers\n");
2161
2162         /* If this is being done by the networker delivering a private
2163          * message, we want to BYPASS saving the sender's copy (because there
2164          * is no local sender; it would otherwise go to the Trashcan).
2165          */
2166         if ((!CC->internal_pgm) || (recps == NULL)) {
2167                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2168                         lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2169                         CtdlSaveMsgPointerInRoom(config.c_aideroom,
2170                                                         newmsgid, 0);
2171                 }
2172         }
2173
2174         /* For internet mail, drop a copy in the outbound queue room */
2175         if (recps != NULL)
2176          if (recps->num_internet > 0) {
2177                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2178         }
2179
2180         /* If other rooms are specified, drop them there too. */
2181         if (recps != NULL)
2182          if (recps->num_room > 0)
2183           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2184                 extract_token(recipient, recps->recp_room, i,
2185                                         '|', sizeof recipient);
2186                 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2187                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2188         }
2189
2190         /* Bump this user's messages posted counter. */
2191         lprintf(CTDL_DEBUG, "Updating user\n");
2192         lgetuser(&CC->user, CC->curr_user);
2193         CC->user.posted = CC->user.posted + 1;
2194         lputuser(&CC->user);
2195
2196         /* If this is private, local mail, make a copy in the
2197          * recipient's mailbox and bump the reference count.
2198          */
2199         if (recps != NULL)
2200          if (recps->num_local > 0)
2201           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2202                 extract_token(recipient, recps->recp_local, i,
2203                                         '|', sizeof recipient);
2204                 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2205                         recipient);
2206                 if (getuser(&userbuf, recipient) == 0) {
2207                         MailboxName(actual_rm, sizeof actual_rm,
2208                                         &userbuf, MAILROOM);
2209                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2210                         BumpNewMailCounter(userbuf.usernum);
2211                 }
2212                 else {
2213                         lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2214                         CtdlSaveMsgPointerInRoom(config.c_aideroom,
2215                                                         newmsgid, 0);
2216                 }
2217         }
2218
2219         /* Perform "after save" hooks */
2220         lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2221         PerformMessageHooks(msg, EVT_AFTERSAVE);
2222
2223         /* For IGnet mail, we have to save a new copy into the spooler for
2224          * each recipient, with the R and D fields set to the recipient and
2225          * destination-node.  This has two ugly side effects: all other
2226          * recipients end up being unlisted in this recipient's copy of the
2227          * message, and it has to deliver multiple messages to the same
2228          * node.  We'll revisit this again in a year or so when everyone has
2229          * a network spool receiver that can handle the new style messages.
2230          */
2231         if (recps != NULL)
2232          if (recps->num_ignet > 0)
2233           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2234                 extract_token(recipient, recps->recp_ignet, i,
2235                                 '|', sizeof recipient);
2236
2237                 hold_R = msg->cm_fields['R'];
2238                 hold_D = msg->cm_fields['D'];
2239                 msg->cm_fields['R'] = malloc(SIZ);
2240                 msg->cm_fields['D'] = malloc(128);
2241                 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2242                 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2243                 
2244                 serialize_message(&smr, msg);
2245                 if (smr.len > 0) {
2246                         snprintf(submit_filename, sizeof submit_filename,
2247                                 "./network/spoolin/netmail.%04lx.%04x.%04x",
2248                                 (long) getpid(), CC->cs_pid, ++seqnum);
2249                         network_fp = fopen(submit_filename, "wb+");
2250                         if (network_fp != NULL) {
2251                                 fwrite(smr.ser, smr.len, 1, network_fp);
2252                                 fclose(network_fp);
2253                         }
2254                         free(smr.ser);
2255                 }
2256
2257                 free(msg->cm_fields['R']);
2258                 free(msg->cm_fields['D']);
2259                 msg->cm_fields['R'] = hold_R;
2260                 msg->cm_fields['D'] = hold_D;
2261         }
2262
2263         /* Go back to the room we started from */
2264         lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2265         if (strcasecmp(hold_rm, CC->room.QRname))
2266                 /* getroom(&CC->room, hold_rm); */
2267                 usergoto(hold_rm, 0, 1, NULL, NULL);
2268
2269         /* For internet mail, generate delivery instructions.
2270          * Yes, this is recursive.  Deal with it.  Infinite recursion does
2271          * not happen because the delivery instructions message does not
2272          * contain a recipient.
2273          */
2274         if (recps != NULL)
2275          if (recps->num_internet > 0) {
2276                 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2277                 instr = malloc(SIZ * 2);
2278                 snprintf(instr, SIZ * 2,
2279                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2280                         "bounceto|%s@%s\n",
2281                         SPOOLMIME, newmsgid, (long)time(NULL),
2282                         msg->cm_fields['A'], msg->cm_fields['N']
2283                 );
2284
2285                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2286                         size_t tmp = strlen(instr);
2287                         extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2288                         snprintf(&instr[tmp], SIZ * 2 - tmp,
2289                                  "remote|%s|0||\n", recipient);
2290                 }
2291
2292                 imsg = malloc(sizeof(struct CtdlMessage));
2293                 memset(imsg, 0, sizeof(struct CtdlMessage));
2294                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2295                 imsg->cm_anon_type = MES_NORMAL;
2296                 imsg->cm_format_type = FMT_RFC822;
2297                 imsg->cm_fields['A'] = strdup("Citadel");
2298                 imsg->cm_fields['M'] = instr;
2299                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2300                 CtdlFreeMessage(imsg);
2301         }
2302
2303         return(newmsgid);
2304 }
2305
2306
2307
2308 /*
2309  * Convenience function for generating small administrative messages.
2310  */
2311 void quickie_message(char *from, char *to, char *room, char *text, 
2312                         int format_type, char *subject)
2313 {
2314         struct CtdlMessage *msg;
2315         struct recptypes *recp = NULL;
2316
2317         msg = malloc(sizeof(struct CtdlMessage));
2318         memset(msg, 0, sizeof(struct CtdlMessage));
2319         msg->cm_magic = CTDLMESSAGE_MAGIC;
2320         msg->cm_anon_type = MES_NORMAL;
2321         msg->cm_format_type = format_type;
2322         msg->cm_fields['A'] = strdup(from);
2323         if (room != NULL) msg->cm_fields['O'] = strdup(room);
2324         msg->cm_fields['N'] = strdup(NODENAME);
2325         if (to != NULL) {
2326                 msg->cm_fields['R'] = strdup(to);
2327                 recp = validate_recipients(to);
2328         }
2329         if (subject != NULL) {
2330                 msg->cm_fields['U'] = strdup(subject);
2331         }
2332         msg->cm_fields['M'] = strdup(text);
2333
2334         CtdlSubmitMsg(msg, recp, room);
2335         CtdlFreeMessage(msg);
2336         if (recp != NULL) free(recp);
2337 }
2338
2339
2340
2341 /*
2342  * Back end function used by CtdlMakeMessage() and similar functions
2343  */
2344 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
2345                         size_t maxlen,          /* maximum message length */
2346                         char *exist,            /* if non-null, append to it;
2347                                                    exist is ALWAYS freed  */
2348                         int crlf                /* CRLF newlines instead of LF */
2349                         ) {
2350         char buf[1024];
2351         int linelen;
2352         size_t message_len = 0;
2353         size_t buffer_len = 0;
2354         char *ptr;
2355         char *m;
2356         int flushing = 0;
2357         int finished = 0;
2358
2359         if (exist == NULL) {
2360                 m = malloc(4096);
2361                 m[0] = 0;
2362                 buffer_len = 4096;
2363                 message_len = 0;
2364         }
2365         else {
2366                 message_len = strlen(exist);
2367                 buffer_len = message_len + 4096;
2368                 m = realloc(exist, buffer_len);
2369                 if (m == NULL) {
2370                         free(exist);
2371                         return m;
2372                 }
2373         }
2374
2375         /* flush the input if we have nowhere to store it */
2376         if (m == NULL) {
2377                 flushing = 1;
2378         }
2379
2380         /* read in the lines of message text one by one */
2381         do {
2382                 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2383                 if (!strcmp(buf, terminator)) finished = 1;
2384                 if (crlf) {
2385                         strcat(buf, "\r\n");
2386                 }
2387                 else {
2388                         strcat(buf, "\n");
2389                 }
2390
2391                 if ( (!flushing) && (!finished) ) {
2392                         /* Measure the line */
2393                         linelen = strlen(buf);
2394         
2395                         /* augment the buffer if we have to */
2396                         if ((message_len + linelen) >= buffer_len) {
2397                                 ptr = realloc(m, (buffer_len * 2) );
2398                                 if (ptr == NULL) {      /* flush if can't allocate */
2399                                         flushing = 1;
2400                                 } else {
2401                                         buffer_len = (buffer_len * 2);
2402                                         m = ptr;
2403                                         lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2404                                 }
2405                         }
2406         
2407                         /* Add the new line to the buffer.  NOTE: this loop must avoid
2408                         * using functions like strcat() and strlen() because they
2409                         * traverse the entire buffer upon every call, and doing that
2410                         * for a multi-megabyte message slows it down beyond usability.
2411                         */
2412                         strcpy(&m[message_len], buf);
2413                         message_len += linelen;
2414                 }
2415
2416                 /* if we've hit the max msg length, flush the rest */
2417                 if (message_len >= maxlen) flushing = 1;
2418
2419         } while (!finished);
2420         return(m);
2421 }
2422
2423
2424
2425
2426 /*
2427  * Build a binary message to be saved on disk.
2428  * (NOTE: if you supply 'preformatted_text', the buffer you give it
2429  * will become part of the message.  This means you are no longer
2430  * responsible for managing that memory -- it will be freed along with
2431  * the rest of the fields when CtdlFreeMessage() is called.)
2432  */
2433
2434 struct CtdlMessage *CtdlMakeMessage(
2435         struct ctdluser *author,        /* author's user structure */
2436         char *recipient,                /* NULL if it's not mail */
2437         char *room,                     /* room where it's going */
2438         int type,                       /* see MES_ types in header file */
2439         int format_type,                /* variformat, plain text, MIME... */
2440         char *fake_name,                /* who we're masquerading as */
2441         char *subject,                  /* Subject (optional) */
2442         char *preformatted_text         /* ...or NULL to read text from client */
2443 ) {
2444         char dest_node[SIZ];
2445         char buf[SIZ];
2446         struct CtdlMessage *msg;
2447
2448         msg = malloc(sizeof(struct CtdlMessage));
2449         memset(msg, 0, sizeof(struct CtdlMessage));
2450         msg->cm_magic = CTDLMESSAGE_MAGIC;
2451         msg->cm_anon_type = type;
2452         msg->cm_format_type = format_type;
2453
2454         /* Don't confuse the poor folks if it's not routed mail. */
2455         strcpy(dest_node, "");
2456
2457         striplt(recipient);
2458
2459         snprintf(buf, sizeof buf, "cit%ld", author->usernum);   /* Path */
2460         msg->cm_fields['P'] = strdup(buf);
2461
2462         snprintf(buf, sizeof buf, "%ld", (long)time(NULL));     /* timestamp */
2463         msg->cm_fields['T'] = strdup(buf);
2464
2465         if (fake_name[0])                                       /* author */
2466                 msg->cm_fields['A'] = strdup(fake_name);
2467         else
2468                 msg->cm_fields['A'] = strdup(author->fullname);
2469
2470         if (CC->room.QRflags & QR_MAILBOX) {            /* room */
2471                 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2472         }
2473         else {
2474                 msg->cm_fields['O'] = strdup(CC->room.QRname);
2475         }
2476
2477         msg->cm_fields['N'] = strdup(NODENAME);         /* nodename */
2478         msg->cm_fields['H'] = strdup(HUMANNODE);                /* hnodename */
2479
2480         if (recipient[0] != 0) {
2481                 msg->cm_fields['R'] = strdup(recipient);
2482         }
2483         if (dest_node[0] != 0) {
2484                 msg->cm_fields['D'] = strdup(dest_node);
2485         }
2486
2487         if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2488                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2489         }
2490
2491         if (subject != NULL) {
2492                 striplt(subject);
2493                 if (strlen(subject) > 0) {
2494                         msg->cm_fields['U'] = strdup(subject);
2495                 }
2496         }
2497
2498         if (preformatted_text != NULL) {
2499                 msg->cm_fields['M'] = preformatted_text;
2500         }
2501         else {
2502                 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2503                                         config.c_maxmsglen, NULL, 0);
2504         }
2505
2506         return(msg);
2507 }
2508
2509
2510 /*
2511  * Check to see whether we have permission to post a message in the current
2512  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2513  * returns 0 on success.
2514  */
2515 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2516
2517         if (!(CC->logged_in)) {
2518                 snprintf(errmsgbuf, n, "Not logged in.");
2519                 return (ERROR + NOT_LOGGED_IN);
2520         }
2521
2522         if ((CC->user.axlevel < 2)
2523             && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2524                 snprintf(errmsgbuf, n, "Need to be validated to enter "
2525                                 "(except in %s> to sysop)", MAILROOM);
2526                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2527         }
2528
2529         if ((CC->user.axlevel < 4)
2530            && (CC->room.QRflags & QR_NETWORK)) {
2531                 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2532                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2533         }
2534
2535         if ((CC->user.axlevel < 6)
2536            && (CC->room.QRflags & QR_READONLY)) {
2537                 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2538                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2539         }
2540
2541         strcpy(errmsgbuf, "Ok");
2542         return(0);
2543 }
2544
2545
2546 /*
2547  * Check to see if the specified user has Internet mail permission
2548  * (returns nonzero if permission is granted)
2549  */
2550 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2551
2552         /* Do not allow twits to send Internet mail */
2553         if (who->axlevel <= 2) return(0);
2554
2555         /* Globally enabled? */
2556         if (config.c_restrict == 0) return(1);
2557
2558         /* User flagged ok? */
2559         if (who->flags & US_INTERNET) return(2);
2560
2561         /* Aide level access? */
2562         if (who->axlevel >= 6) return(3);
2563
2564         /* No mail for you! */
2565         return(0);
2566 }
2567
2568
2569
2570 /*
2571  * Validate recipients, count delivery types and errors, and handle aliasing
2572  * FIXME check for dupes!!!!!
2573  */
2574 struct recptypes *validate_recipients(char *recipients) {
2575         struct recptypes *ret;
2576         char this_recp[SIZ];
2577         char this_recp_cooked[SIZ];
2578         char append[SIZ];
2579         int num_recps;
2580         int i, j;
2581         int mailtype;
2582         int invalid;
2583         struct ctdluser tempUS;
2584         struct ctdlroom tempQR;
2585
2586         /* Initialize */
2587         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2588         if (ret == NULL) return(NULL);
2589         memset(ret, 0, sizeof(struct recptypes));
2590
2591         ret->num_local = 0;
2592         ret->num_internet = 0;
2593         ret->num_ignet = 0;
2594         ret->num_error = 0;
2595         ret->num_room = 0;
2596
2597         if (recipients == NULL) {
2598                 num_recps = 0;
2599         }
2600         else if (strlen(recipients) == 0) {
2601                 num_recps = 0;
2602         }
2603         else {
2604                 /* Change all valid separator characters to commas */
2605                 for (i=0; i<strlen(recipients); ++i) {
2606                         if ((recipients[i] == ';') || (recipients[i] == '|')) {
2607                                 recipients[i] = ',';
2608                         }
2609                 }
2610
2611                 /* Count 'em up */
2612                 num_recps = num_tokens(recipients, ',');
2613         }
2614
2615         if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2616                 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2617                 striplt(this_recp);
2618                 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2619                 mailtype = alias(this_recp);
2620                 mailtype = alias(this_recp);
2621                 mailtype = alias(this_recp);
2622                 for (j=0; j<=strlen(this_recp); ++j) {
2623                         if (this_recp[j]=='_') {
2624                                 this_recp_cooked[j] = ' ';
2625                         }
2626                         else {
2627                                 this_recp_cooked[j] = this_recp[j];
2628                         }
2629                 }
2630                 invalid = 0;
2631                 switch(mailtype) {
2632                         case MES_LOCAL:
2633                                 if (!strcasecmp(this_recp, "sysop")) {
2634                                         ++ret->num_room;
2635                                         strcpy(this_recp, config.c_aideroom);
2636                                         if (strlen(ret->recp_room) > 0) {
2637                                                 strcat(ret->recp_room, "|");
2638                                         }
2639                                         strcat(ret->recp_room, this_recp);
2640                                 }
2641                                 else if (getuser(&tempUS, this_recp) == 0) {
2642                                         ++ret->num_local;
2643                                         strcpy(this_recp, tempUS.fullname);
2644                                         if (strlen(ret->recp_local) > 0) {
2645                                                 strcat(ret->recp_local, "|");
2646                                         }
2647                                         strcat(ret->recp_local, this_recp);
2648                                 }
2649                                 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2650                                         ++ret->num_local;
2651                                         strcpy(this_recp, tempUS.fullname);
2652                                         if (strlen(ret->recp_local) > 0) {
2653                                                 strcat(ret->recp_local, "|");
2654                                         }
2655                                         strcat(ret->recp_local, this_recp);
2656                                 }
2657                                 else if ( (!strncasecmp(this_recp, "room_", 5))
2658                                       && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2659                                         ++ret->num_room;
2660                                         if (strlen(ret->recp_room) > 0) {
2661                                                 strcat(ret->recp_room, "|");
2662                                         }
2663                                         strcat(ret->recp_room, &this_recp_cooked[5]);
2664                                 }
2665                                 else {
2666                                         ++ret->num_error;
2667                                         invalid = 1;
2668                                 }
2669                                 break;
2670                         case MES_INTERNET:
2671                                 /* Yes, you're reading this correctly: if the target
2672                                  * domain points back to the local system or an attached
2673                                  * Citadel directory, the address is invalid.  That's
2674                                  * because if the address were valid, we would have
2675                                  * already translated it to a local address by now.
2676                                  */
2677                                 if (IsDirectory(this_recp)) {
2678                                         ++ret->num_error;
2679                                         invalid = 1;
2680                                 }
2681                                 else {
2682                                         ++ret->num_internet;
2683                                         if (strlen(ret->recp_internet) > 0) {
2684                                                 strcat(ret->recp_internet, "|");
2685                                         }
2686                                         strcat(ret->recp_internet, this_recp);
2687                                 }
2688                                 break;
2689                         case MES_IGNET:
2690                                 ++ret->num_ignet;
2691                                 if (strlen(ret->recp_ignet) > 0) {
2692                                         strcat(ret->recp_ignet, "|");
2693                                 }
2694                                 strcat(ret->recp_ignet, this_recp);
2695                                 break;
2696                         case MES_ERROR:
2697                                 ++ret->num_error;
2698                                 invalid = 1;
2699                                 break;
2700                 }
2701                 if (invalid) {
2702                         if (strlen(ret->errormsg) == 0) {
2703                                 snprintf(append, sizeof append,
2704                                          "Invalid recipient: %s",
2705                                          this_recp);
2706                         }
2707                         else {
2708                                 snprintf(append, sizeof append,
2709                                          ", %s", this_recp);
2710                         }
2711                         if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2712                                 strcat(ret->errormsg, append);
2713                         }
2714                 }
2715                 else {
2716                         if (strlen(ret->display_recp) == 0) {
2717                                 strcpy(append, this_recp);
2718                         }
2719                         else {
2720                                 snprintf(append, sizeof append, ", %s",
2721                                          this_recp);
2722                         }
2723                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2724                                 strcat(ret->display_recp, append);
2725                         }
2726                 }
2727         }
2728
2729         if ((ret->num_local + ret->num_internet + ret->num_ignet +
2730            ret->num_room + ret->num_error) == 0) {
2731                 ++ret->num_error;
2732                 strcpy(ret->errormsg, "No recipients specified.");
2733         }
2734
2735         lprintf(CTDL_DEBUG, "validate_recipients()\n");
2736         lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2737         lprintf(CTDL_DEBUG, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
2738         lprintf(CTDL_DEBUG, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2739         lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2740         lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2741
2742         return(ret);
2743 }
2744
2745
2746
2747 /*
2748  * message entry  -  mode 0 (normal)
2749  */
2750 void cmd_ent0(char *entargs)
2751 {
2752         int post = 0;
2753         char recp[SIZ];
2754         char masquerade_as[SIZ];
2755         int anon_flag = 0;
2756         int format_type = 0;
2757         char newusername[SIZ];
2758         struct CtdlMessage *msg;
2759         int anonymous = 0;
2760         char errmsg[SIZ];
2761         int err = 0;
2762         struct recptypes *valid = NULL;
2763         char subject[SIZ];
2764         int do_confirm = 0;
2765         long msgnum;
2766
2767         unbuffer_output();
2768
2769         post = extract_int(entargs, 0);
2770         extract_token(recp, entargs, 1, '|', sizeof recp);
2771         anon_flag = extract_int(entargs, 2);
2772         format_type = extract_int(entargs, 3);
2773         extract_token(subject, entargs, 4, '|', sizeof subject);
2774         do_confirm = extract_int(entargs, 6);
2775
2776         /* first check to make sure the request is valid. */
2777
2778         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2779         if (err) {
2780                 cprintf("%d %s\n", err, errmsg);
2781                 return;
2782         }
2783
2784         /* Check some other permission type things. */
2785
2786         if (post == 2) {
2787                 if (CC->user.axlevel < 6) {
2788                         cprintf("%d You don't have permission to masquerade.\n",
2789                                 ERROR + HIGHER_ACCESS_REQUIRED);
2790                         return;
2791                 }
2792                 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2793                 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2794                 safestrncpy(CC->fake_postname, newusername,
2795                         sizeof(CC->fake_postname) );
2796                 cprintf("%d ok\n", CIT_OK);
2797                 return;
2798         }
2799         CC->cs_flags |= CS_POSTING;
2800
2801         /* In the Mail> room we have to behave a little differently --
2802          * make sure the user has specified at least one recipient.  Then
2803          * validate the recipient(s).
2804          */
2805         if ( (CC->room.QRflags & QR_MAILBOX)
2806            && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2807
2808                 if (CC->user.axlevel < 2) {
2809                         strcpy(recp, "sysop");
2810                 }
2811
2812                 valid = validate_recipients(recp);
2813                 if (valid->num_error > 0) {
2814                         cprintf("%d %s\n",
2815                                 ERROR + NO_SUCH_USER, valid->errormsg);
2816                         free(valid);
2817                         return;
2818                 }
2819                 if (valid->num_internet > 0) {
2820                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2821                                 cprintf("%d You do not have permission "
2822                                         "to send Internet mail.\n",
2823                                         ERROR + HIGHER_ACCESS_REQUIRED);
2824                                 free(valid);
2825                                 return;
2826                         }
2827                 }
2828
2829                 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2830                    && (CC->user.axlevel < 4) ) {
2831                         cprintf("%d Higher access required for network mail.\n",
2832                                 ERROR + HIGHER_ACCESS_REQUIRED);
2833                         free(valid);
2834                         return;
2835                 }
2836         
2837                 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2838                     && ((CC->user.flags & US_INTERNET) == 0)
2839                     && (!CC->internal_pgm)) {
2840                         cprintf("%d You don't have access to Internet mail.\n",
2841                                 ERROR + HIGHER_ACCESS_REQUIRED);
2842                         free(valid);
2843                         return;
2844                 }
2845
2846         }
2847
2848         /* Is this a room which has anonymous-only or anonymous-option? */
2849         anonymous = MES_NORMAL;
2850         if (CC->room.QRflags & QR_ANONONLY) {
2851                 anonymous = MES_ANONONLY;
2852         }
2853         if (CC->room.QRflags & QR_ANONOPT) {
2854                 if (anon_flag == 1) {   /* only if the user requested it */
2855                         anonymous = MES_ANONOPT;
2856                 }
2857         }
2858
2859         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2860                 recp[0] = 0;
2861         }
2862
2863         /* If we're only checking the validity of the request, return
2864          * success without creating the message.
2865          */
2866         if (post == 0) {
2867                 cprintf("%d %s\n", CIT_OK,
2868                         ((valid != NULL) ? valid->display_recp : "") );
2869                 free(valid);
2870                 return;
2871         }
2872
2873         /* Handle author masquerading */
2874         if (CC->fake_postname[0]) {
2875                 strcpy(masquerade_as, CC->fake_postname);
2876         }
2877         else if (CC->fake_username[0]) {
2878                 strcpy(masquerade_as, CC->fake_username);
2879         }
2880         else {
2881                 strcpy(masquerade_as, "");
2882         }
2883
2884         /* Read in the message from the client. */
2885         if (do_confirm) {
2886                 cprintf("%d send message\n", START_CHAT_MODE);
2887         } else {
2888                 cprintf("%d send message\n", SEND_LISTING);
2889         }
2890         msg = CtdlMakeMessage(&CC->user, recp,
2891                 CC->room.QRname, anonymous, format_type,
2892                 masquerade_as, subject, NULL);
2893
2894         if (msg != NULL) {
2895                 msgnum = CtdlSubmitMsg(msg, valid, "");
2896
2897                 if (do_confirm) {
2898                         cprintf("%ld\n", msgnum);
2899                         if (msgnum >= 0L) {
2900                                 cprintf("Message accepted.\n");
2901                         }
2902                         else {
2903                                 cprintf("Internal error.\n");
2904                         }
2905                         if (msg->cm_fields['E'] != NULL) {
2906                                 cprintf("%s\n", msg->cm_fields['E']);
2907                         } else {
2908                                 cprintf("\n");
2909                         }
2910                         cprintf("000\n");
2911                 }
2912
2913                 CtdlFreeMessage(msg);
2914         }
2915         CC->fake_postname[0] = '\0';
2916         free(valid);
2917         return;
2918 }
2919
2920
2921
2922 /*
2923  * API function to delete messages which match a set of criteria
2924  * (returns the actual number of messages deleted)
2925  */
2926 int CtdlDeleteMessages(char *room_name,         /* which room */
2927                        long dmsgnum,            /* or "0" for any */
2928                        char *content_type       /* or "" for any */
2929 )
2930 {
2931
2932         struct ctdlroom qrbuf;
2933         struct cdbdata *cdbfr;
2934         long *msglist = NULL;
2935         long *dellist = NULL;
2936         int num_msgs = 0;
2937         int i;
2938         int num_deleted = 0;
2939         int delete_this;
2940         struct MetaData smi;
2941
2942         lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2943                 room_name, dmsgnum, content_type);
2944
2945         /* get room record, obtaining a lock... */
2946         if (lgetroom(&qrbuf, room_name) != 0) {
2947                 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2948                         room_name);
2949                 return (0);     /* room not found */
2950         }
2951         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2952
2953         if (cdbfr != NULL) {
2954                 msglist = malloc(cdbfr->len);
2955                 dellist = malloc(cdbfr->len);
2956                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2957                 num_msgs = cdbfr->len / sizeof(long);
2958                 cdb_free(cdbfr);
2959         }
2960         if (num_msgs > 0) {
2961                 for (i = 0; i < num_msgs; ++i) {
2962                         delete_this = 0x00;
2963
2964                         /* Set/clear a bit for each criterion */
2965
2966                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2967                                 delete_this |= 0x01;
2968                         }
2969                         if (strlen(content_type) == 0) {
2970                                 delete_this |= 0x02;
2971                         } else {
2972                                 GetMetaData(&smi, msglist[i]);
2973                                 if (!strcasecmp(smi.meta_content_type,
2974                                                 content_type)) {
2975                                         delete_this |= 0x02;
2976                                 }
2977                         }
2978
2979                         /* Delete message only if all bits are set */
2980                         if (delete_this == 0x03) {
2981                                 dellist[num_deleted++] = msglist[i];
2982                                 msglist[i] = 0L;
2983                         }
2984                 }
2985
2986                 num_msgs = sort_msglist(msglist, num_msgs);
2987                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2988                           msglist, (int)(num_msgs * sizeof(long)));
2989
2990                 qrbuf.QRhighest = msglist[num_msgs - 1];
2991         }
2992         lputroom(&qrbuf);
2993
2994         /* Go through the messages we pulled out of the index, and decrement
2995          * their reference counts by 1.  If this is the only room the message
2996          * was in, the reference count will reach zero and the message will
2997          * automatically be deleted from the database.  We do this in a
2998          * separate pass because there might be plug-in hooks getting called,
2999          * and we don't want that happening during an S_ROOMS critical
3000          * section.
3001          */
3002         if (num_deleted) for (i=0; i<num_deleted; ++i) {
3003                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3004                 AdjRefCount(dellist[i], -1);
3005         }
3006
3007         /* Now free the memory we used, and go away. */
3008         if (msglist != NULL) free(msglist);
3009         if (dellist != NULL) free(dellist);
3010         lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3011         return (num_deleted);
3012 }
3013
3014
3015
3016 /*
3017  * Check whether the current user has permission to delete messages from
3018  * the current room (returns 1 for yes, 0 for no)
3019  */
3020 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3021         getuser(&CC->user, CC->curr_user);
3022         if ((CC->user.axlevel < 6)
3023             && (CC->user.usernum != CC->room.QRroomaide)
3024             && ((CC->room.QRflags & QR_MAILBOX) == 0)
3025             && (!(CC->internal_pgm))) {
3026                 return(0);
3027         }
3028         return(1);
3029 }
3030
3031
3032
3033 /*
3034  * Delete message from current room
3035  */
3036 void cmd_dele(char *delstr)
3037 {
3038         long delnum;
3039         int num_deleted;
3040
3041         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3042                 cprintf("%d Higher access required.\n",
3043                         ERROR + HIGHER_ACCESS_REQUIRED);
3044                 return;
3045         }
3046         delnum = extract_long(delstr, 0);
3047
3048         num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
3049
3050         if (num_deleted) {
3051                 cprintf("%d %d message%s deleted.\n", CIT_OK,
3052                         num_deleted, ((num_deleted != 1) ? "s" : ""));
3053         } else {
3054                 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3055         }
3056 }
3057
3058
3059 /*
3060  * Back end API function for moves and deletes
3061  */
3062 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3063         int err;
3064
3065         err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3066                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3067         if (err != 0) return(err);
3068
3069         return(0);
3070 }
3071
3072
3073
3074 /*
3075  * move or copy a message to another room
3076  */
3077 void cmd_move(char *args)
3078 {
3079         long num;
3080         char targ[ROOMNAMELEN];
3081         struct ctdlroom qtemp;
3082         int err;
3083         int is_copy = 0;
3084         int ra;
3085         int permit = 0;
3086
3087         num = extract_long(args, 0);
3088         extract_token(targ, args, 1, '|', sizeof targ);
3089         targ[ROOMNAMELEN - 1] = 0;
3090         is_copy = extract_int(args, 2);
3091
3092         if (getroom(&qtemp, targ) != 0) {
3093                 cprintf("%d '%s' does not exist.\n",
3094                         ERROR + ROOM_NOT_FOUND, targ);
3095                 return;
3096         }
3097
3098         getuser(&CC->user, CC->curr_user);
3099         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3100
3101         /* Check for permission to perform this operation.
3102          * Remember: "CC->room" is source, "qtemp" is target.
3103          */
3104         permit = 0;
3105
3106         /* Aides can move/copy */
3107         if (CC->user.axlevel >= 6) permit = 1;
3108
3109         /* Room aides can move/copy */
3110         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3111
3112         /* Permit move/copy from personal rooms */
3113         if ((CC->room.QRflags & QR_MAILBOX)
3114            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3115
3116         /* Permit only copy from public to personal room */
3117         if ( (is_copy)
3118            && (!(CC->room.QRflags & QR_MAILBOX))
3119            && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3120
3121         /* User must have access to target room */
3122         if (!(ra & UA_KNOWN))  permit = 0;
3123
3124         if (!permit) {
3125                 cprintf("%d Higher access required.\n",
3126                         ERROR + HIGHER_ACCESS_REQUIRED);
3127                 return;
3128         }
3129
3130         err = CtdlCopyMsgToRoom(num, targ);
3131         if (err != 0) {
3132                 cprintf("%d Cannot store message in %s: error %d\n",
3133                         err, targ, err);
3134                 return;
3135         }
3136
3137         /* Now delete the message from the source room,
3138          * if this is a 'move' rather than a 'copy' operation.
3139          */
3140         if (is_copy == 0) {
3141                 CtdlDeleteMessages(CC->room.QRname, num, "");
3142         }
3143
3144         cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3145 }
3146
3147
3148
3149 /*
3150  * GetMetaData()  -  Get the supplementary record for a message
3151  */
3152 void GetMetaData(struct MetaData *smibuf, long msgnum)
3153 {
3154
3155         struct cdbdata *cdbsmi;
3156         long TheIndex;
3157
3158         memset(smibuf, 0, sizeof(struct MetaData));
3159         smibuf->meta_msgnum = msgnum;
3160         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
3161
3162         /* Use the negative of the message number for its supp record index */
3163         TheIndex = (0L - msgnum);
3164
3165         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3166         if (cdbsmi == NULL) {
3167                 return;         /* record not found; go with defaults */
3168         }
3169         memcpy(smibuf, cdbsmi->ptr,
3170                ((cdbsmi->len > sizeof(struct MetaData)) ?
3171                 sizeof(struct MetaData) : cdbsmi->len));
3172         cdb_free(cdbsmi);
3173         return;
3174 }
3175
3176
3177 /*
3178  * PutMetaData()  -  (re)write supplementary record for a message
3179  */
3180 void PutMetaData(struct MetaData *smibuf)
3181 {
3182         long TheIndex;
3183
3184         /* Use the negative of the message number for the metadata db index */
3185         TheIndex = (0L - smibuf->meta_msgnum);
3186
3187         lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3188                 smibuf->meta_msgnum, smibuf->meta_refcount);
3189
3190         cdb_store(CDB_MSGMAIN,
3191                   &TheIndex, (int)sizeof(long),
3192                   smibuf, (int)sizeof(struct MetaData));
3193
3194 }
3195
3196 /*
3197  * AdjRefCount  -  change the reference count for a message;
3198  *               delete the message if it reaches zero
3199  */
3200 void AdjRefCount(long msgnum, int incr)
3201 {
3202
3203         struct MetaData smi;
3204         long delnum;
3205
3206         /* This is a *tight* critical section; please keep it that way, as
3207          * it may get called while nested in other critical sections.  
3208          * Complicating this any further will surely cause deadlock!
3209          */
3210         begin_critical_section(S_SUPPMSGMAIN);
3211         GetMetaData(&smi, msgnum);
3212         smi.meta_refcount += incr;
3213         PutMetaData(&smi);
3214         end_critical_section(S_SUPPMSGMAIN);
3215         lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3216                 msgnum, incr, smi.meta_refcount);
3217
3218         /* If the reference count is now zero, delete the message
3219          * (and its supplementary record as well).
3220          * FIXME ... defer this so it doesn't keep the user waiting.
3221          */
3222         if (smi.meta_refcount == 0) {
3223                 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3224
3225                 /* Remove from fulltext index */
3226                 if (config.c_enable_fulltext) {
3227                         ft_index_message(msgnum, 0);
3228                 }
3229
3230                 /* Remove from message base */
3231                 delnum = msgnum;
3232                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3233                 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3234
3235                 /* Remove metadata record */
3236                 delnum = (0L - msgnum);
3237                 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3238         }
3239 }
3240
3241 /*
3242  * Write a generic object to this room
3243  *
3244  * Note: this could be much more efficient.  Right now we use two temporary
3245  * files, and still pull the message into memory as with all others.
3246  */
3247 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
3248                         char *content_type,     /* MIME type of this object */
3249                         char *tempfilename,     /* Where to fetch it from */
3250                         struct ctdluser *is_mailbox,    /* Mailbox room? */
3251                         int is_binary,          /* Is encoding necessary? */
3252                         int is_unique,          /* Del others of this type? */
3253                         unsigned int flags      /* Internal save flags */
3254                         )
3255 {
3256
3257         FILE *fp;
3258         struct ctdlroom qrbuf;
3259         char roomname[ROOMNAMELEN];
3260         struct CtdlMessage *msg;
3261
3262         char *raw_message = NULL;
3263         char *encoded_message = NULL;
3264         off_t raw_length = 0;
3265
3266         if (is_mailbox != NULL)
3267                 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3268         else
3269                 safestrncpy(roomname, req_room, sizeof(roomname));
3270         lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3271
3272
3273         fp = fopen(tempfilename, "rb");
3274         if (fp == NULL) {
3275                 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3276                         tempfilename, strerror(errno));
3277                 return;
3278         }
3279         fseek(fp, 0L, SEEK_END);
3280         raw_length = ftell(fp);
3281         rewind(fp);
3282         lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3283
3284         raw_message = malloc((size_t)raw_length + 2);
3285         fread(raw_message, (size_t)raw_length, 1, fp);
3286         fclose(fp);
3287
3288         if (is_binary) {
3289                 encoded_message = malloc((size_t)
3290                         (((raw_length * 134) / 100) + 4096 ) );
3291         }
3292         else {
3293                 encoded_message = malloc((size_t)(raw_length + 4096));
3294         }
3295
3296         sprintf(encoded_message, "Content-type: %s\n", content_type);
3297
3298         if (is_binary) {
3299                 sprintf(&encoded_message[strlen(encoded_message)],
3300                         "Content-transfer-encoding: base64\n\n"
3301                 );
3302         }
3303         else {
3304                 sprintf(&encoded_message[strlen(encoded_message)],
3305                         "Content-transfer-encoding: 7bit\n\n"
3306                 );
3307         }
3308
3309         if (is_binary) {
3310                 CtdlEncodeBase64(
3311                         &encoded_message[strlen(encoded_message)],
3312                         raw_message,
3313                         (int)raw_length
3314                 );
3315         }
3316         else {
3317                 raw_message[raw_length] = 0;
3318                 memcpy(
3319                         &encoded_message[strlen(encoded_message)],
3320                         raw_message,
3321                         (int)(raw_length+1)
3322                 );
3323         }
3324
3325         free(raw_message);
3326
3327         lprintf(CTDL_DEBUG, "Allocating\n");
3328         msg = malloc(sizeof(struct CtdlMessage));
3329         memset(msg, 0, sizeof(struct CtdlMessage));
3330         msg->cm_magic = CTDLMESSAGE_MAGIC;
3331         msg->cm_anon_type = MES_NORMAL;
3332         msg->cm_format_type = 4;
3333         msg->cm_fields['A'] = strdup(CC->user.fullname);
3334         msg->cm_fields['O'] = strdup(req_room);
3335         msg->cm_fields['N'] = strdup(config.c_nodename);
3336         msg->cm_fields['H'] = strdup(config.c_humannode);
3337         msg->cm_flags = flags;
3338         
3339         msg->cm_fields['M'] = encoded_message;
3340
3341         /* Create the requested room if we have to. */
3342         if (getroom(&qrbuf, roomname) != 0) {
3343                 create_room(roomname, 
3344                         ( (is_mailbox != NULL) ? 5 : 3 ),
3345                         "", 0, 1, 0, VIEW_BBS);
3346         }
3347         /* If the caller specified this object as unique, delete all
3348          * other objects of this type that are currently in the room.
3349          */
3350         if (is_unique) {
3351                 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3352                         CtdlDeleteMessages(roomname, 0L, content_type));
3353         }
3354         /* Now write the data */
3355         CtdlSubmitMsg(msg, NULL, roomname);
3356         CtdlFreeMessage(msg);
3357 }
3358
3359
3360
3361
3362
3363
3364 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3365         config_msgnum = msgnum;
3366 }
3367
3368
3369 char *CtdlGetSysConfig(char *sysconfname) {
3370         char hold_rm[ROOMNAMELEN];
3371         long msgnum;
3372         char *conf;
3373         struct CtdlMessage *msg;
3374         char buf[SIZ];
3375         
3376         strcpy(hold_rm, CC->room.QRname);
3377         if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3378                 getroom(&CC->room, hold_rm);
3379                 return NULL;
3380         }
3381
3382
3383         /* We want the last (and probably only) config in this room */
3384         begin_critical_section(S_CONFIG);
3385         config_msgnum = (-1L);
3386         CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3387                 CtdlGetSysConfigBackend, NULL);
3388         msgnum = config_msgnum;
3389         end_critical_section(S_CONFIG);
3390
3391         if (msgnum < 0L) {
3392                 conf = NULL;
3393         }
3394         else {
3395                 msg = CtdlFetchMessage(msgnum, 1);
3396                 if (msg != NULL) {
3397                         conf = strdup(msg->cm_fields['M']);
3398                         CtdlFreeMessage(msg);
3399                 }
3400                 else {
3401                         conf = NULL;
3402                 }
3403         }
3404
3405         getroom(&CC->room, hold_rm);
3406
3407         if (conf != NULL) do {
3408                 extract_token(buf, conf, 0, '\n', sizeof buf);
3409                 strcpy(conf, &conf[strlen(buf)+1]);
3410         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3411
3412         return(conf);
3413 }
3414
3415 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3416         char temp[PATH_MAX];
3417         FILE *fp;
3418
3419         strcpy(temp, tmpnam(NULL));
3420
3421         fp = fopen(temp, "w");
3422         if (fp == NULL) return;
3423         fprintf(fp, "%s", sysconfdata);
3424         fclose(fp);
3425
3426         /* this handy API function does all the work for us */
3427         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3428         unlink(temp);
3429 }
3430
3431
3432 /*
3433  * Determine whether a given Internet address belongs to the current user
3434  */
3435 int CtdlIsMe(char *addr, int addr_buf_len)
3436 {
3437         struct recptypes *recp;
3438         int i;
3439
3440         recp = validate_recipients(addr);
3441         if (recp == NULL) return(0);
3442
3443         if (recp->num_local == 0) {
3444                 free(recp);
3445                 return(0);
3446         }
3447
3448         for (i=0; i<recp->num_local; ++i) {
3449                 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3450                 if (!strcasecmp(addr, CC->user.fullname)) {
3451                         free(recp);
3452                         return(1);
3453                 }
3454         }
3455
3456         free(recp);
3457         return(0);
3458 }
3459
3460
3461 /*
3462  * Citadel protocol command to do the same
3463  */
3464 void cmd_isme(char *argbuf) {
3465         char addr[256];
3466
3467         if (CtdlAccessCheck(ac_logged_in)) return;
3468         extract_token(addr, argbuf, 0, '|', sizeof addr);
3469
3470         if (CtdlIsMe(addr, sizeof addr)) {
3471                 cprintf("%d %s\n", CIT_OK, addr);
3472         }
3473         else {
3474                 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
3475         }
3476
3477 }