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