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