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