afbb717ff6b5f6589d8d0c6b689b0b2b06cdeca5
[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 <syslog.h>
33 #include <limits.h>
34 #include <errno.h>
35 #include <stdarg.h>
36 #include <sys/stat.h>
37 #include "citadel.h"
38 #include "server.h"
39 #include "dynloader.h"
40 #include "database.h"
41 #include "msgbase.h"
42 #include "support.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
45 #include "room_ops.h"
46 #include "user_ops.h"
47 #include "file_ops.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
55 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
56 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
57 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
58
59 extern struct config config;
60 long config_msgnum;
61
62 char *msgkeys[] = {
63         "", "", "", "", "", "", "", "", 
64         "", "", "", "", "", "", "", "", 
65         "", "", "", "", "", "", "", "", 
66         "", "", "", "", "", "", "", "", 
67         "", "", "", "", "", "", "", "", 
68         "", "", "", "", "", "", "", "", 
69         "", "", "", "", "", "", "", "", 
70         "", "", "", "", "", "", "", "", 
71         "", 
72         "from",
73         "", "", "",
74         "exti",
75         "rfca",
76         "", 
77         "hnod",
78         "msgn",
79         "", "", "",
80         "text",
81         "node",
82         "room",
83         "path",
84         "",
85         "rcpt",
86         "spec",
87         "time",
88         "subj",
89         "",
90         "",
91         "",
92         "",
93         ""
94 };
95
96 /*
97  * This function is self explanatory.
98  * (What can I say, I'm in a weird mood today...)
99  */
100 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
101 {
102         int i;
103
104         for (i = 0; i < strlen(name); ++i) {
105                 if (name[i] == '@') {
106                         while (isspace(name[i - 1]) && i > 0) {
107                                 strcpy(&name[i - 1], &name[i]);
108                                 --i;
109                         }
110                         while (isspace(name[i + 1])) {
111                                 strcpy(&name[i + 1], &name[i + 2]);
112                         }
113                 }
114         }
115 }
116
117
118 /*
119  * Aliasing for network mail.
120  * (Error messages have been commented out, because this is a server.)
121  */
122 int alias(char *name)
123 {                               /* process alias and routing info for mail */
124         FILE *fp;
125         int a, i;
126         char aaa[SIZ], bbb[SIZ];
127         char *ignetcfg = NULL;
128         char *ignetmap = NULL;
129         int at = 0;
130         char node[SIZ];
131         char testnode[SIZ];
132         char buf[SIZ];
133
134         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
135
136         fp = fopen("network/mail.aliases", "r");
137         if (fp == NULL) {
138                 fp = fopen("/dev/null", "r");
139         }
140         if (fp == NULL) {
141                 return (MES_ERROR);
142         }
143         strcpy(aaa, "");
144         strcpy(bbb, "");
145         while (fgets(aaa, sizeof aaa, fp) != NULL) {
146                 while (isspace(name[0]))
147                         strcpy(name, &name[1]);
148                 aaa[strlen(aaa) - 1] = 0;
149                 strcpy(bbb, "");
150                 for (a = 0; a < strlen(aaa); ++a) {
151                         if (aaa[a] == ',') {
152                                 strcpy(bbb, &aaa[a + 1]);
153                                 aaa[a] = 0;
154                         }
155                 }
156                 if (!strcasecmp(name, aaa))
157                         strcpy(name, bbb);
158         }
159         fclose(fp);
160         lprintf(7, "Mail is being forwarded to %s\n", name);
161
162         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
163         for (a=0; a<strlen(name); ++a) {
164                 if (name[a] == '@') {
165                         if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
166                                 name[a] = 0;
167                                 lprintf(7, "Changed to <%s>\n", name);
168                         }
169                 }
170         }
171
172         /* determine local or remote type, see citadel.h */
173
174         at = haschar(name, '@');
175         if (at == 0) return(MES_LOCAL);         /* no @'s - local address */
176         if (at > 1) return(MES_ERROR);          /* >1 @'s - invalid address */
177         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
178
179         /* figure out the delivery mode */
180
181         extract_token(node, name, 1, '@');
182
183         /* If there are one or more dots in the nodename, we assume that it
184          * is an FQDN and will attempt SMTP delivery to the Internet.
185          */
186         if (haschar(node, '.') > 0) {
187                 return(MES_INTERNET);
188         }
189
190         /* Otherwise we look in the IGnet maps for a valid Citadel node.
191          * Try directly-connected nodes first...
192          */
193         ignetcfg = CtdlGetSysConfig(IGNETCFG);
194         for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
195                 extract_token(buf, ignetcfg, i, '\n');
196                 extract_token(testnode, buf, 0, '|');
197                 if (!strcasecmp(node, testnode)) {
198                         phree(ignetcfg);
199                         return(MES_IGNET);
200                 }
201         }
202         phree(ignetcfg);
203
204         /*
205          * Then try nodes that are two or more hops away.
206          */
207         ignetmap = CtdlGetSysConfig(IGNETMAP);
208         for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
209                 extract_token(buf, ignetmap, i, '\n');
210                 extract_token(testnode, buf, 0, '|');
211                 if (!strcasecmp(node, testnode)) {
212                         phree(ignetmap);
213                         return(MES_IGNET);
214                 }
215         }
216         phree(ignetmap);
217
218         /* If we get to this point it's an invalid node name */
219         return (MES_ERROR);
220 }
221
222
223 void get_mm(void)
224 {
225         FILE *fp;
226
227         fp = fopen("citadel.control", "r");
228         fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
229         fclose(fp);
230 }
231
232
233
234 void simple_listing(long msgnum, void *userdata)
235 {
236         cprintf("%ld\n", msgnum);
237 }
238
239
240
241 /* Determine if a given message matches the fields in a message template.
242  * Return 0 for a successful match.
243  */
244 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
245         int i;
246
247         /* If there aren't any fields in the template, all messages will
248          * match.
249          */
250         if (template == NULL) return(0);
251
252         /* Null messages are bogus. */
253         if (msg == NULL) return(1);
254
255         for (i='A'; i<='Z'; ++i) {
256                 if (template->cm_fields[i] != NULL) {
257                         if (msg->cm_fields[i] == NULL) {
258                                 return 1;
259                         }
260                         if (strcasecmp(msg->cm_fields[i],
261                                 template->cm_fields[i])) return 1;
262                 }
263         }
264
265         /* All compares succeeded: we have a match! */
266         return 0;
267 }
268
269
270 /*
271  * Manipulate the "seen msgs" string.
272  */
273 void CtdlSetSeen(long target_msgnum, int target_setting) {
274         char newseen[SIZ];
275         struct cdbdata *cdbfr;
276         int i;
277         int is_seen = 0;
278         int was_seen = 1;
279         long lo = (-1L);
280         long hi = (-1L);
281         struct visit vbuf;
282         long *msglist;
283         int num_msgs = 0;
284
285         /* Learn about the user and room in question */
286         get_mm();
287         getuser(&CC->usersupp, CC->curr_user);
288         CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
289
290         /* Load the message list */
291         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
292         if (cdbfr != NULL) {
293                 msglist = mallok(cdbfr->len);
294                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
295                 num_msgs = cdbfr->len / sizeof(long);
296                 cdb_free(cdbfr);
297         } else {
298                 return; /* No messages at all?  No further action. */
299         }
300
301         lprintf(9, "before optimize: %s\n", vbuf.v_seen);
302         strcpy(newseen, "");
303
304         for (i=0; i<num_msgs; ++i) {
305                 is_seen = 0;
306
307                 if (msglist[i] == target_msgnum) {
308                         is_seen = target_setting;
309                 }
310                 else {
311                         if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
312                                 is_seen = 1;
313                         }
314                 }
315
316                 if (is_seen == 1) {
317                         if (lo < 0L) lo = msglist[i];
318                         hi = msglist[i];
319                 }
320                 if (  ((is_seen == 0) && (was_seen == 1))
321                    || ((is_seen == 1) && (i == num_msgs-1)) ) {
322                         if ( (strlen(newseen) + 20) > SIZ) {
323                                 strcpy(newseen, &newseen[20]);
324                                 newseen[0] = '*';
325                         }
326                         if (strlen(newseen) > 0) strcat(newseen, ",");
327                         if (lo == hi) {
328                                 sprintf(&newseen[strlen(newseen)], "%ld", lo);
329                         }
330                         else {
331                                 sprintf(&newseen[strlen(newseen)], "%ld:%ld",
332                                         lo, hi);
333                         }
334                         lo = (-1L);
335                         hi = (-1L);
336                 }
337                 was_seen = is_seen;
338         }
339
340         safestrncpy(vbuf.v_seen, newseen, SIZ);
341         lprintf(9, " after optimize: %s\n", vbuf.v_seen);
342         phree(msglist);
343         CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
344 }
345
346
347 /*
348  * API function to perform an operation for each qualifying message in the
349  * current room.  (Returns the number of messages processed.)
350  */
351 int CtdlForEachMessage(int mode, long ref,
352                         int moderation_level,
353                         char *content_type,
354                         struct CtdlMessage *compare,
355                         void (*CallBack) (long, void *),
356                         void *userdata)
357 {
358
359         int a;
360         struct visit vbuf;
361         struct cdbdata *cdbfr;
362         long *msglist = NULL;
363         int num_msgs = 0;
364         int num_processed = 0;
365         long thismsg;
366         struct MetaData smi;
367         struct CtdlMessage *msg;
368         int is_seen;
369         long lastold = 0L;
370         int printed_lastold = 0;
371
372         /* Learn about the user and room in question */
373         get_mm();
374         getuser(&CC->usersupp, CC->curr_user);
375         CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
376
377         /* Load the message list */
378         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
379         if (cdbfr != NULL) {
380                 msglist = mallok(cdbfr->len);
381                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
382                 num_msgs = cdbfr->len / sizeof(long);
383                 cdb_free(cdbfr);
384         } else {
385                 return 0;       /* No messages at all?  No further action. */
386         }
387
388
389         /*
390          * Now begin the traversal.
391          */
392         if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
393                 GetMetaData(&smi, msglist[a]);
394
395                 /* Filter out messages that are moderated below the level
396                  * currently being viewed at.
397                  */
398                 if (smi.meta_mod < moderation_level) {
399                         msglist[a] = 0L;
400                 }
401
402                 /* If the caller is looking for a specific MIME type, filter
403                  * out all messages which are not of the type requested.
404                  */
405                 if (content_type != NULL) if (strlen(content_type) > 0) {
406                         if (strcasecmp(smi.meta_content_type, content_type)) {
407                                 msglist[a] = 0L;
408                         }
409                 }
410         }
411
412         num_msgs = sort_msglist(msglist, num_msgs);
413
414         /* If a template was supplied, filter out the messages which
415          * don't match.  (This could induce some delays!)
416          */
417         if (num_msgs > 0) {
418                 if (compare != NULL) {
419                         for (a = 0; a < num_msgs; ++a) {
420                                 msg = CtdlFetchMessage(msglist[a]);
421                                 if (msg != NULL) {
422                                         if (CtdlMsgCmp(msg, compare)) {
423                                                 msglist[a] = 0L;
424                                         }
425                                         CtdlFreeMessage(msg);
426                                 }
427                         }
428                 }
429         }
430
431         
432         /*
433          * Now iterate through the message list, according to the
434          * criteria supplied by the caller.
435          */
436         if (num_msgs > 0)
437                 for (a = 0; a < num_msgs; ++a) {
438                         thismsg = msglist[a];
439                         is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
440                         if (is_seen) lastold = thismsg;
441                         if ((thismsg > 0L)
442                             && (
443
444                                        (mode == MSGS_ALL)
445                                        || ((mode == MSGS_OLD) && (is_seen))
446                                        || ((mode == MSGS_NEW) && (!is_seen))
447                                        || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
448                                    || ((mode == MSGS_FIRST) && (a < ref))
449                                 || ((mode == MSGS_GT) && (thismsg > ref))
450                                 || ((mode == MSGS_EQ) && (thismsg == ref))
451                             )
452                             ) {
453                                 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
454                                         if (CallBack)
455                                                 CallBack(lastold, userdata);
456                                         printed_lastold = 1;
457                                         ++num_processed;
458                                 }
459                                 if (CallBack) CallBack(thismsg, userdata);
460                                 ++num_processed;
461                         }
462                 }
463         phree(msglist);         /* Clean up */
464         return num_processed;
465 }
466
467
468
469 /*
470  * cmd_msgs()  -  get list of message #'s in this room
471  *                implements the MSGS server command using CtdlForEachMessage()
472  */
473 void cmd_msgs(char *cmdbuf)
474 {
475         int mode = 0;
476         char which[SIZ];
477         char buf[SIZ];
478         char tfield[SIZ];
479         char tvalue[SIZ];
480         int cm_ref = 0;
481         int i;
482         int with_template = 0;
483         struct CtdlMessage *template = NULL;
484
485         extract(which, cmdbuf, 0);
486         cm_ref = extract_int(cmdbuf, 1);
487         with_template = extract_int(cmdbuf, 2);
488
489         mode = MSGS_ALL;
490         strcat(which, "   ");
491         if (!strncasecmp(which, "OLD", 3))
492                 mode = MSGS_OLD;
493         else if (!strncasecmp(which, "NEW", 3))
494                 mode = MSGS_NEW;
495         else if (!strncasecmp(which, "FIRST", 5))
496                 mode = MSGS_FIRST;
497         else if (!strncasecmp(which, "LAST", 4))
498                 mode = MSGS_LAST;
499         else if (!strncasecmp(which, "GT", 2))
500                 mode = MSGS_GT;
501
502         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
503                 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
504                 return;
505         }
506
507         if (with_template) {
508                 cprintf("%d Send template then receive message list\n",
509                         START_CHAT_MODE);
510                 template = (struct CtdlMessage *)
511                         mallok(sizeof(struct CtdlMessage));
512                 memset(template, 0, sizeof(struct CtdlMessage));
513                 while(client_gets(buf), strcmp(buf,"000")) {
514                         extract(tfield, buf, 0);
515                         extract(tvalue, buf, 1);
516                         for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
517                                 if (!strcasecmp(tfield, msgkeys[i])) {
518                                         template->cm_fields[i] =
519                                                 strdoop(tvalue);
520                                 }
521                         }
522                 }
523         }
524         else {
525                 cprintf("%d Message list...\n", LISTING_FOLLOWS);
526         }
527
528         CtdlForEachMessage(mode, cm_ref,
529                 CC->usersupp.moderation_filter,
530                 NULL, template, simple_listing, NULL);
531         if (template != NULL) CtdlFreeMessage(template);
532         cprintf("000\n");
533 }
534
535
536
537
538 /* 
539  * help_subst()  -  support routine for help file viewer
540  */
541 void help_subst(char *strbuf, char *source, char *dest)
542 {
543         char workbuf[SIZ];
544         int p;
545
546         while (p = pattern2(strbuf, source), (p >= 0)) {
547                 strcpy(workbuf, &strbuf[p + strlen(source)]);
548                 strcpy(&strbuf[p], dest);
549                 strcat(strbuf, workbuf);
550         }
551 }
552
553
554 void do_help_subst(char *buffer)
555 {
556         char buf2[16];
557
558         help_subst(buffer, "^nodename", config.c_nodename);
559         help_subst(buffer, "^humannode", config.c_humannode);
560         help_subst(buffer, "^fqdn", config.c_fqdn);
561         help_subst(buffer, "^username", CC->usersupp.fullname);
562         sprintf(buf2, "%ld", CC->usersupp.usernum);
563         help_subst(buffer, "^usernum", buf2);
564         help_subst(buffer, "^sysadm", config.c_sysadm);
565         help_subst(buffer, "^variantname", CITADEL);
566         sprintf(buf2, "%d", config.c_maxsessions);
567         help_subst(buffer, "^maxsessions", buf2);
568 }
569
570
571
572 /*
573  * memfmout()  -  Citadel text formatter and paginator.
574  *             Although the original purpose of this routine was to format
575  *             text to the reader's screen width, all we're really using it
576  *             for here is to format text out to 80 columns before sending it
577  *             to the client.  The client software may reformat it again.
578  */
579 void memfmout(
580         int width,              /* screen width to use */
581         char *mptr,             /* where are we going to get our text from? */
582         char subst,             /* nonzero if we should do substitutions */
583         char *nl)               /* string to terminate lines with */
584 {
585         int a, b, c;
586         int real = 0;
587         int old = 0;
588         CIT_UBYTE ch;
589         char aaa[140];
590         char buffer[SIZ];
591
592         strcpy(aaa, "");
593         old = 255;
594         strcpy(buffer, "");
595         c = 1;                  /* c is the current pos */
596
597         do {
598                 if (subst) {
599                         while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
600                                 ch = *mptr++;
601                                 buffer[strlen(buffer) + 1] = 0;
602                                 buffer[strlen(buffer)] = ch;
603                         }
604
605                         if (buffer[0] == '^')
606                                 do_help_subst(buffer);
607
608                         buffer[strlen(buffer) + 1] = 0;
609                         a = buffer[0];
610                         strcpy(buffer, &buffer[1]);
611                 } else {
612                         ch = *mptr++;
613                 }
614
615                 old = real;
616                 real = ch;
617
618                 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
619                         ch = 32;
620                 if (((old == 13) || (old == 10)) && (isspace(real))) {
621                         cprintf("%s", nl);
622                         c = 1;
623                 }
624                 if (ch > 126)
625                         continue;
626
627                 if (ch > 32) {
628                         if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
629                                 cprintf("%s%s", nl, aaa);
630                                 c = strlen(aaa);
631                                 aaa[0] = 0;
632                         }
633                         b = strlen(aaa);
634                         aaa[b] = ch;
635                         aaa[b + 1] = 0;
636                 }
637                 if (ch == 32) {
638                         if ((strlen(aaa) + c) > (width - 5)) {
639                                 cprintf("%s", nl);
640                                 c = 1;
641                         }
642                         cprintf("%s ", aaa);
643                         ++c;
644                         c = c + strlen(aaa);
645                         strcpy(aaa, "");
646                 }
647                 if ((ch == 13) || (ch == 10)) {
648                         cprintf("%s%s", aaa, nl);
649                         c = 1;
650                         strcpy(aaa, "");
651                 }
652
653         } while (ch > 0);
654
655         cprintf("%s%s", aaa, nl);
656 }
657
658
659
660 /*
661  * Callback function for mime parser that simply lists the part
662  */
663 void list_this_part(char *name, char *filename, char *partnum, char *disp,
664                     void *content, char *cbtype, size_t length, char *encoding,
665                     void *cbuserdata)
666 {
667
668         cprintf("part=%s|%s|%s|%s|%s|%ld\n",
669                 name, filename, partnum, disp, cbtype, (long)length);
670 }
671
672
673 /*
674  * Callback function for mime parser that opens a section for downloading
675  */
676 void mime_download(char *name, char *filename, char *partnum, char *disp,
677                    void *content, char *cbtype, size_t length, char *encoding,
678                    void *cbuserdata)
679 {
680
681         /* Silently go away if there's already a download open... */
682         if (CC->download_fp != NULL)
683                 return;
684
685         /* ...or if this is not the desired section */
686         if (strcasecmp(desired_section, partnum))
687                 return;
688
689         CC->download_fp = tmpfile();
690         if (CC->download_fp == NULL)
691                 return;
692
693         fwrite(content, length, 1, CC->download_fp);
694         fflush(CC->download_fp);
695         rewind(CC->download_fp);
696
697         OpenCmdResult(filename, cbtype);
698 }
699
700
701
702 /*
703  * Load a message from disk into memory.
704  * This is used by CtdlOutputMsg() and other fetch functions.
705  *
706  * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
707  *       using the CtdlMessageFree() function.
708  */
709 struct CtdlMessage *CtdlFetchMessage(long msgnum)
710 {
711         struct cdbdata *dmsgtext;
712         struct CtdlMessage *ret = NULL;
713         char *mptr;
714         CIT_UBYTE ch;
715         CIT_UBYTE field_header;
716         size_t field_length;
717
718         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
719         if (dmsgtext == NULL) {
720                 return NULL;
721         }
722         mptr = dmsgtext->ptr;
723
724         /* Parse the three bytes that begin EVERY message on disk.
725          * The first is always 0xFF, the on-disk magic number.
726          * The second is the anonymous/public type byte.
727          * The third is the format type byte (vari, fixed, or MIME).
728          */
729         ch = *mptr++;
730         if (ch != 255) {
731                 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
732                 cdb_free(dmsgtext);
733                 return NULL;
734         }
735         ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
736         memset(ret, 0, sizeof(struct CtdlMessage));
737
738         ret->cm_magic = CTDLMESSAGE_MAGIC;
739         ret->cm_anon_type = *mptr++;    /* Anon type byte */
740         ret->cm_format_type = *mptr++;  /* Format type byte */
741
742         /*
743          * The rest is zero or more arbitrary fields.  Load them in.
744          * We're done when we encounter either a zero-length field or
745          * have just processed the 'M' (message text) field.
746          */
747         do {
748                 field_length = strlen(mptr);
749                 if (field_length == 0)
750                         break;
751                 field_header = *mptr++;
752                 ret->cm_fields[field_header] = mallok(field_length);
753                 strcpy(ret->cm_fields[field_header], mptr);
754
755                 while (*mptr++ != 0);   /* advance to next field */
756
757         } while ((field_length > 0) && (field_header != 'M'));
758
759         cdb_free(dmsgtext);
760
761         /* Always make sure there's something in the msg text field */
762         if (ret->cm_fields['M'] == NULL)
763                 ret->cm_fields['M'] = strdoop("<no text>\n");
764
765         /* Perform "before read" hooks (aborting if any return nonzero) */
766         if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
767                 CtdlFreeMessage(ret);
768                 return NULL;
769         }
770
771         return (ret);
772 }
773
774
775 /*
776  * Returns 1 if the supplied pointer points to a valid Citadel message.
777  * If the pointer is NULL or the magic number check fails, returns 0.
778  */
779 int is_valid_message(struct CtdlMessage *msg) {
780         if (msg == NULL)
781                 return 0;
782         if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
783                 lprintf(3, "is_valid_message() -- self-check failed\n");
784                 return 0;
785         }
786         return 1;
787 }
788
789
790 /*
791  * 'Destructor' for struct CtdlMessage
792  */
793 void CtdlFreeMessage(struct CtdlMessage *msg)
794 {
795         int i;
796
797         lprintf(9, "CtdlFreeMessage() called\n");
798         if (is_valid_message(msg) == 0) return;
799
800         for (i = 0; i < 256; ++i)
801                 if (msg->cm_fields[i] != NULL) {
802                         phree(msg->cm_fields[i]);
803                 }
804
805         msg->cm_magic = 0;      /* just in case */
806         phree(msg);
807 }
808
809
810 /*
811  * Pre callback function for multipart/alternative
812  *
813  * NOTE: this differs from the standard behavior for a reason.  Normally when
814  *       displaying multipart/alternative you want to show the _last_ usable
815  *       format in the message.  Here we show the _first_ one, because it's
816  *       usually text/plain.  Since this set of functions is designed for text
817  *       output to non-MIME-aware clients, this is the desired behavior.
818  *
819  */
820 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
821                 void *content, char *cbtype, size_t length, char *encoding,
822                 void *cbuserdata)
823 {
824                 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);   
825                 if (!strcasecmp(cbtype, "multipart/alternative")) {
826                         ma->is_ma = 1;
827                         ma->did_print = 0;
828                         return;
829                 }
830 }
831
832 /*
833  * Post callback function for multipart/alternative
834  */
835 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
836                 void *content, char *cbtype, size_t length, char *encoding,
837                 void *cbuserdata)
838 {
839                 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);  
840                 if (!strcasecmp(cbtype, "multipart/alternative")) {
841                         ma->is_ma = 0;
842                         ma->did_print = 0;
843                         return;
844                 }
845 }
846
847 /*
848  * Inline callback function for mime parser that wants to display text
849  */
850 void fixed_output(char *name, char *filename, char *partnum, char *disp,
851                 void *content, char *cbtype, size_t length, char *encoding,
852                 void *cbuserdata)
853         {
854                 char *ptr;
855                 char *wptr;
856                 size_t wlen;
857                 CIT_UBYTE ch = 0;
858
859                 lprintf(9, "fixed_output() type=<%s>\n", cbtype);       
860
861                 /*
862                  * If we're in the middle of a multipart/alternative scope and
863                  * we've already printed another section, skip this one.
864                  */     
865                 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
866                         lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
867                         return;
868                 }
869                 ma->did_print = 1;
870         
871                 if ( (!strcasecmp(cbtype, "text/plain")) 
872                    || (strlen(cbtype)==0) ) {
873                         wlen = length;
874                         wptr = content;
875                         while (wlen--) {
876                                 ch = *wptr++;
877                                 /**********
878                                 if (ch==10) cprintf("\r\n");
879                                 else cprintf("%c", ch);
880                                  **********/
881                                 cprintf("%c", ch);
882                         }
883                         if (ch != '\n') cprintf("\n");
884                 }
885                 else if (!strcasecmp(cbtype, "text/html")) {
886                         ptr = html_to_ascii(content, 80, 0);
887                         wlen = strlen(ptr);
888                         wptr = ptr;
889                         while (wlen--) {
890                                 ch = *wptr++;
891                                 if (ch==10) cprintf("\r\n");
892                                 else cprintf("%c", ch);
893                         }
894                         phree(ptr);
895                 }
896                 else if (strncasecmp(cbtype, "multipart/", 10)) {
897                         cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
898                                 partnum, filename, cbtype, (long)length);
899                 }
900         }
901
902
903 /*
904  * Get a message off disk.  (returns om_* values found in msgbase.h)
905  * 
906  */
907 int CtdlOutputMsg(long msg_num,         /* message number (local) to fetch */
908                 int mode,               /* how would you like that message? */
909                 int headers_only,       /* eschew the message body? */
910                 int do_proto,           /* do Citadel protocol responses? */
911                 int crlf                /* Use CRLF newlines instead of LF? */
912 ) {
913         struct CtdlMessage *TheMessage;
914         int retcode;
915
916         lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n", 
917                 msg_num, mode);
918
919         TheMessage = NULL;
920
921         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
922                 if (do_proto) cprintf("%d Not logged in.\n",
923                         ERROR + NOT_LOGGED_IN);
924                 return(om_not_logged_in);
925         }
926
927         /* FIXME ... small security issue
928          * We need to check to make sure the requested message is actually
929          * in the current room, and set msg_ok to 1 only if it is.  This
930          * functionality is currently missing because I'm in a hurry to replace
931          * broken production code with nonbroken pre-beta code.  :(   -- ajc
932          *
933          if (!msg_ok) {
934          if (do_proto) cprintf("%d Message %ld is not in this room.\n",
935          ERROR, msg_num);
936          return(om_no_such_msg);
937          }
938          */
939
940         /*
941          * Fetch the message from disk
942          */
943         TheMessage = CtdlFetchMessage(msg_num);
944         if (TheMessage == NULL) {
945                 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
946                         ERROR, msg_num);
947                 return(om_no_such_msg);
948         }
949         
950         retcode = CtdlOutputPreLoadedMsg(
951                         TheMessage, msg_num, mode,
952                         headers_only, do_proto, crlf);
953
954         CtdlFreeMessage(TheMessage);
955         return(retcode);
956 }
957
958
959 /*
960  * Get a message off disk.  (returns om_* values found in msgbase.h)
961  * 
962  */
963 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
964                 long msg_num,
965                 int mode,               /* how would you like that message? */
966                 int headers_only,       /* eschew the message body? */
967                 int do_proto,           /* do Citadel protocol responses? */
968                 int crlf                /* Use CRLF newlines instead of LF? */
969 ) {
970         int i, k;
971         char buf[1024];
972         CIT_UBYTE ch;
973         char allkeys[SIZ];
974         char display_name[SIZ];
975         char *mptr;
976         char *nl;       /* newline string */
977
978         /* buffers needed for RFC822 translation */
979         char suser[SIZ];
980         char luser[SIZ];
981         char fuser[SIZ];
982         char snode[SIZ];
983         char lnode[SIZ];
984         char mid[SIZ];
985         char datestamp[SIZ];
986         /*                                       */
987
988         sprintf(mid, "%ld", msg_num);
989         nl = (crlf ? "\r\n" : "\n");
990
991         if (!is_valid_message(TheMessage)) {
992                 lprintf(1, "ERROR: invalid preloaded message for output\n");
993                 return(om_no_such_msg);
994         }
995
996         /* Are we downloading a MIME component? */
997         if (mode == MT_DOWNLOAD) {
998                 if (TheMessage->cm_format_type != FMT_RFC822) {
999                         if (do_proto)
1000                                 cprintf("%d This is not a MIME message.\n",
1001                                 ERROR);
1002                 } else if (CC->download_fp != NULL) {
1003                         if (do_proto) cprintf(
1004                                 "%d You already have a download open.\n",
1005                                 ERROR);
1006                 } else {
1007                         /* Parse the message text component */
1008                         mptr = TheMessage->cm_fields['M'];
1009                         mime_parser(mptr, NULL,
1010                                 *mime_download, NULL, NULL,
1011                                 NULL, 0);
1012                         /* If there's no file open by this time, the requested
1013                          * section wasn't found, so print an error
1014                          */
1015                         if (CC->download_fp == NULL) {
1016                                 if (do_proto) cprintf(
1017                                         "%d Section %s not found.\n",
1018                                         ERROR + FILE_NOT_FOUND,
1019                                         desired_section);
1020                         }
1021                 }
1022                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1023         }
1024
1025         /* now for the user-mode message reading loops */
1026         if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1027
1028         /* Tell the client which format type we're using.  If this is a
1029          * MIME message, *lie* about it and tell the user it's fixed-format.
1030          */
1031         if (mode == MT_CITADEL) {
1032                 if (TheMessage->cm_format_type == FMT_RFC822) {
1033                         if (do_proto) cprintf("type=1\n");
1034                 }
1035                 else {
1036                         if (do_proto) cprintf("type=%d\n",
1037                                 TheMessage->cm_format_type);
1038                 }
1039         }
1040
1041         /* nhdr=yes means that we're only displaying headers, no body */
1042         if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1043                 if (do_proto) cprintf("nhdr=yes\n");
1044         }
1045
1046         /* begin header processing loop for Citadel message format */
1047
1048         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1049
1050                 strcpy(display_name, "<unknown>");
1051                 if (TheMessage->cm_fields['A']) {
1052                         strcpy(buf, TheMessage->cm_fields['A']);
1053                         PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1054                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1055                                 strcpy(display_name, "****");
1056                         }
1057                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1058                                 strcpy(display_name, "anonymous");
1059                         }
1060                         else {
1061                                 strcpy(display_name, buf);
1062                         }
1063                         if ((is_room_aide())
1064                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1065                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1066                                 sprintf(&display_name[strlen(display_name)],
1067                                         " [%s]", buf);
1068                         }
1069                 }
1070
1071                 strcpy(allkeys, FORDER);
1072                 for (i=0; i<strlen(allkeys); ++i) {
1073                         k = (int) allkeys[i];
1074                         if (k != 'M') {
1075                                 if (TheMessage->cm_fields[k] != NULL) {
1076                                         if (k == 'A') {
1077                                                 if (do_proto) cprintf("%s=%s\n",
1078                                                         msgkeys[k],
1079                                                         display_name);
1080                                         }
1081                                         else {
1082                                                 if (do_proto) cprintf("%s=%s\n",
1083                                                         msgkeys[k],
1084                                                         TheMessage->cm_fields[k]
1085                                         );
1086                                         }
1087                                 }
1088                         }
1089                 }
1090
1091         }
1092
1093         /* begin header processing loop for RFC822 transfer format */
1094
1095         strcpy(suser, "");
1096         strcpy(luser, "");
1097         strcpy(fuser, "");
1098         strcpy(snode, NODENAME);
1099         strcpy(lnode, HUMANNODE);
1100         if (mode == MT_RFC822) {
1101                 cprintf("X-UIDL: %ld%s", msg_num, nl);
1102                 for (i = 0; i < 256; ++i) {
1103                         if (TheMessage->cm_fields[i]) {
1104                                 mptr = TheMessage->cm_fields[i];
1105
1106                                 if (i == 'A') {
1107                                         strcpy(luser, mptr);
1108                                         strcpy(suser, mptr);
1109                                 }
1110 /****
1111  "Path:" removed for now because it confuses brain-dead Microsoft shitware
1112  into thinking that mail messages are newsgroup messages instead.  When we
1113  add NNTP support back into Citadel we'll have to add code to only output
1114  this field when appropriate.
1115                                 else if (i == 'P') {
1116                                         cprintf("Path: %s%s", mptr, nl);
1117                                 }
1118  ****/
1119                                 else if (i == 'U')
1120                                         cprintf("Subject: %s%s", mptr, nl);
1121                                 else if (i == 'I')
1122                                         strcpy(mid, mptr);
1123                                 else if (i == 'H')
1124                                         strcpy(lnode, mptr);
1125                                 else if (i == 'O')
1126                                         cprintf("X-Citadel-Room: %s%s",
1127                                                 mptr, nl);
1128                                 else if (i == 'N')
1129                                         strcpy(snode, mptr);
1130                                 else if (i == 'R')
1131                                         cprintf("To: %s%s", mptr, nl);
1132                                 else if (i == 'T') {
1133                                         datestring(datestamp, atol(mptr),
1134                                                 DATESTRING_RFC822 );
1135                                         cprintf("Date: %s%s", datestamp, nl);
1136                                 }
1137                         }
1138                 }
1139         }
1140
1141         for (i=0; i<strlen(suser); ++i) {
1142                 suser[i] = tolower(suser[i]);
1143                 if (!isalnum(suser[i])) suser[i]='_';
1144         }
1145
1146         if (mode == MT_RFC822) {
1147                 if (!strcasecmp(snode, NODENAME)) {
1148                         strcpy(snode, FQDN);
1149                 }
1150
1151                 /* Construct a fun message id */
1152                 cprintf("Message-ID: <%s", mid);
1153                 if (strchr(mid, '@')==NULL) {
1154                         cprintf("@%s", snode);
1155                 }
1156                 cprintf(">%s", nl);
1157
1158                 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1159
1160                 if (strlen(fuser) > 0) {
1161                         cprintf("From: %s (%s)%s", fuser, luser, nl);
1162                 }
1163                 else {
1164                         cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1165                 }
1166
1167                 cprintf("Organization: %s%s", lnode, nl);
1168         }
1169
1170         /* end header processing loop ... at this point, we're in the text */
1171
1172         mptr = TheMessage->cm_fields['M'];
1173
1174         /* Tell the client about the MIME parts in this message */
1175         if (TheMessage->cm_format_type == FMT_RFC822) {
1176                 if (mode == MT_CITADEL) {
1177                         mime_parser(mptr, NULL,
1178                                 *list_this_part, NULL, NULL,
1179                                 NULL, 0);
1180                 }
1181                 else if (mode == MT_MIME) {     /* list parts only */
1182                         mime_parser(mptr, NULL,
1183                                 *list_this_part, NULL, NULL,
1184                                 NULL, 0);
1185                         if (do_proto) cprintf("000\n");
1186                         return(om_ok);
1187                 }
1188                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1189                         /* FIXME ... we have to put some code in here to avoid
1190                          * printing duplicate header information when both
1191                          * Citadel and RFC822 headers exist.  Preference should
1192                          * probably be given to the RFC822 headers.
1193                          */
1194                         while (ch=*(mptr++), ch!=0) {
1195                                 if (ch==13) ;
1196                                 else if (ch==10) cprintf("%s", nl);
1197                                 else cprintf("%c", ch);
1198                         }
1199                         if (do_proto) cprintf("000\n");
1200                         return(om_ok);
1201                 }
1202         }
1203
1204         if (headers_only) {
1205                 if (do_proto) cprintf("000\n");
1206                 return(om_ok);
1207         }
1208
1209         /* signify start of msg text */
1210         if (mode == MT_CITADEL)
1211                 if (do_proto) cprintf("text\n");
1212         if (mode == MT_RFC822) {
1213                 if (TheMessage->cm_fields['U'] == NULL) {
1214                         cprintf("Subject: (no subject)%s", nl);
1215                 }
1216                 cprintf("%s", nl);
1217         }
1218
1219         /* If the format type on disk is 1 (fixed-format), then we want
1220          * everything to be output completely literally ... regardless of
1221          * what message transfer format is in use.
1222          */
1223         if (TheMessage->cm_format_type == FMT_FIXED) {
1224                 strcpy(buf, "");
1225                 while (ch = *mptr++, ch > 0) {
1226                         if (ch == 13)
1227                                 ch = 10;
1228                         if ((ch == 10) || (strlen(buf) > 250)) {
1229                                 cprintf("%s%s", buf, nl);
1230                                 strcpy(buf, "");
1231                         } else {
1232                                 buf[strlen(buf) + 1] = 0;
1233                                 buf[strlen(buf)] = ch;
1234                         }
1235                 }
1236                 if (strlen(buf) > 0)
1237                         cprintf("%s%s", buf, nl);
1238         }
1239
1240         /* If the message on disk is format 0 (Citadel vari-format), we
1241          * output using the formatter at 80 columns.  This is the final output
1242          * form if the transfer format is RFC822, but if the transfer format
1243          * is Citadel proprietary, it'll still work, because the indentation
1244          * for new paragraphs is correct and the client will reformat the
1245          * message to the reader's screen width.
1246          */
1247         if (TheMessage->cm_format_type == FMT_CITADEL) {
1248                 memfmout(80, mptr, 0, nl);
1249         }
1250
1251         /* If the message on disk is format 4 (MIME), we've gotta hand it
1252          * off to the MIME parser.  The client has already been told that
1253          * this message is format 1 (fixed format), so the callback function
1254          * we use will display those parts as-is.
1255          */
1256         if (TheMessage->cm_format_type == FMT_RFC822) {
1257                 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1258                 memset(ma, 0, sizeof(struct ma_info));
1259                 mime_parser(mptr, NULL,
1260                         *fixed_output, *fixed_output_pre, *fixed_output_post,
1261                         NULL, 0);
1262         }
1263
1264         /* now we're done */
1265         if (do_proto) cprintf("000\n");
1266         return(om_ok);
1267 }
1268
1269
1270
1271 /*
1272  * display a message (mode 0 - Citadel proprietary)
1273  */
1274 void cmd_msg0(char *cmdbuf)
1275 {
1276         long msgid;
1277         int headers_only = 0;
1278
1279         msgid = extract_long(cmdbuf, 0);
1280         headers_only = extract_int(cmdbuf, 1);
1281
1282         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1283         return;
1284 }
1285
1286
1287 /*
1288  * display a message (mode 2 - RFC822)
1289  */
1290 void cmd_msg2(char *cmdbuf)
1291 {
1292         long msgid;
1293         int headers_only = 0;
1294
1295         msgid = extract_long(cmdbuf, 0);
1296         headers_only = extract_int(cmdbuf, 1);
1297
1298         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1299 }
1300
1301
1302
1303 /* 
1304  * display a message (mode 3 - IGnet raw format - internal programs only)
1305  */
1306 void cmd_msg3(char *cmdbuf)
1307 {
1308         long msgnum;
1309         struct CtdlMessage *msg;
1310         struct ser_ret smr;
1311
1312         if (CC->internal_pgm == 0) {
1313                 cprintf("%d This command is for internal programs only.\n",
1314                         ERROR);
1315                 return;
1316         }
1317
1318         msgnum = extract_long(cmdbuf, 0);
1319         msg = CtdlFetchMessage(msgnum);
1320         if (msg == NULL) {
1321                 cprintf("%d Message %ld not found.\n", 
1322                         ERROR, msgnum);
1323                 return;
1324         }
1325
1326         serialize_message(&smr, msg);
1327         CtdlFreeMessage(msg);
1328
1329         if (smr.len == 0) {
1330                 cprintf("%d Unable to serialize message\n",
1331                         ERROR+INTERNAL_ERROR);
1332                 return;
1333         }
1334
1335         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1336         client_write(smr.ser, smr.len);
1337         phree(smr.ser);
1338 }
1339
1340
1341
1342 /* 
1343  * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1344  */
1345 void cmd_msg4(char *cmdbuf)
1346 {
1347         long msgid;
1348
1349         msgid = extract_long(cmdbuf, 0);
1350         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1351 }
1352
1353 /*
1354  * Open a component of a MIME message as a download file 
1355  */
1356 void cmd_opna(char *cmdbuf)
1357 {
1358         long msgid;
1359
1360         CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1361
1362         msgid = extract_long(cmdbuf, 0);
1363         extract(desired_section, cmdbuf, 1);
1364
1365         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1366 }                       
1367
1368
1369 /*
1370  * Save a message pointer into a specified room
1371  * (Returns 0 for success, nonzero for failure)
1372  * roomname may be NULL to use the current room
1373  */
1374 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1375         int i;
1376         char hold_rm[ROOMNAMELEN];
1377         struct cdbdata *cdbfr;
1378         int num_msgs;
1379         long *msglist;
1380         long highest_msg = 0L;
1381         struct CtdlMessage *msg = NULL;
1382
1383         lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1384                 roomname, msgid, flags);
1385
1386         strcpy(hold_rm, CC->quickroom.QRname);
1387
1388         /* We may need to check to see if this message is real */
1389         if (  (flags & SM_VERIFY_GOODNESS)
1390            || (flags & SM_DO_REPL_CHECK)
1391            ) {
1392                 msg = CtdlFetchMessage(msgid);
1393                 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1394         }
1395
1396         /* Perform replication checks if necessary */
1397         if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1398
1399                 if (getroom(&CC->quickroom,
1400                    ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1401                    != 0) {
1402                         lprintf(9, "No such room <%s>\n", roomname);
1403                         if (msg != NULL) CtdlFreeMessage(msg);
1404                         return(ERROR + ROOM_NOT_FOUND);
1405                 }
1406
1407                 if (ReplicationChecks(msg) != 0) {
1408                         getroom(&CC->quickroom, hold_rm);
1409                         if (msg != NULL) CtdlFreeMessage(msg);
1410                         lprintf(9, "Did replication, and newer exists\n");
1411                         return(0);
1412                 }
1413         }
1414
1415         /* Now the regular stuff */
1416         if (lgetroom(&CC->quickroom,
1417            ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1418            != 0) {
1419                 lprintf(9, "No such room <%s>\n", roomname);
1420                 if (msg != NULL) CtdlFreeMessage(msg);
1421                 return(ERROR + ROOM_NOT_FOUND);
1422         }
1423
1424         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1425         if (cdbfr == NULL) {
1426                 msglist = NULL;
1427                 num_msgs = 0;
1428         } else {
1429                 msglist = mallok(cdbfr->len);
1430                 if (msglist == NULL)
1431                         lprintf(3, "ERROR malloc msglist!\n");
1432                 num_msgs = cdbfr->len / sizeof(long);
1433                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1434                 cdb_free(cdbfr);
1435         }
1436
1437
1438         /* Make sure the message doesn't already exist in this room.  It
1439          * is absolutely taboo to have more than one reference to the same
1440          * message in a room.
1441          */
1442         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1443                 if (msglist[i] == msgid) {
1444                         lputroom(&CC->quickroom);       /* unlock the room */
1445                         getroom(&CC->quickroom, hold_rm);
1446                         if (msg != NULL) CtdlFreeMessage(msg);
1447                         return(ERROR + ALREADY_EXISTS);
1448                 }
1449         }
1450
1451         /* Now add the new message */
1452         ++num_msgs;
1453         msglist = reallok(msglist,
1454                           (num_msgs * sizeof(long)));
1455
1456         if (msglist == NULL) {
1457                 lprintf(3, "ERROR: can't realloc message list!\n");
1458         }
1459         msglist[num_msgs - 1] = msgid;
1460
1461         /* Sort the message list, so all the msgid's are in order */
1462         num_msgs = sort_msglist(msglist, num_msgs);
1463
1464         /* Determine the highest message number */
1465         highest_msg = msglist[num_msgs - 1];
1466
1467         /* Write it back to disk. */
1468         cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1469                   msglist, num_msgs * sizeof(long));
1470
1471         /* Free up the memory we used. */
1472         phree(msglist);
1473
1474         /* Update the highest-message pointer and unlock the room. */
1475         CC->quickroom.QRhighest = highest_msg;
1476         lputroom(&CC->quickroom);
1477         getroom(&CC->quickroom, hold_rm);
1478
1479         /* Bump the reference count for this message. */
1480         if ((flags & SM_DONT_BUMP_REF)==0) {
1481                 AdjRefCount(msgid, +1);
1482         }
1483
1484         /* Return success. */
1485         if (msg != NULL) CtdlFreeMessage(msg);
1486         return (0);
1487 }
1488
1489
1490
1491 /*
1492  * Message base operation to send a message to the master file
1493  * (returns new message number)
1494  *
1495  * This is the back end for CtdlSubmitMsg() and should not be directly
1496  * called by server-side modules.
1497  *
1498  */
1499 long send_message(struct CtdlMessage *msg,      /* pointer to buffer */
1500                 FILE *save_a_copy)              /* save a copy to disk? */
1501 {
1502         long newmsgid;
1503         long retval;
1504         char msgidbuf[SIZ];
1505         struct ser_ret smr;
1506
1507         /* Get a new message number */
1508         newmsgid = get_new_message_number();
1509         sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1510
1511         /* Generate an ID if we don't have one already */
1512         if (msg->cm_fields['I']==NULL) {
1513                 msg->cm_fields['I'] = strdoop(msgidbuf);
1514         }
1515         
1516         serialize_message(&smr, msg);
1517
1518         if (smr.len == 0) {
1519                 cprintf("%d Unable to serialize message\n",
1520                         ERROR+INTERNAL_ERROR);
1521                 return (-1L);
1522         }
1523
1524         /* Write our little bundle of joy into the message base */
1525         if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1526                       smr.ser, smr.len) < 0) {
1527                 lprintf(2, "Can't store message\n");
1528                 retval = 0L;
1529         } else {
1530                 retval = newmsgid;
1531         }
1532
1533         /* If the caller specified that a copy should be saved to a particular
1534          * file handle, do that now too.
1535          */
1536         if (save_a_copy != NULL) {
1537                 fwrite(smr.ser, smr.len, 1, save_a_copy);
1538         }
1539
1540         /* Free the memory we used for the serialized message */
1541         phree(smr.ser);
1542
1543         /* Return the *local* message ID to the caller
1544          * (even if we're storing an incoming network message)
1545          */
1546         return(retval);
1547 }
1548
1549
1550
1551 /*
1552  * Serialize a struct CtdlMessage into the format used on disk and network.
1553  * 
1554  * This function loads up a "struct ser_ret" (defined in server.h) which
1555  * contains the length of the serialized message and a pointer to the
1556  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
1557  */
1558 void serialize_message(struct ser_ret *ret,             /* return values */
1559                         struct CtdlMessage *msg)        /* unserialized msg */
1560 {
1561         size_t wlen;
1562         int i;
1563         static char *forder = FORDER;
1564
1565         if (is_valid_message(msg) == 0) return;         /* self check */
1566
1567         ret->len = 3;
1568         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1569                 ret->len = ret->len +
1570                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
1571
1572         lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1573         ret->ser = mallok(ret->len);
1574         if (ret->ser == NULL) {
1575                 ret->len = 0;
1576                 return;
1577         }
1578
1579         ret->ser[0] = 0xFF;
1580         ret->ser[1] = msg->cm_anon_type;
1581         ret->ser[2] = msg->cm_format_type;
1582         wlen = 3;
1583
1584         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1585                 ret->ser[wlen++] = (char)forder[i];
1586                 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1587                 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1588         }
1589         if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1590                 (long)ret->len, (long)wlen);
1591
1592         return;
1593 }
1594
1595
1596
1597 /*
1598  * Back end for the ReplicationChecks() function
1599  */
1600 void check_repl(long msgnum, void *userdata) {
1601         struct CtdlMessage *msg;
1602         time_t timestamp = (-1L);
1603
1604         lprintf(9, "check_repl() found message %ld\n", msgnum);
1605         msg = CtdlFetchMessage(msgnum);
1606         if (msg == NULL) return;
1607         if (msg->cm_fields['T'] != NULL) {
1608                 timestamp = atol(msg->cm_fields['T']);
1609         }
1610         CtdlFreeMessage(msg);
1611
1612         if (timestamp > msg_repl->highest) {
1613                 msg_repl->highest = timestamp;  /* newer! */
1614                 lprintf(9, "newer!\n");
1615                 return;
1616         }
1617         lprintf(9, "older!\n");
1618
1619         /* Existing isn't newer?  Then delete the old one(s). */
1620         CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1621 }
1622
1623
1624 /*
1625  * Check to see if any messages already exist which carry the same Extended ID
1626  * as this one.  
1627  *
1628  * If any are found:
1629  * -> With older timestamps: delete them and return 0.  Message will be saved.
1630  * -> With newer timestamps: return 1.  Message save will be aborted.
1631  */
1632 int ReplicationChecks(struct CtdlMessage *msg) {
1633         struct CtdlMessage *template;
1634         int abort_this = 0;
1635
1636         lprintf(9, "ReplicationChecks() started\n");
1637         /* No extended id?  Don't do anything. */
1638         if (msg->cm_fields['E'] == NULL) return 0;
1639         if (strlen(msg->cm_fields['E']) == 0) return 0;
1640         lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1641
1642         CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1643         strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1644         msg_repl->highest = atol(msg->cm_fields['T']);
1645
1646         template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1647         memset(template, 0, sizeof(struct CtdlMessage));
1648         template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1649
1650         CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1651                 check_repl, NULL);
1652
1653         /* If a newer message exists with the same Extended ID, abort
1654          * this save.
1655          */
1656         if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1657                 abort_this = 1;
1658                 }
1659
1660         CtdlFreeMessage(template);
1661         lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1662         return(abort_this);
1663 }
1664
1665
1666
1667
1668 /*
1669  * Save a message to disk and submit it into the delivery system.
1670  */
1671 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
1672                 struct recptypes *recps,        /* recipients (if mail) */
1673                 char *force                     /* force a particular room? */
1674 ) {
1675         char aaa[SIZ];
1676         char hold_rm[ROOMNAMELEN];
1677         char actual_rm[ROOMNAMELEN];
1678         char force_room[ROOMNAMELEN];
1679         char content_type[SIZ];                 /* We have to learn this */
1680         char recipient[SIZ];
1681         long newmsgid;
1682         char *mptr = NULL;
1683         struct usersupp userbuf;
1684         int a, i;
1685         struct MetaData smi;
1686         FILE *network_fp = NULL;
1687         static int seqnum = 1;
1688         struct CtdlMessage *imsg = NULL;
1689         char *instr;
1690         struct ser_ret smr;
1691         char *hold_R, *hold_D;
1692
1693         lprintf(9, "CtdlSubmitMsg() called\n");
1694         if (is_valid_message(msg) == 0) return(-1);     /* self check */
1695
1696         /* If this message has no timestamp, we take the liberty of
1697          * giving it one, right now.
1698          */
1699         if (msg->cm_fields['T'] == NULL) {
1700                 lprintf(9, "Generating timestamp\n");
1701                 sprintf(aaa, "%ld", (long)time(NULL));
1702                 msg->cm_fields['T'] = strdoop(aaa);
1703         }
1704
1705         /* If this message has no path, we generate one.
1706          */
1707         if (msg->cm_fields['P'] == NULL) {
1708                 lprintf(9, "Generating path\n");
1709                 if (msg->cm_fields['A'] != NULL) {
1710                         msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1711                         for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1712                                 if (isspace(msg->cm_fields['P'][a])) {
1713                                         msg->cm_fields['P'][a] = ' ';
1714                                 }
1715                         }
1716                 }
1717                 else {
1718                         msg->cm_fields['P'] = strdoop("unknown");
1719                 }
1720         }
1721
1722         strcpy(force_room, force);
1723
1724         /* Learn about what's inside, because it's what's inside that counts */
1725         lprintf(9, "Learning what's inside\n");
1726         if (msg->cm_fields['M'] == NULL) {
1727                 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1728         }
1729
1730         switch (msg->cm_format_type) {
1731         case 0:
1732                 strcpy(content_type, "text/x-citadel-variformat");
1733                 break;
1734         case 1:
1735                 strcpy(content_type, "text/plain");
1736                 break;
1737         case 4:
1738                 strcpy(content_type, "text/plain");
1739                 /* advance past header fields */
1740                 mptr = msg->cm_fields['M'];
1741                 a = strlen(mptr);
1742                 while ((--a) > 0) {
1743                         if (!strncasecmp(mptr, "Content-type: ", 14)) {
1744                                 safestrncpy(content_type, mptr,
1745                                             sizeof(content_type));
1746                                 strcpy(content_type, &content_type[14]);
1747                                 for (a = 0; a < strlen(content_type); ++a)
1748                                         if ((content_type[a] == ';')
1749                                             || (content_type[a] == ' ')
1750                                             || (content_type[a] == 13)
1751                                             || (content_type[a] == 10))
1752                                                 content_type[a] = 0;
1753                                 break;
1754                         }
1755                         ++mptr;
1756                 }
1757         }
1758
1759         /* Goto the correct room */
1760         lprintf(9, "Switching rooms\n");
1761         strcpy(hold_rm, CC->quickroom.QRname);
1762         strcpy(actual_rm, CC->quickroom.QRname);
1763         if (recps != NULL) {
1764                 strcpy(actual_rm, SENTITEMS);
1765         }
1766
1767         /* If the user is a twit, move to the twit room for posting */
1768         lprintf(9, "Handling twit stuff\n");
1769         if (TWITDETECT) {
1770                 if (CC->usersupp.axlevel == 2) {
1771                         strcpy(hold_rm, actual_rm);
1772                         strcpy(actual_rm, config.c_twitroom);
1773                 }
1774         }
1775
1776         /* ...or if this message is destined for Aide> then go there. */
1777         if (strlen(force_room) > 0) {
1778                 strcpy(actual_rm, force_room);
1779         }
1780
1781         lprintf(9, "Possibly relocating\n");
1782         if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1783                 getroom(&CC->quickroom, actual_rm);
1784         }
1785
1786         /*
1787          * If this message has no O (room) field, generate one.
1788          */
1789         if (msg->cm_fields['O'] == NULL) {
1790                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1791         }
1792
1793         /* Perform "before save" hooks (aborting if any return nonzero) */
1794         lprintf(9, "Performing before-save hooks\n");
1795         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1796
1797         /* If this message has an Extended ID, perform replication checks */
1798         lprintf(9, "Performing replication checks\n");
1799         if (ReplicationChecks(msg) > 0) return(-1);
1800
1801         /* Save it to disk */
1802         lprintf(9, "Saving to disk\n");
1803         newmsgid = send_message(msg, NULL);
1804         if (newmsgid <= 0L) return(-1);
1805
1806         /* Write a supplemental message info record.  This doesn't have to
1807          * be a critical section because nobody else knows about this message
1808          * yet.
1809          */
1810         lprintf(9, "Creating MetaData record\n");
1811         memset(&smi, 0, sizeof(struct MetaData));
1812         smi.meta_msgnum = newmsgid;
1813         smi.meta_refcount = 0;
1814         safestrncpy(smi.meta_content_type, content_type, 64);
1815         PutMetaData(&smi);
1816
1817         /* Now figure out where to store the pointers */
1818         lprintf(9, "Storing pointers\n");
1819
1820         /* If this is being done by the networker delivering a private
1821          * message, we want to BYPASS saving the sender's copy (because there
1822          * is no local sender; it would otherwise go to the Trashcan).
1823          */
1824         if ((!CC->internal_pgm) || (recps == NULL)) {
1825                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1826                         lprintf(3, "ERROR saving message pointer!\n");
1827                         CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1828                 }
1829         }
1830
1831         /* For internet mail, drop a copy in the outbound queue room */
1832         if (recps != NULL)
1833          if (recps->num_internet > 0) {
1834                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1835         }
1836
1837         /* If other rooms are specified, drop them there too. */
1838         if (recps != NULL)
1839          if (recps->num_room > 0)
1840           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1841                 extract(recipient, recps->recp_room, i);
1842                 lprintf(9, "Delivering to local room <%s>\n", recipient);
1843                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1844         }
1845
1846         /* Bump this user's messages posted counter. */
1847         lprintf(9, "Updating user\n");
1848         lgetuser(&CC->usersupp, CC->curr_user);
1849         CC->usersupp.posted = CC->usersupp.posted + 1;
1850         lputuser(&CC->usersupp);
1851
1852         /* If this is private, local mail, make a copy in the
1853          * recipient's mailbox and bump the reference count.
1854          */
1855         if (recps != NULL)
1856          if (recps->num_local > 0)
1857           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1858                 extract(recipient, recps->recp_local, i);
1859                 lprintf(9, "Delivering private local mail to <%s>\n",
1860                         recipient);
1861                 if (getuser(&userbuf, recipient) == 0) {
1862                         MailboxName(actual_rm, &userbuf, MAILROOM);
1863                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1864                 }
1865                 else {
1866                         lprintf(9, "No user <%s>\n", recipient);
1867                         CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1868                 }
1869         }
1870
1871         /* Perform "after save" hooks */
1872         lprintf(9, "Performing after-save hooks\n");
1873         PerformMessageHooks(msg, EVT_AFTERSAVE);
1874
1875         /* For IGnet mail, we have to save a new copy into the spooler for
1876          * each recipient, with the R and D fields set to the recipient and
1877          * destination-node.  This has two ugly side effects: all other
1878          * recipients end up being unlisted in this recipient's copy of the
1879          * message, and it has to deliver multiple messages to the same
1880          * node.  We'll revisit this again in a year or so when everyone has
1881          * a network spool receiver that can handle the new style messages.
1882          */
1883         if (recps != NULL)
1884          if (recps->num_ignet > 0)
1885           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1886                 extract(recipient, recps->recp_ignet, i);
1887
1888                 hold_R = msg->cm_fields['R'];
1889                 hold_D = msg->cm_fields['D'];
1890                 msg->cm_fields['R'] = mallok(SIZ);
1891                 msg->cm_fields['D'] = mallok(SIZ);
1892                 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1893                 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1894                 
1895                 serialize_message(&smr, msg);
1896                 if (smr.len > 0) {
1897                         sprintf(aaa,
1898                                 "./network/spoolin/netmail.%04lx.%04x.%04x",
1899                                 (long) getpid(), CC->cs_pid, ++seqnum);
1900                         network_fp = fopen(aaa, "wb+");
1901                         if (network_fp != NULL) {
1902                                 fwrite(smr.ser, smr.len, 1, network_fp);
1903                                 fclose(network_fp);
1904                         }
1905                         phree(smr.ser);
1906                 }
1907
1908                 phree(msg->cm_fields['R']);
1909                 phree(msg->cm_fields['D']);
1910                 msg->cm_fields['R'] = hold_R;
1911                 msg->cm_fields['D'] = hold_D;
1912         }
1913
1914         /* Go back to the room we started from */
1915         lprintf(9, "Returning to original room\n");
1916         if (strcasecmp(hold_rm, CC->quickroom.QRname))
1917                 getroom(&CC->quickroom, hold_rm);
1918
1919         /* For internet mail, generate delivery instructions.
1920          * Yes, this is recursive.  Deal with it.  Infinite recursion does
1921          * not happen because the delivery instructions message does not
1922          * contain a recipient.
1923          */
1924         if (recps != NULL)
1925          if (recps->num_internet > 0) {
1926                 lprintf(9, "Generating delivery instructions\n");
1927                 instr = mallok(SIZ * 2);
1928                 sprintf(instr,
1929                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1930                         "bounceto|%s@%s\n",
1931                         SPOOLMIME, newmsgid, (long)time(NULL),
1932                         msg->cm_fields['A'], msg->cm_fields['N']
1933                 );
1934
1935                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
1936                         extract(recipient, recps->recp_internet, i);
1937                         sprintf(&instr[strlen(instr)],
1938                                 "remote|%s|0||\n", recipient);
1939                 }
1940
1941                 imsg = mallok(sizeof(struct CtdlMessage));
1942                 memset(imsg, 0, sizeof(struct CtdlMessage));
1943                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1944                 imsg->cm_anon_type = MES_NORMAL;
1945                 imsg->cm_format_type = FMT_RFC822;
1946                 imsg->cm_fields['A'] = strdoop("Citadel");
1947                 imsg->cm_fields['M'] = instr;
1948                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
1949                 CtdlFreeMessage(imsg);
1950         }
1951
1952         return(newmsgid);
1953 }
1954
1955
1956
1957 /*
1958  * Convenience function for generating small administrative messages.
1959  */
1960 void quickie_message(char *from, char *to, char *room, char *text)
1961 {
1962         struct CtdlMessage *msg;
1963
1964         msg = mallok(sizeof(struct CtdlMessage));
1965         memset(msg, 0, sizeof(struct CtdlMessage));
1966         msg->cm_magic = CTDLMESSAGE_MAGIC;
1967         msg->cm_anon_type = MES_NORMAL;
1968         msg->cm_format_type = 0;
1969         msg->cm_fields['A'] = strdoop(from);
1970         msg->cm_fields['O'] = strdoop(room);
1971         msg->cm_fields['N'] = strdoop(NODENAME);
1972         if (to != NULL)
1973                 msg->cm_fields['R'] = strdoop(to);
1974         msg->cm_fields['M'] = strdoop(text);
1975
1976         CtdlSubmitMsg(msg, NULL, room);
1977         CtdlFreeMessage(msg);
1978         syslog(LOG_NOTICE, text);
1979 }
1980
1981
1982
1983 /*
1984  * Back end function used by make_message() and similar functions
1985  */
1986 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
1987                         size_t maxlen,          /* maximum message length */
1988                         char *exist             /* if non-null, append to it;
1989                                                    exist is ALWAYS freed  */
1990                         ) {
1991         char buf[SIZ];
1992         int linelen;
1993         size_t message_len = 0;
1994         size_t buffer_len = 0;
1995         char *ptr;
1996         char *m;
1997
1998         if (exist == NULL) {
1999                 m = mallok(4096);
2000         }
2001         else {
2002                 m = reallok(exist, strlen(exist) + 4096);
2003                 if (m == NULL) phree(exist);
2004         }
2005
2006         /* flush the input if we have nowhere to store it */
2007         if (m == NULL) {
2008                 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2009                 return(NULL);
2010         }
2011
2012         /* otherwise read it into memory */
2013         else {
2014                 buffer_len = 4096;
2015                 m[0] = 0;
2016                 message_len = 0;
2017         }
2018         /* read in the lines of message text one by one */
2019         while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2020
2021                 /* strip trailing newline type stuff */
2022                 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2023                 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2024
2025                 linelen = strlen(buf);
2026
2027                 /* augment the buffer if we have to */
2028                 if ((message_len + linelen + 2) > buffer_len) {
2029                         lprintf(9, "realloking\n");
2030                         ptr = reallok(m, (buffer_len * 2) );
2031                         if (ptr == NULL) {      /* flush if can't allocate */
2032                                 while ( (client_gets(buf)>0) &&
2033                                         strcmp(buf, terminator)) ;;
2034                                 return(m);
2035                         } else {
2036                                 buffer_len = (buffer_len * 2);
2037                                 m = ptr;
2038                                 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2039                         }
2040                 }
2041
2042                 /* Add the new line to the buffer.  NOTE: this loop must avoid
2043                  * using functions like strcat() and strlen() because they
2044                  * traverse the entire buffer upon every call, and doing that
2045                  * for a multi-megabyte message slows it down beyond usability.
2046                  */
2047                 strcpy(&m[message_len], buf);
2048                 m[message_len + linelen] = '\n';
2049                 m[message_len + linelen + 1] = 0;
2050                 message_len = message_len + linelen + 1;
2051
2052                 /* if we've hit the max msg length, flush the rest */
2053                 if (message_len >= maxlen) {
2054                         while ( (client_gets(buf)>0)
2055                                 && strcmp(buf, terminator)) ;;
2056                         return(m);
2057                 }
2058         }
2059         return(m);
2060 }
2061
2062
2063
2064
2065 /*
2066  * Build a binary message to be saved on disk.
2067  */
2068
2069 static struct CtdlMessage *make_message(
2070         struct usersupp *author,        /* author's usersupp structure */
2071         char *recipient,                /* NULL if it's not mail */
2072         char *room,                     /* room where it's going */
2073         int type,                       /* see MES_ types in header file */
2074         int format_type,                /* variformat, plain text, MIME... */
2075         char *fake_name                 /* who we're masquerading as */
2076 ) {
2077         char dest_node[SIZ];
2078         char buf[SIZ];
2079         struct CtdlMessage *msg;
2080
2081         msg = mallok(sizeof(struct CtdlMessage));
2082         memset(msg, 0, sizeof(struct CtdlMessage));
2083         msg->cm_magic = CTDLMESSAGE_MAGIC;
2084         msg->cm_anon_type = type;
2085         msg->cm_format_type = format_type;
2086
2087         /* Don't confuse the poor folks if it's not routed mail. */
2088         strcpy(dest_node, "");
2089
2090         striplt(recipient);
2091
2092         sprintf(buf, "cit%ld", author->usernum);                /* Path */
2093         msg->cm_fields['P'] = strdoop(buf);
2094
2095         sprintf(buf, "%ld", (long)time(NULL));                  /* timestamp */
2096         msg->cm_fields['T'] = strdoop(buf);
2097
2098         if (fake_name[0])                                       /* author */
2099                 msg->cm_fields['A'] = strdoop(fake_name);
2100         else
2101                 msg->cm_fields['A'] = strdoop(author->fullname);
2102
2103         if (CC->quickroom.QRflags & QR_MAILBOX) {               /* room */
2104                 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2105         }
2106         else {
2107                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2108         }
2109
2110         msg->cm_fields['N'] = strdoop(NODENAME);                /* nodename */
2111         msg->cm_fields['H'] = strdoop(HUMANNODE);               /* hnodename */
2112
2113         if (recipient[0] != 0) {
2114                 msg->cm_fields['R'] = strdoop(recipient);
2115         }
2116         if (dest_node[0] != 0) {
2117                 msg->cm_fields['D'] = strdoop(dest_node);
2118         }
2119
2120         msg->cm_fields['M'] = CtdlReadMessageBody("000",
2121                                                 config.c_maxmsglen, NULL);
2122
2123         return(msg);
2124 }
2125
2126
2127 /*
2128  * Check to see whether we have permission to post a message in the current
2129  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2130  * returns 0 on success.
2131  */
2132 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2133
2134         if (!(CC->logged_in)) {
2135                 sprintf(errmsgbuf, "Not logged in.");
2136                 return (ERROR + NOT_LOGGED_IN);
2137         }
2138
2139         if ((CC->usersupp.axlevel < 2)
2140             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2141                 sprintf(errmsgbuf, "Need to be validated to enter "
2142                                 "(except in %s> to sysop)", MAILROOM);
2143                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2144         }
2145
2146         if ((CC->usersupp.axlevel < 4)
2147            && (CC->quickroom.QRflags & QR_NETWORK)) {
2148                 sprintf(errmsgbuf, "Need net privileges to enter here.");
2149                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2150         }
2151
2152         if ((CC->usersupp.axlevel < 6)
2153            && (CC->quickroom.QRflags & QR_READONLY)) {
2154                 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2155                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2156         }
2157
2158         strcpy(errmsgbuf, "Ok");
2159         return(0);
2160 }
2161
2162
2163 /*
2164  * Validate recipients, count delivery types and errors, and handle aliasing
2165  * FIXME check for dupes!!!!!
2166  */
2167 struct recptypes *validate_recipients(char *recipients) {
2168         struct recptypes *ret;
2169         char this_recp[SIZ];
2170         char append[SIZ];
2171         int num_recps;
2172         int i;
2173         int mailtype;
2174         int invalid;
2175         struct usersupp tempUS;
2176
2177         /* Initialize */
2178         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2179         if (ret == NULL) return(NULL);
2180         memset(ret, 0, sizeof(struct recptypes));
2181
2182         ret->num_local = 0;
2183         ret->num_internet = 0;
2184         ret->num_ignet = 0;
2185         ret->num_error = 0;
2186
2187         if (recipients == NULL) {
2188                 num_recps = 0;
2189         }
2190         else if (strlen(recipients) == 0) {
2191                 num_recps = 0;
2192         }
2193         else {
2194                 /* Change all valid separator characters to commas */
2195                 for (i=0; i<strlen(recipients); ++i) {
2196                         if ((recipients[i] == ';') || (recipients[i] == '|')) {
2197                                 recipients[i] = ',';
2198                         }
2199                 }
2200
2201                 /* Count 'em up */
2202                 num_recps = num_tokens(recipients, ',');
2203         }
2204
2205         if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2206                 extract_token(this_recp, recipients, i, ',');
2207                 striplt(this_recp);
2208                 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2209                 mailtype = alias(this_recp);
2210                 invalid = 0;
2211                 switch(mailtype) {
2212                         case MES_LOCAL:
2213                                 if (!strcasecmp(this_recp, "sysop")) {
2214                                         ++ret->num_room;
2215                                         strcpy(this_recp, AIDEROOM);
2216                                         if (strlen(ret->recp_room) > 0) {
2217                                                 strcat(ret->recp_room, "|");
2218                                         }
2219                                         strcat(ret->recp_room, this_recp);
2220                                 }
2221                                 else if (getuser(&tempUS, this_recp) == 0) {
2222                                         ++ret->num_local;
2223                                         strcpy(this_recp, tempUS.fullname);
2224                                         if (strlen(ret->recp_local) > 0) {
2225                                                 strcat(ret->recp_local, "|");
2226                                         }
2227                                         strcat(ret->recp_local, this_recp);
2228                                 }
2229                                 else {
2230                                         ++ret->num_error;
2231                                         invalid = 1;
2232                                 }
2233                                 break;
2234                         case MES_INTERNET:
2235                                 ++ret->num_internet;
2236                                 if (strlen(ret->recp_internet) > 0) {
2237                                         strcat(ret->recp_internet, "|");
2238                                 }
2239                                 strcat(ret->recp_internet, this_recp);
2240                                 break;
2241                         case MES_IGNET:
2242                                 ++ret->num_ignet;
2243                                 if (strlen(ret->recp_ignet) > 0) {
2244                                         strcat(ret->recp_ignet, "|");
2245                                 }
2246                                 strcat(ret->recp_ignet, this_recp);
2247                                 break;
2248                         case MES_ERROR:
2249                                 ++ret->num_error;
2250                                 invalid = 1;
2251                                 break;
2252                 }
2253                 if (invalid) {
2254                         if (strlen(ret->errormsg) == 0) {
2255                                 sprintf(append,
2256                                         "Invalid recipient: %s",
2257                                         this_recp);
2258                         }
2259                         else {
2260                                 sprintf(append, 
2261                                         ", %s", this_recp);
2262                         }
2263                         if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2264                                 strcat(ret->errormsg, append);
2265                         }
2266                 }
2267                 else {
2268                         if (strlen(ret->display_recp) == 0) {
2269                                 strcpy(append, this_recp);
2270                         }
2271                         else {
2272                                 sprintf(append, ", %s", this_recp);
2273                         }
2274                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2275                                 strcat(ret->display_recp, append);
2276                         }
2277                 }
2278         }
2279
2280         if ((ret->num_local + ret->num_internet + ret->num_ignet +
2281            ret->num_room + ret->num_error) == 0) {
2282                 ++ret->num_error;
2283                 strcpy(ret->errormsg, "No recipients specified.");
2284         }
2285
2286         return(ret);
2287 }
2288
2289
2290
2291 /*
2292  * message entry  -  mode 0 (normal)
2293  */
2294 void cmd_ent0(char *entargs)
2295 {
2296         int post = 0;
2297         char recp[SIZ];
2298         char masquerade_as[SIZ];
2299         int anon_flag = 0;
2300         int format_type = 0;
2301         char newusername[SIZ];
2302         struct CtdlMessage *msg;
2303         int anonymous = 0;
2304         char errmsg[SIZ];
2305         int err = 0;
2306         struct recptypes *valid = NULL;
2307
2308         post = extract_int(entargs, 0);
2309         extract(recp, entargs, 1);
2310         anon_flag = extract_int(entargs, 2);
2311         format_type = extract_int(entargs, 3);
2312
2313         /* first check to make sure the request is valid. */
2314
2315         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg);
2316         if (err) {
2317                 cprintf("%d %s\n", err, errmsg);
2318                 return;
2319         }
2320
2321         /* Check some other permission type things. */
2322
2323         if (post == 2) {
2324                 if (CC->usersupp.axlevel < 6) {
2325                         cprintf("%d You don't have permission to masquerade.\n",
2326                                 ERROR + HIGHER_ACCESS_REQUIRED);
2327                         return;
2328                 }
2329                 extract(newusername, entargs, 4);
2330                 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2331                 safestrncpy(CC->fake_postname, newusername,
2332                         sizeof(CC->fake_postname) );
2333                 cprintf("%d ok\n", OK);
2334                 return;
2335         }
2336         CC->cs_flags |= CS_POSTING;
2337
2338         if (CC->quickroom.QRflags & QR_MAILBOX) {
2339                 if (CC->usersupp.axlevel < 2) {
2340                         strcpy(recp, "sysop");
2341                 }
2342
2343                 valid = validate_recipients(recp);
2344                 if (valid->num_error > 0) {
2345                         cprintf("%d %s\n",
2346                                 ERROR + NO_SUCH_USER, valid->errormsg);
2347                         phree(valid);
2348                         return;
2349                 }
2350
2351                 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2352                    && (CC->usersupp.axlevel < 4) ) {
2353                         cprintf("%d Higher access required for network mail.\n",
2354                                 ERROR + HIGHER_ACCESS_REQUIRED);
2355                         phree(valid);
2356                         return;
2357                 }
2358         
2359                 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2360                     && ((CC->usersupp.flags & US_INTERNET) == 0)
2361                     && (!CC->internal_pgm)) {
2362                         cprintf("%d You don't have access to Internet mail.\n",
2363                                 ERROR + HIGHER_ACCESS_REQUIRED);
2364                         phree(valid);
2365                         return;
2366                 }
2367
2368         }
2369
2370         /* Is this a room which has anonymous-only or anonymous-option? */
2371         anonymous = MES_NORMAL;
2372         if (CC->quickroom.QRflags & QR_ANONONLY) {
2373                 anonymous = MES_ANONONLY;
2374         }
2375         if (CC->quickroom.QRflags & QR_ANONOPT) {
2376                 if (anon_flag == 1) {   /* only if the user requested it */
2377                         anonymous = MES_ANONOPT;
2378                 }
2379         }
2380
2381         if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2382                 recp[0] = 0;
2383         }
2384
2385         /* If we're only checking the validity of the request, return
2386          * success without creating the message.
2387          */
2388         if (post == 0) {
2389                 cprintf("%d %s\n", OK,
2390                         ((valid != NULL) ? valid->display_recp : "") );
2391                 phree(valid);
2392                 return;
2393         }
2394
2395         /* Handle author masquerading */
2396         if (CC->fake_postname[0]) {
2397                 strcpy(masquerade_as, CC->fake_postname);
2398         }
2399         else if (CC->fake_username[0]) {
2400                 strcpy(masquerade_as, CC->fake_username);
2401         }
2402         else {
2403                 strcpy(masquerade_as, "");
2404         }
2405
2406         /* Read in the message from the client. */
2407         cprintf("%d send message\n", SEND_LISTING);
2408         msg = make_message(&CC->usersupp, recp,
2409                 CC->quickroom.QRname, anonymous, format_type, masquerade_as);
2410
2411         if (msg != NULL) {
2412                 CtdlSubmitMsg(msg, valid, "");
2413                 CtdlFreeMessage(msg);
2414         }
2415         CC->fake_postname[0] = '\0';
2416         phree(valid);
2417         return;
2418 }
2419
2420
2421
2422 /*
2423  * API function to delete messages which match a set of criteria
2424  * (returns the actual number of messages deleted)
2425  */
2426 int CtdlDeleteMessages(char *room_name,         /* which room */
2427                        long dmsgnum,            /* or "0" for any */
2428                        char *content_type       /* or "" for any */
2429 )
2430 {
2431
2432         struct quickroom qrbuf;
2433         struct cdbdata *cdbfr;
2434         long *msglist = NULL;
2435         int num_msgs = 0;
2436         int i;
2437         int num_deleted = 0;
2438         int delete_this;
2439         struct MetaData smi;
2440
2441         lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2442                 room_name, dmsgnum, content_type);
2443
2444         /* get room record, obtaining a lock... */
2445         if (lgetroom(&qrbuf, room_name) != 0) {
2446                 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2447                         room_name);
2448                 return (0);     /* room not found */
2449         }
2450         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2451
2452         if (cdbfr != NULL) {
2453                 msglist = mallok(cdbfr->len);
2454                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2455                 num_msgs = cdbfr->len / sizeof(long);
2456                 cdb_free(cdbfr);
2457         }
2458         if (num_msgs > 0) {
2459                 for (i = 0; i < num_msgs; ++i) {
2460                         delete_this = 0x00;
2461
2462                         /* Set/clear a bit for each criterion */
2463
2464                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2465                                 delete_this |= 0x01;
2466                         }
2467                         if (strlen(content_type) == 0) {
2468                                 delete_this |= 0x02;
2469                         } else {
2470                                 GetMetaData(&smi, msglist[i]);
2471                                 if (!strcasecmp(smi.meta_content_type,
2472                                                 content_type)) {
2473                                         delete_this |= 0x02;
2474                                 }
2475                         }
2476
2477                         /* Delete message only if all bits are set */
2478                         if (delete_this == 0x03) {
2479                                 AdjRefCount(msglist[i], -1);
2480                                 msglist[i] = 0L;
2481                                 ++num_deleted;
2482                         }
2483                 }
2484
2485                 num_msgs = sort_msglist(msglist, num_msgs);
2486                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2487                           msglist, (num_msgs * sizeof(long)));
2488
2489                 qrbuf.QRhighest = msglist[num_msgs - 1];
2490                 phree(msglist);
2491         }
2492         lputroom(&qrbuf);
2493         lprintf(9, "%d message(s) deleted.\n", num_deleted);
2494         return (num_deleted);
2495 }
2496
2497
2498
2499 /*
2500  * Check whether the current user has permission to delete messages from
2501  * the current room (returns 1 for yes, 0 for no)
2502  */
2503 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2504         getuser(&CC->usersupp, CC->curr_user);
2505         if ((CC->usersupp.axlevel < 6)
2506             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2507             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2508             && (!(CC->internal_pgm))) {
2509                 return(0);
2510         }
2511         return(1);
2512 }
2513
2514
2515
2516 /*
2517  * Delete message from current room
2518  */
2519 void cmd_dele(char *delstr)
2520 {
2521         long delnum;
2522         int num_deleted;
2523
2524         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2525                 cprintf("%d Higher access required.\n",
2526                         ERROR + HIGHER_ACCESS_REQUIRED);
2527                 return;
2528         }
2529         delnum = extract_long(delstr, 0);
2530
2531         num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2532
2533         if (num_deleted) {
2534                 cprintf("%d %d message%s deleted.\n", OK,
2535                         num_deleted, ((num_deleted != 1) ? "s" : ""));
2536         } else {
2537                 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2538         }
2539 }
2540
2541
2542 /*
2543  * Back end API function for moves and deletes
2544  */
2545 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2546         int err;
2547
2548         err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2549                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2550         if (err != 0) return(err);
2551
2552         return(0);
2553 }
2554
2555
2556
2557 /*
2558  * move or copy a message to another room
2559  */
2560 void cmd_move(char *args)
2561 {
2562         long num;
2563         char targ[SIZ];
2564         struct quickroom qtemp;
2565         int err;
2566         int is_copy = 0;
2567
2568         num = extract_long(args, 0);
2569         extract(targ, args, 1);
2570         targ[ROOMNAMELEN - 1] = 0;
2571         is_copy = extract_int(args, 2);
2572
2573         getuser(&CC->usersupp, CC->curr_user);
2574         if ((CC->usersupp.axlevel < 6)
2575             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2576                 cprintf("%d Higher access required.\n",
2577                         ERROR + HIGHER_ACCESS_REQUIRED);
2578                 return;
2579         }
2580
2581         if (getroom(&qtemp, targ) != 0) {
2582                 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2583                 return;
2584         }
2585
2586         err = CtdlCopyMsgToRoom(num, targ);
2587         if (err != 0) {
2588                 cprintf("%d Cannot store message in %s: error %d\n",
2589                         err, targ, err);
2590                 return;
2591         }
2592
2593         /* Now delete the message from the source room,
2594          * if this is a 'move' rather than a 'copy' operation.
2595          */
2596         if (is_copy == 0) {
2597                 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2598         }
2599
2600         cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2601 }
2602
2603
2604
2605 /*
2606  * GetMetaData()  -  Get the supplementary record for a message
2607  */
2608 void GetMetaData(struct MetaData *smibuf, long msgnum)
2609 {
2610
2611         struct cdbdata *cdbsmi;
2612         long TheIndex;
2613
2614         memset(smibuf, 0, sizeof(struct MetaData));
2615         smibuf->meta_msgnum = msgnum;
2616         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
2617
2618         /* Use the negative of the message number for its supp record index */
2619         TheIndex = (0L - msgnum);
2620
2621         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2622         if (cdbsmi == NULL) {
2623                 return;         /* record not found; go with defaults */
2624         }
2625         memcpy(smibuf, cdbsmi->ptr,
2626                ((cdbsmi->len > sizeof(struct MetaData)) ?
2627                 sizeof(struct MetaData) : cdbsmi->len));
2628         cdb_free(cdbsmi);
2629         return;
2630 }
2631
2632
2633 /*
2634  * PutMetaData()  -  (re)write supplementary record for a message
2635  */
2636 void PutMetaData(struct MetaData *smibuf)
2637 {
2638         long TheIndex;
2639
2640         /* Use the negative of the message number for the metadata db index */
2641         TheIndex = (0L - smibuf->meta_msgnum);
2642
2643         lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2644                 smibuf->meta_msgnum, smibuf->meta_refcount);
2645
2646         cdb_store(CDB_MSGMAIN,
2647                   &TheIndex, sizeof(long),
2648                   smibuf, sizeof(struct MetaData));
2649
2650 }
2651
2652 /*
2653  * AdjRefCount  -  change the reference count for a message;
2654  *                 delete the message if it reaches zero
2655  */
2656 void AdjRefCount(long msgnum, int incr)
2657 {
2658
2659         struct MetaData smi;
2660         long delnum;
2661
2662         /* This is a *tight* critical section; please keep it that way, as
2663          * it may get called while nested in other critical sections.  
2664          * Complicating this any further will surely cause deadlock!
2665          */
2666         begin_critical_section(S_SUPPMSGMAIN);
2667         GetMetaData(&smi, msgnum);
2668         lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2669                 msgnum, smi.meta_refcount);
2670         smi.meta_refcount += incr;
2671         PutMetaData(&smi);
2672         end_critical_section(S_SUPPMSGMAIN);
2673         lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2674                 msgnum, smi.meta_refcount);
2675
2676         /* If the reference count is now zero, delete the message
2677          * (and its supplementary record as well).
2678          */
2679         if (smi.meta_refcount == 0) {
2680                 lprintf(9, "Deleting message <%ld>\n", msgnum);
2681                 delnum = msgnum;
2682                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2683
2684                 /* We have to delete the metadata record too! */
2685                 delnum = (0L - msgnum);
2686                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2687         }
2688 }
2689
2690 /*
2691  * Write a generic object to this room
2692  *
2693  * Note: this could be much more efficient.  Right now we use two temporary
2694  * files, and still pull the message into memory as with all others.
2695  */
2696 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
2697                         char *content_type,     /* MIME type of this object */
2698                         char *tempfilename,     /* Where to fetch it from */
2699                         struct usersupp *is_mailbox,    /* Mailbox room? */
2700                         int is_binary,          /* Is encoding necessary? */
2701                         int is_unique,          /* Del others of this type? */
2702                         unsigned int flags      /* Internal save flags */
2703                         )
2704 {
2705
2706         FILE *fp, *tempfp;
2707         char filename[PATH_MAX];
2708         char cmdbuf[SIZ];
2709         char ch;
2710         struct quickroom qrbuf;
2711         char roomname[ROOMNAMELEN];
2712         struct CtdlMessage *msg;
2713         size_t len;
2714
2715         if (is_mailbox != NULL)
2716                 MailboxName(roomname, is_mailbox, req_room);
2717         else
2718                 safestrncpy(roomname, req_room, sizeof(roomname));
2719         lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2720
2721         strcpy(filename, tmpnam(NULL));
2722         fp = fopen(filename, "w");
2723         if (fp == NULL)
2724                 return;
2725
2726         tempfp = fopen(tempfilename, "r");
2727         if (tempfp == NULL) {
2728                 fclose(fp);
2729                 unlink(filename);
2730                 return;
2731         }
2732
2733         fprintf(fp, "Content-type: %s\n", content_type);
2734         lprintf(9, "Content-type: %s\n", content_type);
2735
2736         if (is_binary == 0) {
2737                 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2738                 while (ch = getc(tempfp), ch > 0)
2739                         putc(ch, fp);
2740                 fclose(tempfp);
2741                 putc(0, fp);
2742                 fclose(fp);
2743         } else {
2744                 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2745                 fclose(tempfp);
2746                 fclose(fp);
2747                 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2748                         tempfilename, filename);
2749                 system(cmdbuf);
2750         }
2751
2752         lprintf(9, "Allocating\n");
2753         msg = mallok(sizeof(struct CtdlMessage));
2754         memset(msg, 0, sizeof(struct CtdlMessage));
2755         msg->cm_magic = CTDLMESSAGE_MAGIC;
2756         msg->cm_anon_type = MES_NORMAL;
2757         msg->cm_format_type = 4;
2758         msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2759         msg->cm_fields['O'] = strdoop(req_room);
2760         msg->cm_fields['N'] = strdoop(config.c_nodename);
2761         msg->cm_fields['H'] = strdoop(config.c_humannode);
2762         msg->cm_flags = flags;
2763         
2764         lprintf(9, "Loading\n");
2765         fp = fopen(filename, "rb");
2766         fseek(fp, 0L, SEEK_END);
2767         len = ftell(fp);
2768         rewind(fp);
2769         msg->cm_fields['M'] = mallok(len);
2770         fread(msg->cm_fields['M'], len, 1, fp);
2771         fclose(fp);
2772         unlink(filename);
2773
2774         /* Create the requested room if we have to. */
2775         if (getroom(&qrbuf, roomname) != 0) {
2776                 create_room(roomname, 
2777                         ( (is_mailbox != NULL) ? 5 : 3 ),
2778                         "", 0, 1);
2779         }
2780         /* If the caller specified this object as unique, delete all
2781          * other objects of this type that are currently in the room.
2782          */
2783         if (is_unique) {
2784                 lprintf(9, "Deleted %d other msgs of this type\n",
2785                         CtdlDeleteMessages(roomname, 0L, content_type));
2786         }
2787         /* Now write the data */
2788         CtdlSubmitMsg(msg, NULL, roomname);
2789         CtdlFreeMessage(msg);
2790 }
2791
2792
2793
2794
2795
2796
2797 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2798         config_msgnum = msgnum;
2799 }
2800
2801
2802 char *CtdlGetSysConfig(char *sysconfname) {
2803         char hold_rm[ROOMNAMELEN];
2804         long msgnum;
2805         char *conf;
2806         struct CtdlMessage *msg;
2807         char buf[SIZ];
2808         
2809         strcpy(hold_rm, CC->quickroom.QRname);
2810         if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2811                 getroom(&CC->quickroom, hold_rm);
2812                 return NULL;
2813         }
2814
2815
2816         /* We want the last (and probably only) config in this room */
2817         begin_critical_section(S_CONFIG);
2818         config_msgnum = (-1L);
2819         CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2820                 CtdlGetSysConfigBackend, NULL);
2821         msgnum = config_msgnum;
2822         end_critical_section(S_CONFIG);
2823
2824         if (msgnum < 0L) {
2825                 conf = NULL;
2826         }
2827         else {
2828                 msg = CtdlFetchMessage(msgnum);
2829                 if (msg != NULL) {
2830                         conf = strdoop(msg->cm_fields['M']);
2831                         CtdlFreeMessage(msg);
2832                 }
2833                 else {
2834                         conf = NULL;
2835                 }
2836         }
2837
2838         getroom(&CC->quickroom, hold_rm);
2839
2840         if (conf != NULL) do {
2841                 extract_token(buf, conf, 0, '\n');
2842                 strcpy(conf, &conf[strlen(buf)+1]);
2843         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2844
2845         return(conf);
2846 }
2847
2848 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2849         char temp[PATH_MAX];
2850         FILE *fp;
2851
2852         strcpy(temp, tmpnam(NULL));
2853
2854         fp = fopen(temp, "w");
2855         if (fp == NULL) return;
2856         fprintf(fp, "%s", sysconfdata);
2857         fclose(fp);
2858
2859         /* this handy API function does all the work for us */
2860         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
2861         unlink(temp);
2862 }