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