]> code.citadel.org Git - citadel.git/blob - citadel/msgbase.c
* Added client and server side support for entering Subject lines in
[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
986         /* buffers needed for RFC822 translation */
987         char suser[SIZ];
988         char luser[SIZ];
989         char fuser[SIZ];
990         char snode[SIZ];
991         char lnode[SIZ];
992         char mid[SIZ];
993         char datestamp[SIZ];
994         /*                                       */
995
996         sprintf(mid, "%ld", msg_num);
997         nl = (crlf ? "\r\n" : "\n");
998
999         if (!is_valid_message(TheMessage)) {
1000                 lprintf(1, "ERROR: invalid preloaded message for output\n");
1001                 return(om_no_such_msg);
1002         }
1003
1004         /* Are we downloading a MIME component? */
1005         if (mode == MT_DOWNLOAD) {
1006                 if (TheMessage->cm_format_type != FMT_RFC822) {
1007                         if (do_proto)
1008                                 cprintf("%d This is not a MIME message.\n",
1009                                 ERROR);
1010                 } else if (CC->download_fp != NULL) {
1011                         if (do_proto) cprintf(
1012                                 "%d You already have a download open.\n",
1013                                 ERROR);
1014                 } else {
1015                         /* Parse the message text component */
1016                         mptr = TheMessage->cm_fields['M'];
1017                         mime_parser(mptr, NULL,
1018                                 *mime_download, NULL, NULL,
1019                                 NULL, 0);
1020                         /* If there's no file open by this time, the requested
1021                          * section wasn't found, so print an error
1022                          */
1023                         if (CC->download_fp == NULL) {
1024                                 if (do_proto) cprintf(
1025                                         "%d Section %s not found.\n",
1026                                         ERROR + FILE_NOT_FOUND,
1027                                         desired_section);
1028                         }
1029                 }
1030                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1031         }
1032
1033         /* now for the user-mode message reading loops */
1034         if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1035
1036         /* Tell the client which format type we're using.  If this is a
1037          * MIME message, *lie* about it and tell the user it's fixed-format.
1038          */
1039         if (mode == MT_CITADEL) {
1040                 if (TheMessage->cm_format_type == FMT_RFC822) {
1041                         if (do_proto) cprintf("type=1\n");
1042                 }
1043                 else {
1044                         if (do_proto) cprintf("type=%d\n",
1045                                 TheMessage->cm_format_type);
1046                 }
1047         }
1048
1049         /* nhdr=yes means that we're only displaying headers, no body */
1050         if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1051                 if (do_proto) cprintf("nhdr=yes\n");
1052         }
1053
1054         /* begin header processing loop for Citadel message format */
1055
1056         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1057
1058                 strcpy(display_name, "<unknown>");
1059                 if (TheMessage->cm_fields['A']) {
1060                         strcpy(buf, TheMessage->cm_fields['A']);
1061                         PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1062                         if (TheMessage->cm_anon_type == MES_ANONONLY) {
1063                                 strcpy(display_name, "****");
1064                         }
1065                         else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1066                                 strcpy(display_name, "anonymous");
1067                         }
1068                         else {
1069                                 strcpy(display_name, buf);
1070                         }
1071                         if ((is_room_aide())
1072                             && ((TheMessage->cm_anon_type == MES_ANONONLY)
1073                              || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1074                                 sprintf(&display_name[strlen(display_name)],
1075                                         " [%s]", buf);
1076                         }
1077                 }
1078
1079                 strcpy(allkeys, FORDER);
1080                 for (i=0; i<strlen(allkeys); ++i) {
1081                         k = (int) allkeys[i];
1082                         if (k != 'M') {
1083                                 if ( (TheMessage->cm_fields[k] != NULL)
1084                                    && (msgkeys[k] != NULL) ) {
1085                                         if (k == 'A') {
1086                                                 if (do_proto) cprintf("%s=%s\n",
1087                                                         msgkeys[k],
1088                                                         display_name);
1089                                         }
1090                                         /* Don't show Internet address for
1091                                          * local users
1092                                          */
1093                                         else if (k == 'F') {
1094                                                 if (do_proto) if (TheMessage->cm_fields['N'] != NULL) if (strcasecmp(TheMessage->cm_fields['N'], config.c_nodename)) {
1095                                                         cprintf("%s=%s\n",
1096                                                                 msgkeys[k],
1097                                                                 TheMessage->cm_fields[k]
1098                                                         );
1099
1100                                                 }
1101                                         }
1102                                         /* Masquerade display name if needed */
1103                                         else {
1104                                                 if (do_proto) cprintf("%s=%s\n",
1105                                                         msgkeys[k],
1106                                                         TheMessage->cm_fields[k]
1107                                         );
1108                                         }
1109                                 }
1110                         }
1111                 }
1112
1113         }
1114
1115         /* begin header processing loop for RFC822 transfer format */
1116
1117         strcpy(suser, "");
1118         strcpy(luser, "");
1119         strcpy(fuser, "");
1120         strcpy(snode, NODENAME);
1121         strcpy(lnode, HUMANNODE);
1122         if (mode == MT_RFC822) {
1123                 cprintf("X-UIDL: %ld%s", msg_num, nl);
1124                 for (i = 0; i < 256; ++i) {
1125                         if (TheMessage->cm_fields[i]) {
1126                                 mptr = TheMessage->cm_fields[i];
1127
1128                                 if (i == 'A') {
1129                                         strcpy(luser, mptr);
1130                                         strcpy(suser, mptr);
1131                                 }
1132 /****
1133  "Path:" removed for now because it confuses brain-dead Microsoft shitware
1134  into thinking that mail messages are newsgroup messages instead.  When we
1135  add NNTP support back into Citadel we'll have to add code to only output
1136  this field when appropriate.
1137                                 else if (i == 'P') {
1138                                         cprintf("Path: %s%s", mptr, nl);
1139                                 }
1140  ****/
1141                                 else if (i == 'U')
1142                                         cprintf("Subject: %s%s", mptr, nl);
1143                                 else if (i == 'I')
1144                                         safestrncpy(mid, mptr, sizeof mid);
1145                                 else if (i == 'H')
1146                                         safestrncpy(lnode, mptr, sizeof lnode);
1147                                 else if (i == 'F')
1148                                         safestrncpy(fuser, mptr, sizeof fuser);
1149                                 else if (i == 'O')
1150                                         cprintf("X-Citadel-Room: %s%s",
1151                                                 mptr, nl);
1152                                 else if (i == 'N')
1153                                         safestrncpy(snode, mptr, sizeof snode);
1154                                 else if (i == 'R')
1155                                         cprintf("To: %s%s", mptr, nl);
1156                                 else if (i == 'T') {
1157                                         datestring(datestamp, atol(mptr),
1158                                                 DATESTRING_RFC822 );
1159                                         cprintf("Date: %s%s", datestamp, nl);
1160                                 }
1161                         }
1162                 }
1163         }
1164
1165         for (i=0; i<strlen(suser); ++i) {
1166                 suser[i] = tolower(suser[i]);
1167                 if (!isalnum(suser[i])) suser[i]='_';
1168         }
1169
1170         if (mode == MT_RFC822) {
1171                 if (!strcasecmp(snode, NODENAME)) {
1172                         strcpy(snode, FQDN);
1173                 }
1174
1175                 /* Construct a fun message id */
1176                 cprintf("Message-ID: <%s", mid);
1177                 if (strchr(mid, '@')==NULL) {
1178                         cprintf("@%s", snode);
1179                 }
1180                 cprintf(">%s", nl);
1181
1182                 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1183
1184                 if (strlen(fuser) > 0) {
1185                         cprintf("From: %s (%s)%s", fuser, luser, nl);
1186                 }
1187                 else {
1188                         cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1189                 }
1190
1191                 cprintf("Organization: %s%s", lnode, nl);
1192         }
1193
1194         /* end header processing loop ... at this point, we're in the text */
1195
1196         mptr = TheMessage->cm_fields['M'];
1197
1198         /* Tell the client about the MIME parts in this message */
1199         if (TheMessage->cm_format_type == FMT_RFC822) {
1200                 if (mode == MT_CITADEL) {
1201                         mime_parser(mptr, NULL,
1202                                 *list_this_part, NULL, NULL,
1203                                 NULL, 0);
1204                 }
1205                 else if (mode == MT_MIME) {     /* list parts only */
1206                         mime_parser(mptr, NULL,
1207                                 *list_this_part, NULL, NULL,
1208                                 NULL, 0);
1209                         if (do_proto) cprintf("000\n");
1210                         return(om_ok);
1211                 }
1212                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1213                         /* FIXME ... we have to put some code in here to avoid
1214                          * printing duplicate header information when both
1215                          * Citadel and RFC822 headers exist.  Preference should
1216                          * probably be given to the RFC822 headers.
1217                          */
1218                         while (ch=*(mptr++), ch!=0) {
1219                                 if (ch==13) ;
1220                                 else if (ch==10) cprintf("%s", nl);
1221                                 else cprintf("%c", ch);
1222                         }
1223                         if (do_proto) cprintf("000\n");
1224                         return(om_ok);
1225                 }
1226         }
1227
1228         if (headers_only) {
1229                 if (do_proto) cprintf("000\n");
1230                 return(om_ok);
1231         }
1232
1233         /* signify start of msg text */
1234         if (mode == MT_CITADEL)
1235                 if (do_proto) cprintf("text\n");
1236         if (mode == MT_RFC822) {
1237                 if (TheMessage->cm_fields['U'] == NULL) {
1238                         cprintf("Subject: (no subject)%s", nl);
1239                 }
1240                 cprintf("%s", nl);
1241         }
1242
1243         /* If the format type on disk is 1 (fixed-format), then we want
1244          * everything to be output completely literally ... regardless of
1245          * what message transfer format is in use.
1246          */
1247         if (TheMessage->cm_format_type == FMT_FIXED) {
1248                 strcpy(buf, "");
1249                 while (ch = *mptr++, ch > 0) {
1250                         if (ch == 13)
1251                                 ch = 10;
1252                         if ((ch == 10) || (strlen(buf) > 250)) {
1253                                 cprintf("%s%s", buf, nl);
1254                                 strcpy(buf, "");
1255                         } else {
1256                                 buf[strlen(buf) + 1] = 0;
1257                                 buf[strlen(buf)] = ch;
1258                         }
1259                 }
1260                 if (strlen(buf) > 0)
1261                         cprintf("%s%s", buf, nl);
1262         }
1263
1264         /* If the message on disk is format 0 (Citadel vari-format), we
1265          * output using the formatter at 80 columns.  This is the final output
1266          * form if the transfer format is RFC822, but if the transfer format
1267          * is Citadel proprietary, it'll still work, because the indentation
1268          * for new paragraphs is correct and the client will reformat the
1269          * message to the reader's screen width.
1270          */
1271         if (TheMessage->cm_format_type == FMT_CITADEL) {
1272                 memfmout(80, mptr, 0, nl);
1273         }
1274
1275         /* If the message on disk is format 4 (MIME), we've gotta hand it
1276          * off to the MIME parser.  The client has already been told that
1277          * this message is format 1 (fixed format), so the callback function
1278          * we use will display those parts as-is.
1279          */
1280         if (TheMessage->cm_format_type == FMT_RFC822) {
1281                 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1282                 memset(ma, 0, sizeof(struct ma_info));
1283                 mime_parser(mptr, NULL,
1284                         *fixed_output, *fixed_output_pre, *fixed_output_post,
1285                         NULL, 0);
1286         }
1287
1288         /* now we're done */
1289         if (do_proto) cprintf("000\n");
1290         return(om_ok);
1291 }
1292
1293
1294
1295 /*
1296  * display a message (mode 0 - Citadel proprietary)
1297  */
1298 void cmd_msg0(char *cmdbuf)
1299 {
1300         long msgid;
1301         int headers_only = 0;
1302
1303         msgid = extract_long(cmdbuf, 0);
1304         headers_only = extract_int(cmdbuf, 1);
1305
1306         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1307         return;
1308 }
1309
1310
1311 /*
1312  * display a message (mode 2 - RFC822)
1313  */
1314 void cmd_msg2(char *cmdbuf)
1315 {
1316         long msgid;
1317         int headers_only = 0;
1318
1319         msgid = extract_long(cmdbuf, 0);
1320         headers_only = extract_int(cmdbuf, 1);
1321
1322         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1323 }
1324
1325
1326
1327 /* 
1328  * display a message (mode 3 - IGnet raw format - internal programs only)
1329  */
1330 void cmd_msg3(char *cmdbuf)
1331 {
1332         long msgnum;
1333         struct CtdlMessage *msg;
1334         struct ser_ret smr;
1335
1336         if (CC->internal_pgm == 0) {
1337                 cprintf("%d This command is for internal programs only.\n",
1338                         ERROR);
1339                 return;
1340         }
1341
1342         msgnum = extract_long(cmdbuf, 0);
1343         msg = CtdlFetchMessage(msgnum);
1344         if (msg == NULL) {
1345                 cprintf("%d Message %ld not found.\n", 
1346                         ERROR, msgnum);
1347                 return;
1348         }
1349
1350         serialize_message(&smr, msg);
1351         CtdlFreeMessage(msg);
1352
1353         if (smr.len == 0) {
1354                 cprintf("%d Unable to serialize message\n",
1355                         ERROR+INTERNAL_ERROR);
1356                 return;
1357         }
1358
1359         cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1360         client_write(smr.ser, smr.len);
1361         phree(smr.ser);
1362 }
1363
1364
1365
1366 /* 
1367  * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1368  */
1369 void cmd_msg4(char *cmdbuf)
1370 {
1371         long msgid;
1372
1373         msgid = extract_long(cmdbuf, 0);
1374         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1375 }
1376
1377 /*
1378  * Open a component of a MIME message as a download file 
1379  */
1380 void cmd_opna(char *cmdbuf)
1381 {
1382         long msgid;
1383
1384         CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1385
1386         msgid = extract_long(cmdbuf, 0);
1387         extract(desired_section, cmdbuf, 1);
1388
1389         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1390 }                       
1391
1392
1393 /*
1394  * Save a message pointer into a specified room
1395  * (Returns 0 for success, nonzero for failure)
1396  * roomname may be NULL to use the current room
1397  */
1398 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1399         int i;
1400         char hold_rm[ROOMNAMELEN];
1401         struct cdbdata *cdbfr;
1402         int num_msgs;
1403         long *msglist;
1404         long highest_msg = 0L;
1405         struct CtdlMessage *msg = NULL;
1406
1407         lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1408                 roomname, msgid, flags);
1409
1410         strcpy(hold_rm, CC->quickroom.QRname);
1411
1412         /* We may need to check to see if this message is real */
1413         if (  (flags & SM_VERIFY_GOODNESS)
1414            || (flags & SM_DO_REPL_CHECK)
1415            ) {
1416                 msg = CtdlFetchMessage(msgid);
1417                 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1418         }
1419
1420         /* Perform replication checks if necessary */
1421         if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1422
1423                 if (getroom(&CC->quickroom,
1424                    ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1425                    != 0) {
1426                         lprintf(9, "No such room <%s>\n", roomname);
1427                         if (msg != NULL) CtdlFreeMessage(msg);
1428                         return(ERROR + ROOM_NOT_FOUND);
1429                 }
1430
1431                 if (ReplicationChecks(msg) != 0) {
1432                         getroom(&CC->quickroom, hold_rm);
1433                         if (msg != NULL) CtdlFreeMessage(msg);
1434                         lprintf(9, "Did replication, and newer exists\n");
1435                         return(0);
1436                 }
1437         }
1438
1439         /* Now the regular stuff */
1440         if (lgetroom(&CC->quickroom,
1441            ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1442            != 0) {
1443                 lprintf(9, "No such room <%s>\n", roomname);
1444                 if (msg != NULL) CtdlFreeMessage(msg);
1445                 return(ERROR + ROOM_NOT_FOUND);
1446         }
1447
1448         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1449         if (cdbfr == NULL) {
1450                 msglist = NULL;
1451                 num_msgs = 0;
1452         } else {
1453                 msglist = mallok(cdbfr->len);
1454                 if (msglist == NULL)
1455                         lprintf(3, "ERROR malloc msglist!\n");
1456                 num_msgs = cdbfr->len / sizeof(long);
1457                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1458                 cdb_free(cdbfr);
1459         }
1460
1461
1462         /* Make sure the message doesn't already exist in this room.  It
1463          * is absolutely taboo to have more than one reference to the same
1464          * message in a room.
1465          */
1466         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1467                 if (msglist[i] == msgid) {
1468                         lputroom(&CC->quickroom);       /* unlock the room */
1469                         getroom(&CC->quickroom, hold_rm);
1470                         if (msg != NULL) CtdlFreeMessage(msg);
1471                         return(ERROR + ALREADY_EXISTS);
1472                 }
1473         }
1474
1475         /* Now add the new message */
1476         ++num_msgs;
1477         msglist = reallok(msglist,
1478                           (num_msgs * sizeof(long)));
1479
1480         if (msglist == NULL) {
1481                 lprintf(3, "ERROR: can't realloc message list!\n");
1482         }
1483         msglist[num_msgs - 1] = msgid;
1484
1485         /* Sort the message list, so all the msgid's are in order */
1486         num_msgs = sort_msglist(msglist, num_msgs);
1487
1488         /* Determine the highest message number */
1489         highest_msg = msglist[num_msgs - 1];
1490
1491         /* Write it back to disk. */
1492         cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1493                   msglist, num_msgs * sizeof(long));
1494
1495         /* Free up the memory we used. */
1496         phree(msglist);
1497
1498         /* Update the highest-message pointer and unlock the room. */
1499         CC->quickroom.QRhighest = highest_msg;
1500         lputroom(&CC->quickroom);
1501         getroom(&CC->quickroom, hold_rm);
1502
1503         /* Bump the reference count for this message. */
1504         if ((flags & SM_DONT_BUMP_REF)==0) {
1505                 AdjRefCount(msgid, +1);
1506         }
1507
1508         /* Return success. */
1509         if (msg != NULL) CtdlFreeMessage(msg);
1510         return (0);
1511 }
1512
1513
1514
1515 /*
1516  * Message base operation to send a message to the master file
1517  * (returns new message number)
1518  *
1519  * This is the back end for CtdlSubmitMsg() and should not be directly
1520  * called by server-side modules.
1521  *
1522  */
1523 long send_message(struct CtdlMessage *msg,      /* pointer to buffer */
1524                 FILE *save_a_copy)              /* save a copy to disk? */
1525 {
1526         long newmsgid;
1527         long retval;
1528         char msgidbuf[SIZ];
1529         struct ser_ret smr;
1530
1531         /* Get a new message number */
1532         newmsgid = get_new_message_number();
1533         sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1534
1535         /* Generate an ID if we don't have one already */
1536         if (msg->cm_fields['I']==NULL) {
1537                 msg->cm_fields['I'] = strdoop(msgidbuf);
1538         }
1539         
1540         serialize_message(&smr, msg);
1541
1542         if (smr.len == 0) {
1543                 cprintf("%d Unable to serialize message\n",
1544                         ERROR+INTERNAL_ERROR);
1545                 return (-1L);
1546         }
1547
1548         /* Write our little bundle of joy into the message base */
1549         if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1550                       smr.ser, smr.len) < 0) {
1551                 lprintf(2, "Can't store message\n");
1552                 retval = 0L;
1553         } else {
1554                 retval = newmsgid;
1555         }
1556
1557         /* If the caller specified that a copy should be saved to a particular
1558          * file handle, do that now too.
1559          */
1560         if (save_a_copy != NULL) {
1561                 fwrite(smr.ser, smr.len, 1, save_a_copy);
1562         }
1563
1564         /* Free the memory we used for the serialized message */
1565         phree(smr.ser);
1566
1567         /* Return the *local* message ID to the caller
1568          * (even if we're storing an incoming network message)
1569          */
1570         return(retval);
1571 }
1572
1573
1574
1575 /*
1576  * Serialize a struct CtdlMessage into the format used on disk and network.
1577  * 
1578  * This function loads up a "struct ser_ret" (defined in server.h) which
1579  * contains the length of the serialized message and a pointer to the
1580  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
1581  */
1582 void serialize_message(struct ser_ret *ret,             /* return values */
1583                         struct CtdlMessage *msg)        /* unserialized msg */
1584 {
1585         size_t wlen;
1586         int i;
1587         static char *forder = FORDER;
1588
1589         if (is_valid_message(msg) == 0) return;         /* self check */
1590
1591         ret->len = 3;
1592         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1593                 ret->len = ret->len +
1594                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
1595
1596         lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1597         ret->ser = mallok(ret->len);
1598         if (ret->ser == NULL) {
1599                 ret->len = 0;
1600                 return;
1601         }
1602
1603         ret->ser[0] = 0xFF;
1604         ret->ser[1] = msg->cm_anon_type;
1605         ret->ser[2] = msg->cm_format_type;
1606         wlen = 3;
1607
1608         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1609                 ret->ser[wlen++] = (char)forder[i];
1610                 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1611                 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1612         }
1613         if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1614                 (long)ret->len, (long)wlen);
1615
1616         return;
1617 }
1618
1619
1620
1621 /*
1622  * Back end for the ReplicationChecks() function
1623  */
1624 void check_repl(long msgnum, void *userdata) {
1625         struct CtdlMessage *msg;
1626         time_t timestamp = (-1L);
1627
1628         lprintf(9, "check_repl() found message %ld\n", msgnum);
1629         msg = CtdlFetchMessage(msgnum);
1630         if (msg == NULL) return;
1631         if (msg->cm_fields['T'] != NULL) {
1632                 timestamp = atol(msg->cm_fields['T']);
1633         }
1634         CtdlFreeMessage(msg);
1635
1636         if (timestamp > msg_repl->highest) {
1637                 msg_repl->highest = timestamp;  /* newer! */
1638                 lprintf(9, "newer!\n");
1639                 return;
1640         }
1641         lprintf(9, "older!\n");
1642
1643         /* Existing isn't newer?  Then delete the old one(s). */
1644         CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1645 }
1646
1647
1648 /*
1649  * Check to see if any messages already exist which carry the same Extended ID
1650  * as this one.  
1651  *
1652  * If any are found:
1653  * -> With older timestamps: delete them and return 0.  Message will be saved.
1654  * -> With newer timestamps: return 1.  Message save will be aborted.
1655  */
1656 int ReplicationChecks(struct CtdlMessage *msg) {
1657         struct CtdlMessage *template;
1658         int abort_this = 0;
1659
1660         lprintf(9, "ReplicationChecks() started\n");
1661         /* No extended id?  Don't do anything. */
1662         if (msg->cm_fields['E'] == NULL) return 0;
1663         if (strlen(msg->cm_fields['E']) == 0) return 0;
1664         lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1665
1666         CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1667         strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1668         msg_repl->highest = atol(msg->cm_fields['T']);
1669
1670         template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1671         memset(template, 0, sizeof(struct CtdlMessage));
1672         template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1673
1674         CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1675                 check_repl, NULL);
1676
1677         /* If a newer message exists with the same Extended ID, abort
1678          * this save.
1679          */
1680         if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1681                 abort_this = 1;
1682                 }
1683
1684         CtdlFreeMessage(template);
1685         lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1686         return(abort_this);
1687 }
1688
1689
1690
1691
1692 /*
1693  * Save a message to disk and submit it into the delivery system.
1694  */
1695 long CtdlSubmitMsg(struct CtdlMessage *msg,     /* message to save */
1696                 struct recptypes *recps,        /* recipients (if mail) */
1697                 char *force                     /* force a particular room? */
1698 ) {
1699         char aaa[SIZ];
1700         char hold_rm[ROOMNAMELEN];
1701         char actual_rm[ROOMNAMELEN];
1702         char force_room[ROOMNAMELEN];
1703         char content_type[SIZ];                 /* We have to learn this */
1704         char recipient[SIZ];
1705         long newmsgid;
1706         char *mptr = NULL;
1707         struct usersupp userbuf;
1708         int a, i;
1709         struct MetaData smi;
1710         FILE *network_fp = NULL;
1711         static int seqnum = 1;
1712         struct CtdlMessage *imsg = NULL;
1713         char *instr;
1714         struct ser_ret smr;
1715         char *hold_R, *hold_D;
1716
1717         lprintf(9, "CtdlSubmitMsg() called\n");
1718         if (is_valid_message(msg) == 0) return(-1);     /* self check */
1719
1720         /* If this message has no timestamp, we take the liberty of
1721          * giving it one, right now.
1722          */
1723         if (msg->cm_fields['T'] == NULL) {
1724                 lprintf(9, "Generating timestamp\n");
1725                 sprintf(aaa, "%ld", (long)time(NULL));
1726                 msg->cm_fields['T'] = strdoop(aaa);
1727         }
1728
1729         /* If this message has no path, we generate one.
1730          */
1731         if (msg->cm_fields['P'] == NULL) {
1732                 lprintf(9, "Generating path\n");
1733                 if (msg->cm_fields['A'] != NULL) {
1734                         msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1735                         for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1736                                 if (isspace(msg->cm_fields['P'][a])) {
1737                                         msg->cm_fields['P'][a] = ' ';
1738                                 }
1739                         }
1740                 }
1741                 else {
1742                         msg->cm_fields['P'] = strdoop("unknown");
1743                 }
1744         }
1745
1746         strcpy(force_room, force);
1747
1748         /* Learn about what's inside, because it's what's inside that counts */
1749         lprintf(9, "Learning what's inside\n");
1750         if (msg->cm_fields['M'] == NULL) {
1751                 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1752         }
1753
1754         switch (msg->cm_format_type) {
1755         case 0:
1756                 strcpy(content_type, "text/x-citadel-variformat");
1757                 break;
1758         case 1:
1759                 strcpy(content_type, "text/plain");
1760                 break;
1761         case 4:
1762                 strcpy(content_type, "text/plain");
1763                 /* advance past header fields */
1764                 mptr = msg->cm_fields['M'];
1765                 a = strlen(mptr);
1766                 while ((--a) > 0) {
1767                         if (!strncasecmp(mptr, "Content-type: ", 14)) {
1768                                 safestrncpy(content_type, mptr,
1769                                             sizeof(content_type));
1770                                 strcpy(content_type, &content_type[14]);
1771                                 for (a = 0; a < strlen(content_type); ++a)
1772                                         if ((content_type[a] == ';')
1773                                             || (content_type[a] == ' ')
1774                                             || (content_type[a] == 13)
1775                                             || (content_type[a] == 10))
1776                                                 content_type[a] = 0;
1777                                 break;
1778                         }
1779                         ++mptr;
1780                 }
1781         }
1782
1783         /* Goto the correct room */
1784         lprintf(9, "Switching rooms\n");
1785         strcpy(hold_rm, CC->quickroom.QRname);
1786         strcpy(actual_rm, CC->quickroom.QRname);
1787         if (recps != NULL) {
1788                 strcpy(actual_rm, SENTITEMS);
1789         }
1790
1791         /* If the user is a twit, move to the twit room for posting */
1792         lprintf(9, "Handling twit stuff\n");
1793         if (TWITDETECT) {
1794                 if (CC->usersupp.axlevel == 2) {
1795                         strcpy(hold_rm, actual_rm);
1796                         strcpy(actual_rm, config.c_twitroom);
1797                 }
1798         }
1799
1800         /* ...or if this message is destined for Aide> then go there. */
1801         if (strlen(force_room) > 0) {
1802                 strcpy(actual_rm, force_room);
1803         }
1804
1805         lprintf(9, "Possibly relocating\n");
1806         if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1807                 getroom(&CC->quickroom, actual_rm);
1808         }
1809
1810         /*
1811          * If this message has no O (room) field, generate one.
1812          */
1813         if (msg->cm_fields['O'] == NULL) {
1814                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1815         }
1816
1817         /* Perform "before save" hooks (aborting if any return nonzero) */
1818         lprintf(9, "Performing before-save hooks\n");
1819         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1820
1821         /* If this message has an Extended ID, perform replication checks */
1822         lprintf(9, "Performing replication checks\n");
1823         if (ReplicationChecks(msg) > 0) return(-1);
1824
1825         /* Save it to disk */
1826         lprintf(9, "Saving to disk\n");
1827         newmsgid = send_message(msg, NULL);
1828         if (newmsgid <= 0L) return(-1);
1829
1830         /* Write a supplemental message info record.  This doesn't have to
1831          * be a critical section because nobody else knows about this message
1832          * yet.
1833          */
1834         lprintf(9, "Creating MetaData record\n");
1835         memset(&smi, 0, sizeof(struct MetaData));
1836         smi.meta_msgnum = newmsgid;
1837         smi.meta_refcount = 0;
1838         safestrncpy(smi.meta_content_type, content_type, 64);
1839         PutMetaData(&smi);
1840
1841         /* Now figure out where to store the pointers */
1842         lprintf(9, "Storing pointers\n");
1843
1844         /* If this is being done by the networker delivering a private
1845          * message, we want to BYPASS saving the sender's copy (because there
1846          * is no local sender; it would otherwise go to the Trashcan).
1847          */
1848         if ((!CC->internal_pgm) || (recps == NULL)) {
1849                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1850                         lprintf(3, "ERROR saving message pointer!\n");
1851                         CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1852                 }
1853         }
1854
1855         /* For internet mail, drop a copy in the outbound queue room */
1856         if (recps != NULL)
1857          if (recps->num_internet > 0) {
1858                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1859         }
1860
1861         /* If other rooms are specified, drop them there too. */
1862         if (recps != NULL)
1863          if (recps->num_room > 0)
1864           for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1865                 extract(recipient, recps->recp_room, i);
1866                 lprintf(9, "Delivering to local room <%s>\n", recipient);
1867                 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1868         }
1869
1870         /* Bump this user's messages posted counter. */
1871         lprintf(9, "Updating user\n");
1872         lgetuser(&CC->usersupp, CC->curr_user);
1873         CC->usersupp.posted = CC->usersupp.posted + 1;
1874         lputuser(&CC->usersupp);
1875
1876         /* If this is private, local mail, make a copy in the
1877          * recipient's mailbox and bump the reference count.
1878          */
1879         if (recps != NULL)
1880          if (recps->num_local > 0)
1881           for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1882                 extract(recipient, recps->recp_local, i);
1883                 lprintf(9, "Delivering private local mail to <%s>\n",
1884                         recipient);
1885                 if (getuser(&userbuf, recipient) == 0) {
1886                         MailboxName(actual_rm, &userbuf, MAILROOM);
1887                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1888                 }
1889                 else {
1890                         lprintf(9, "No user <%s>\n", recipient);
1891                         CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1892                 }
1893         }
1894
1895         /* Perform "after save" hooks */
1896         lprintf(9, "Performing after-save hooks\n");
1897         PerformMessageHooks(msg, EVT_AFTERSAVE);
1898
1899         /* For IGnet mail, we have to save a new copy into the spooler for
1900          * each recipient, with the R and D fields set to the recipient and
1901          * destination-node.  This has two ugly side effects: all other
1902          * recipients end up being unlisted in this recipient's copy of the
1903          * message, and it has to deliver multiple messages to the same
1904          * node.  We'll revisit this again in a year or so when everyone has
1905          * a network spool receiver that can handle the new style messages.
1906          */
1907         if (recps != NULL)
1908          if (recps->num_ignet > 0)
1909           for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1910                 extract(recipient, recps->recp_ignet, i);
1911
1912                 hold_R = msg->cm_fields['R'];
1913                 hold_D = msg->cm_fields['D'];
1914                 msg->cm_fields['R'] = mallok(SIZ);
1915                 msg->cm_fields['D'] = mallok(SIZ);
1916                 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1917                 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1918                 
1919                 serialize_message(&smr, msg);
1920                 if (smr.len > 0) {
1921                         sprintf(aaa,
1922                                 "./network/spoolin/netmail.%04lx.%04x.%04x",
1923                                 (long) getpid(), CC->cs_pid, ++seqnum);
1924                         network_fp = fopen(aaa, "wb+");
1925                         if (network_fp != NULL) {
1926                                 fwrite(smr.ser, smr.len, 1, network_fp);
1927                                 fclose(network_fp);
1928                         }
1929                         phree(smr.ser);
1930                 }
1931
1932                 phree(msg->cm_fields['R']);
1933                 phree(msg->cm_fields['D']);
1934                 msg->cm_fields['R'] = hold_R;
1935                 msg->cm_fields['D'] = hold_D;
1936         }
1937
1938         /* Go back to the room we started from */
1939         lprintf(9, "Returning to original room\n");
1940         if (strcasecmp(hold_rm, CC->quickroom.QRname))
1941                 getroom(&CC->quickroom, hold_rm);
1942
1943         /* For internet mail, generate delivery instructions.
1944          * Yes, this is recursive.  Deal with it.  Infinite recursion does
1945          * not happen because the delivery instructions message does not
1946          * contain a recipient.
1947          */
1948         if (recps != NULL)
1949          if (recps->num_internet > 0) {
1950                 lprintf(9, "Generating delivery instructions\n");
1951                 instr = mallok(SIZ * 2);
1952                 sprintf(instr,
1953                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1954                         "bounceto|%s@%s\n",
1955                         SPOOLMIME, newmsgid, (long)time(NULL),
1956                         msg->cm_fields['A'], msg->cm_fields['N']
1957                 );
1958
1959                 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
1960                         extract(recipient, recps->recp_internet, i);
1961                         sprintf(&instr[strlen(instr)],
1962                                 "remote|%s|0||\n", recipient);
1963                 }
1964
1965                 imsg = mallok(sizeof(struct CtdlMessage));
1966                 memset(imsg, 0, sizeof(struct CtdlMessage));
1967                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1968                 imsg->cm_anon_type = MES_NORMAL;
1969                 imsg->cm_format_type = FMT_RFC822;
1970                 imsg->cm_fields['A'] = strdoop("Citadel");
1971                 imsg->cm_fields['M'] = instr;
1972                 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
1973                 CtdlFreeMessage(imsg);
1974         }
1975
1976         return(newmsgid);
1977 }
1978
1979
1980
1981 /*
1982  * Convenience function for generating small administrative messages.
1983  */
1984 void quickie_message(char *from, char *to, char *room, char *text)
1985 {
1986         struct CtdlMessage *msg;
1987
1988         msg = mallok(sizeof(struct CtdlMessage));
1989         memset(msg, 0, sizeof(struct CtdlMessage));
1990         msg->cm_magic = CTDLMESSAGE_MAGIC;
1991         msg->cm_anon_type = MES_NORMAL;
1992         msg->cm_format_type = 0;
1993         msg->cm_fields['A'] = strdoop(from);
1994         msg->cm_fields['O'] = strdoop(room);
1995         msg->cm_fields['N'] = strdoop(NODENAME);
1996         if (to != NULL)
1997                 msg->cm_fields['R'] = strdoop(to);
1998         msg->cm_fields['M'] = strdoop(text);
1999
2000         CtdlSubmitMsg(msg, NULL, room);
2001         CtdlFreeMessage(msg);
2002         syslog(LOG_NOTICE, text);
2003 }
2004
2005
2006
2007 /*
2008  * Back end function used by make_message() and similar functions
2009  */
2010 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
2011                         size_t maxlen,          /* maximum message length */
2012                         char *exist             /* if non-null, append to it;
2013                                                    exist is ALWAYS freed  */
2014                         ) {
2015         char buf[SIZ];
2016         int linelen;
2017         size_t message_len = 0;
2018         size_t buffer_len = 0;
2019         char *ptr;
2020         char *m;
2021
2022         if (exist == NULL) {
2023                 m = mallok(4096);
2024         }
2025         else {
2026                 m = reallok(exist, strlen(exist) + 4096);
2027                 if (m == NULL) phree(exist);
2028         }
2029
2030         /* flush the input if we have nowhere to store it */
2031         if (m == NULL) {
2032                 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2033                 return(NULL);
2034         }
2035
2036         /* otherwise read it into memory */
2037         else {
2038                 buffer_len = 4096;
2039                 m[0] = 0;
2040                 message_len = 0;
2041         }
2042         /* read in the lines of message text one by one */
2043         while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2044
2045                 /* strip trailing newline type stuff */
2046                 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2047                 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2048
2049                 linelen = strlen(buf);
2050
2051                 /* augment the buffer if we have to */
2052                 if ((message_len + linelen + 2) > buffer_len) {
2053                         lprintf(9, "realloking\n");
2054                         ptr = reallok(m, (buffer_len * 2) );
2055                         if (ptr == NULL) {      /* flush if can't allocate */
2056                                 while ( (client_gets(buf)>0) &&
2057                                         strcmp(buf, terminator)) ;;
2058                                 return(m);
2059                         } else {
2060                                 buffer_len = (buffer_len * 2);
2061                                 m = ptr;
2062                                 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2063                         }
2064                 }
2065
2066                 /* Add the new line to the buffer.  NOTE: this loop must avoid
2067                  * using functions like strcat() and strlen() because they
2068                  * traverse the entire buffer upon every call, and doing that
2069                  * for a multi-megabyte message slows it down beyond usability.
2070                  */
2071                 strcpy(&m[message_len], buf);
2072                 m[message_len + linelen] = '\n';
2073                 m[message_len + linelen + 1] = 0;
2074                 message_len = message_len + linelen + 1;
2075
2076                 /* if we've hit the max msg length, flush the rest */
2077                 if (message_len >= maxlen) {
2078                         while ( (client_gets(buf)>0)
2079                                 && strcmp(buf, terminator)) ;;
2080                         return(m);
2081                 }
2082         }
2083         return(m);
2084 }
2085
2086
2087
2088
2089 /*
2090  * Build a binary message to be saved on disk.
2091  */
2092
2093 static struct CtdlMessage *make_message(
2094         struct usersupp *author,        /* author's usersupp structure */
2095         char *recipient,                /* NULL if it's not mail */
2096         char *room,                     /* room where it's going */
2097         int type,                       /* see MES_ types in header file */
2098         int format_type,                /* variformat, plain text, MIME... */
2099         char *fake_name,                /* who we're masquerading as */
2100         char *subject                   /* Subject (optional) */
2101 ) {
2102         char dest_node[SIZ];
2103         char buf[SIZ];
2104         struct CtdlMessage *msg;
2105
2106         msg = mallok(sizeof(struct CtdlMessage));
2107         memset(msg, 0, sizeof(struct CtdlMessage));
2108         msg->cm_magic = CTDLMESSAGE_MAGIC;
2109         msg->cm_anon_type = type;
2110         msg->cm_format_type = format_type;
2111
2112         /* Don't confuse the poor folks if it's not routed mail. */
2113         strcpy(dest_node, "");
2114
2115         striplt(recipient);
2116
2117         sprintf(buf, "cit%ld", author->usernum);                /* Path */
2118         msg->cm_fields['P'] = strdoop(buf);
2119
2120         sprintf(buf, "%ld", (long)time(NULL));                  /* timestamp */
2121         msg->cm_fields['T'] = strdoop(buf);
2122
2123         if (fake_name[0])                                       /* author */
2124                 msg->cm_fields['A'] = strdoop(fake_name);
2125         else
2126                 msg->cm_fields['A'] = strdoop(author->fullname);
2127
2128         if (CC->quickroom.QRflags & QR_MAILBOX) {               /* room */
2129                 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2130         }
2131         else {
2132                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2133         }
2134
2135         msg->cm_fields['N'] = strdoop(NODENAME);                /* nodename */
2136         msg->cm_fields['H'] = strdoop(HUMANNODE);               /* hnodename */
2137
2138         if (recipient[0] != 0) {
2139                 msg->cm_fields['R'] = strdoop(recipient);
2140         }
2141         if (dest_node[0] != 0) {
2142                 msg->cm_fields['D'] = strdoop(dest_node);
2143         }
2144
2145         if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
2146                 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2147         }
2148
2149         if (subject != NULL) {
2150                 striplt(subject);
2151                 if (strlen(subject) > 0) {
2152                         msg->cm_fields['U'] = strdoop(subject);
2153                 }
2154         }
2155
2156         msg->cm_fields['M'] = CtdlReadMessageBody("000",
2157                                                 config.c_maxmsglen, NULL);
2158
2159         return(msg);
2160 }
2161
2162
2163 /*
2164  * Check to see whether we have permission to post a message in the current
2165  * room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2166  * returns 0 on success.
2167  */
2168 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2169
2170         if (!(CC->logged_in)) {
2171                 sprintf(errmsgbuf, "Not logged in.");
2172                 return (ERROR + NOT_LOGGED_IN);
2173         }
2174
2175         if ((CC->usersupp.axlevel < 2)
2176             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2177                 sprintf(errmsgbuf, "Need to be validated to enter "
2178                                 "(except in %s> to sysop)", MAILROOM);
2179                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2180         }
2181
2182         if ((CC->usersupp.axlevel < 4)
2183            && (CC->quickroom.QRflags & QR_NETWORK)) {
2184                 sprintf(errmsgbuf, "Need net privileges to enter here.");
2185                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2186         }
2187
2188         if ((CC->usersupp.axlevel < 6)
2189            && (CC->quickroom.QRflags & QR_READONLY)) {
2190                 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2191                 return (ERROR + HIGHER_ACCESS_REQUIRED);
2192         }
2193
2194         strcpy(errmsgbuf, "Ok");
2195         return(0);
2196 }
2197
2198
2199 /*
2200  * Validate recipients, count delivery types and errors, and handle aliasing
2201  * FIXME check for dupes!!!!!
2202  */
2203 struct recptypes *validate_recipients(char *recipients) {
2204         struct recptypes *ret;
2205         char this_recp[SIZ];
2206         char this_recp_cooked[SIZ];
2207         char append[SIZ];
2208         int num_recps;
2209         int i, j;
2210         int mailtype;
2211         int invalid;
2212         struct usersupp tempUS;
2213         struct quickroom tempQR;
2214
2215         /* Initialize */
2216         ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2217         if (ret == NULL) return(NULL);
2218         memset(ret, 0, sizeof(struct recptypes));
2219
2220         ret->num_local = 0;
2221         ret->num_internet = 0;
2222         ret->num_ignet = 0;
2223         ret->num_error = 0;
2224         ret->num_room = 0;
2225
2226         if (recipients == NULL) {
2227                 num_recps = 0;
2228         }
2229         else if (strlen(recipients) == 0) {
2230                 num_recps = 0;
2231         }
2232         else {
2233                 /* Change all valid separator characters to commas */
2234                 for (i=0; i<strlen(recipients); ++i) {
2235                         if ((recipients[i] == ';') || (recipients[i] == '|')) {
2236                                 recipients[i] = ',';
2237                         }
2238                 }
2239
2240                 /* Count 'em up */
2241                 num_recps = num_tokens(recipients, ',');
2242         }
2243
2244         if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2245                 extract_token(this_recp, recipients, i, ',');
2246                 striplt(this_recp);
2247                 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2248                 mailtype = alias(this_recp);
2249                 mailtype = alias(this_recp);
2250                 mailtype = alias(this_recp);
2251                 for (j=0; j<=strlen(this_recp); ++j) {
2252                         if (this_recp[j]=='_') {
2253                                 this_recp_cooked[j] = ' ';
2254                         }
2255                         else {
2256                                 this_recp_cooked[j] = this_recp[j];
2257                         }
2258                 }
2259                 invalid = 0;
2260                 switch(mailtype) {
2261                         case MES_LOCAL:
2262                                 if (!strcasecmp(this_recp, "sysop")) {
2263                                         ++ret->num_room;
2264                                         strcpy(this_recp, AIDEROOM);
2265                                         if (strlen(ret->recp_room) > 0) {
2266                                                 strcat(ret->recp_room, "|");
2267                                         }
2268                                         strcat(ret->recp_room, this_recp);
2269                                 }
2270                                 else if (getuser(&tempUS, this_recp) == 0) {
2271                                         ++ret->num_local;
2272                                         strcpy(this_recp, tempUS.fullname);
2273                                         if (strlen(ret->recp_local) > 0) {
2274                                                 strcat(ret->recp_local, "|");
2275                                         }
2276                                         strcat(ret->recp_local, this_recp);
2277                                 }
2278                                 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2279                                         ++ret->num_local;
2280                                         strcpy(this_recp, tempUS.fullname);
2281                                         if (strlen(ret->recp_local) > 0) {
2282                                                 strcat(ret->recp_local, "|");
2283                                         }
2284                                         strcat(ret->recp_local, this_recp);
2285                                 }
2286                                 else if ( (!strncasecmp(this_recp, "room_", 5))
2287                                       && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2288                                         ++ret->num_room;
2289                                         if (strlen(ret->recp_room) > 0) {
2290                                                 strcat(ret->recp_room, "|");
2291                                         }
2292                                         strcat(ret->recp_room, &this_recp_cooked[5]);
2293                                 }
2294                                 else {
2295                                         ++ret->num_error;
2296                                         invalid = 1;
2297                                 }
2298                                 break;
2299                         case MES_INTERNET:
2300                                 ++ret->num_internet;
2301                                 if (strlen(ret->recp_internet) > 0) {
2302                                         strcat(ret->recp_internet, "|");
2303                                 }
2304                                 strcat(ret->recp_internet, this_recp);
2305                                 break;
2306                         case MES_IGNET:
2307                                 ++ret->num_ignet;
2308                                 if (strlen(ret->recp_ignet) > 0) {
2309                                         strcat(ret->recp_ignet, "|");
2310                                 }
2311                                 strcat(ret->recp_ignet, this_recp);
2312                                 break;
2313                         case MES_ERROR:
2314                                 ++ret->num_error;
2315                                 invalid = 1;
2316                                 break;
2317                 }
2318                 if (invalid) {
2319                         if (strlen(ret->errormsg) == 0) {
2320                                 sprintf(append,
2321                                         "Invalid recipient: %s",
2322                                         this_recp);
2323                         }
2324                         else {
2325                                 sprintf(append, 
2326                                         ", %s", this_recp);
2327                         }
2328                         if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2329                                 strcat(ret->errormsg, append);
2330                         }
2331                 }
2332                 else {
2333                         if (strlen(ret->display_recp) == 0) {
2334                                 strcpy(append, this_recp);
2335                         }
2336                         else {
2337                                 sprintf(append, ", %s", this_recp);
2338                         }
2339                         if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2340                                 strcat(ret->display_recp, append);
2341                         }
2342                 }
2343         }
2344
2345         if ((ret->num_local + ret->num_internet + ret->num_ignet +
2346            ret->num_room + ret->num_error) == 0) {
2347                 ++ret->num_error;
2348                 strcpy(ret->errormsg, "No recipients specified.");
2349         }
2350
2351         lprintf(9, "validate_recipients()\n");
2352         lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2353         lprintf(9, "  room: %d <%s>\n", ret->num_room, ret->recp_room);
2354         lprintf(9, "  inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2355         lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2356         lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2357
2358         return(ret);
2359 }
2360
2361
2362
2363 /*
2364  * message entry  -  mode 0 (normal)
2365  */
2366 void cmd_ent0(char *entargs)
2367 {
2368         int post = 0;
2369         char recp[SIZ];
2370         char masquerade_as[SIZ];
2371         int anon_flag = 0;
2372         int format_type = 0;
2373         char newusername[SIZ];
2374         struct CtdlMessage *msg;
2375         int anonymous = 0;
2376         char errmsg[SIZ];
2377         int err = 0;
2378         struct recptypes *valid = NULL;
2379         char subject[SIZ];
2380
2381         post = extract_int(entargs, 0);
2382         extract(recp, entargs, 1);
2383         anon_flag = extract_int(entargs, 2);
2384         format_type = extract_int(entargs, 3);
2385         extract(subject, entargs, 4);
2386
2387         /* first check to make sure the request is valid. */
2388
2389         err = CtdlDoIHavePermissionToPostInThisRoom(errmsg);
2390         if (err) {
2391                 cprintf("%d %s\n", err, errmsg);
2392                 return;
2393         }
2394
2395         /* Check some other permission type things. */
2396
2397         if (post == 2) {
2398                 if (CC->usersupp.axlevel < 6) {
2399                         cprintf("%d You don't have permission to masquerade.\n",
2400                                 ERROR + HIGHER_ACCESS_REQUIRED);
2401                         return;
2402                 }
2403                 extract(newusername, entargs, 4);
2404                 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2405                 safestrncpy(CC->fake_postname, newusername,
2406                         sizeof(CC->fake_postname) );
2407                 cprintf("%d ok\n", OK);
2408                 return;
2409         }
2410         CC->cs_flags |= CS_POSTING;
2411
2412         if (CC->quickroom.QRflags & QR_MAILBOX) {
2413                 if (CC->usersupp.axlevel < 2) {
2414                         strcpy(recp, "sysop");
2415                 }
2416
2417                 valid = validate_recipients(recp);
2418                 if (valid->num_error > 0) {
2419                         cprintf("%d %s\n",
2420                                 ERROR + NO_SUCH_USER, valid->errormsg);
2421                         phree(valid);
2422                         return;
2423                 }
2424
2425                 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2426                    && (CC->usersupp.axlevel < 4) ) {
2427                         cprintf("%d Higher access required for network mail.\n",
2428                                 ERROR + HIGHER_ACCESS_REQUIRED);
2429                         phree(valid);
2430                         return;
2431                 }
2432         
2433                 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2434                     && ((CC->usersupp.flags & US_INTERNET) == 0)
2435                     && (!CC->internal_pgm)) {
2436                         cprintf("%d You don't have access to Internet mail.\n",
2437                                 ERROR + HIGHER_ACCESS_REQUIRED);
2438                         phree(valid);
2439                         return;
2440                 }
2441
2442         }
2443
2444         /* Is this a room which has anonymous-only or anonymous-option? */
2445         anonymous = MES_NORMAL;
2446         if (CC->quickroom.QRflags & QR_ANONONLY) {
2447                 anonymous = MES_ANONONLY;
2448         }
2449         if (CC->quickroom.QRflags & QR_ANONOPT) {
2450                 if (anon_flag == 1) {   /* only if the user requested it */
2451                         anonymous = MES_ANONOPT;
2452                 }
2453         }
2454
2455         if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2456                 recp[0] = 0;
2457         }
2458
2459         /* If we're only checking the validity of the request, return
2460          * success without creating the message.
2461          */
2462         if (post == 0) {
2463                 cprintf("%d %s\n", OK,
2464                         ((valid != NULL) ? valid->display_recp : "") );
2465                 phree(valid);
2466                 return;
2467         }
2468
2469         /* Handle author masquerading */
2470         if (CC->fake_postname[0]) {
2471                 strcpy(masquerade_as, CC->fake_postname);
2472         }
2473         else if (CC->fake_username[0]) {
2474                 strcpy(masquerade_as, CC->fake_username);
2475         }
2476         else {
2477                 strcpy(masquerade_as, "");
2478         }
2479
2480         /* Read in the message from the client. */
2481         cprintf("%d send message\n", SEND_LISTING);
2482         msg = make_message(&CC->usersupp, recp,
2483                 CC->quickroom.QRname, anonymous, format_type,
2484                 masquerade_as, subject);
2485
2486         if (msg != NULL) {
2487                 CtdlSubmitMsg(msg, valid, "");
2488                 CtdlFreeMessage(msg);
2489         }
2490         CC->fake_postname[0] = '\0';
2491         phree(valid);
2492         return;
2493 }
2494
2495
2496
2497 /*
2498  * API function to delete messages which match a set of criteria
2499  * (returns the actual number of messages deleted)
2500  */
2501 int CtdlDeleteMessages(char *room_name,         /* which room */
2502                        long dmsgnum,            /* or "0" for any */
2503                        char *content_type       /* or "" for any */
2504 )
2505 {
2506
2507         struct quickroom qrbuf;
2508         struct cdbdata *cdbfr;
2509         long *msglist = NULL;
2510         long *dellist = NULL;
2511         int num_msgs = 0;
2512         int i;
2513         int num_deleted = 0;
2514         int delete_this;
2515         struct MetaData smi;
2516
2517         lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2518                 room_name, dmsgnum, content_type);
2519
2520         /* get room record, obtaining a lock... */
2521         if (lgetroom(&qrbuf, room_name) != 0) {
2522                 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2523                         room_name);
2524                 return (0);     /* room not found */
2525         }
2526         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2527
2528         if (cdbfr != NULL) {
2529                 msglist = mallok(cdbfr->len);
2530                 dellist = mallok(cdbfr->len);
2531                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2532                 num_msgs = cdbfr->len / sizeof(long);
2533                 cdb_free(cdbfr);
2534         }
2535         if (num_msgs > 0) {
2536                 for (i = 0; i < num_msgs; ++i) {
2537                         delete_this = 0x00;
2538
2539                         /* Set/clear a bit for each criterion */
2540
2541                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2542                                 delete_this |= 0x01;
2543                         }
2544                         if (strlen(content_type) == 0) {
2545                                 delete_this |= 0x02;
2546                         } else {
2547                                 GetMetaData(&smi, msglist[i]);
2548                                 if (!strcasecmp(smi.meta_content_type,
2549                                                 content_type)) {
2550                                         delete_this |= 0x02;
2551                                 }
2552                         }
2553
2554                         /* Delete message only if all bits are set */
2555                         if (delete_this == 0x03) {
2556                                 dellist[num_deleted++] = msglist[i];
2557                                 msglist[i] = 0L;
2558                         }
2559                 }
2560
2561                 num_msgs = sort_msglist(msglist, num_msgs);
2562                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2563                           msglist, (num_msgs * sizeof(long)));
2564
2565                 qrbuf.QRhighest = msglist[num_msgs - 1];
2566         }
2567         lputroom(&qrbuf);
2568
2569         /* Go through the messages we pulled out of the index, and decrement
2570          * their reference counts by 1.  If this is the only room the message
2571          * was in, the reference count will reach zero and the message will
2572          * automatically be deleted from the database.  We do this in a
2573          * separate pass because there might be plug-in hooks getting called,
2574          * and we don't want that happening during an S_QUICKROOM critical
2575          * section.
2576          */
2577         if (num_deleted) for (i=0; i<num_deleted; ++i) {
2578                 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2579                 AdjRefCount(dellist[i], -1);
2580         }
2581
2582         /* Now free the memory we used, and go away. */
2583         if (msglist != NULL) phree(msglist);
2584         if (dellist != NULL) phree(dellist);
2585         lprintf(9, "%d message(s) deleted.\n", num_deleted);
2586         return (num_deleted);
2587 }
2588
2589
2590
2591 /*
2592  * Check whether the current user has permission to delete messages from
2593  * the current room (returns 1 for yes, 0 for no)
2594  */
2595 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2596         getuser(&CC->usersupp, CC->curr_user);
2597         if ((CC->usersupp.axlevel < 6)
2598             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2599             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2600             && (!(CC->internal_pgm))) {
2601                 return(0);
2602         }
2603         return(1);
2604 }
2605
2606
2607
2608 /*
2609  * Delete message from current room
2610  */
2611 void cmd_dele(char *delstr)
2612 {
2613         long delnum;
2614         int num_deleted;
2615
2616         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2617                 cprintf("%d Higher access required.\n",
2618                         ERROR + HIGHER_ACCESS_REQUIRED);
2619                 return;
2620         }
2621         delnum = extract_long(delstr, 0);
2622
2623         num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2624
2625         if (num_deleted) {
2626                 cprintf("%d %d message%s deleted.\n", OK,
2627                         num_deleted, ((num_deleted != 1) ? "s" : ""));
2628         } else {
2629                 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2630         }
2631 }
2632
2633
2634 /*
2635  * Back end API function for moves and deletes
2636  */
2637 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2638         int err;
2639
2640         err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2641                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2642         if (err != 0) return(err);
2643
2644         return(0);
2645 }
2646
2647
2648
2649 /*
2650  * move or copy a message to another room
2651  */
2652 void cmd_move(char *args)
2653 {
2654         long num;
2655         char targ[SIZ];
2656         struct quickroom qtemp;
2657         int err;
2658         int is_copy = 0;
2659
2660         num = extract_long(args, 0);
2661         extract(targ, args, 1);
2662         targ[ROOMNAMELEN - 1] = 0;
2663         is_copy = extract_int(args, 2);
2664
2665         if (getroom(&qtemp, targ) != 0) {
2666                 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2667                 return;
2668         }
2669
2670         getuser(&CC->usersupp, CC->curr_user);
2671         /* Aides can move/copy */
2672         if ((CC->usersupp.axlevel < 6)
2673             /* Roomaides can move/copy */
2674             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2675             /* Permit move/copy to/from personal rooms */
2676             && (!((CC->quickroom.QRflags & QR_MAILBOX)
2677                             && (qtemp.QRflags & QR_MAILBOX)))
2678             /* Permit only copy from public to personal room */
2679             && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2680                             && (qtemp.QRflags & QR_MAILBOX)))) {
2681                 cprintf("%d Higher access required.\n",
2682                         ERROR + HIGHER_ACCESS_REQUIRED);
2683                 return;
2684         }
2685
2686         err = CtdlCopyMsgToRoom(num, targ);
2687         if (err != 0) {
2688                 cprintf("%d Cannot store message in %s: error %d\n",
2689                         err, targ, err);
2690                 return;
2691         }
2692
2693         /* Now delete the message from the source room,
2694          * if this is a 'move' rather than a 'copy' operation.
2695          */
2696         if (is_copy == 0) {
2697                 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2698         }
2699
2700         cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2701 }
2702
2703
2704
2705 /*
2706  * GetMetaData()  -  Get the supplementary record for a message
2707  */
2708 void GetMetaData(struct MetaData *smibuf, long msgnum)
2709 {
2710
2711         struct cdbdata *cdbsmi;
2712         long TheIndex;
2713
2714         memset(smibuf, 0, sizeof(struct MetaData));
2715         smibuf->meta_msgnum = msgnum;
2716         smibuf->meta_refcount = 1;      /* Default reference count is 1 */
2717
2718         /* Use the negative of the message number for its supp record index */
2719         TheIndex = (0L - msgnum);
2720
2721         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2722         if (cdbsmi == NULL) {
2723                 return;         /* record not found; go with defaults */
2724         }
2725         memcpy(smibuf, cdbsmi->ptr,
2726                ((cdbsmi->len > sizeof(struct MetaData)) ?
2727                 sizeof(struct MetaData) : cdbsmi->len));
2728         cdb_free(cdbsmi);
2729         return;
2730 }
2731
2732
2733 /*
2734  * PutMetaData()  -  (re)write supplementary record for a message
2735  */
2736 void PutMetaData(struct MetaData *smibuf)
2737 {
2738         long TheIndex;
2739
2740         /* Use the negative of the message number for the metadata db index */
2741         TheIndex = (0L - smibuf->meta_msgnum);
2742
2743         lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2744                 smibuf->meta_msgnum, smibuf->meta_refcount);
2745
2746         cdb_store(CDB_MSGMAIN,
2747                   &TheIndex, sizeof(long),
2748                   smibuf, sizeof(struct MetaData));
2749
2750 }
2751
2752 /*
2753  * AdjRefCount  -  change the reference count for a message;
2754  *                 delete the message if it reaches zero
2755  */
2756 void AdjRefCount(long msgnum, int incr)
2757 {
2758
2759         struct MetaData smi;
2760         long delnum;
2761
2762         /* This is a *tight* critical section; please keep it that way, as
2763          * it may get called while nested in other critical sections.  
2764          * Complicating this any further will surely cause deadlock!
2765          */
2766         begin_critical_section(S_SUPPMSGMAIN);
2767         GetMetaData(&smi, msgnum);
2768         lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2769                 msgnum, smi.meta_refcount);
2770         smi.meta_refcount += incr;
2771         PutMetaData(&smi);
2772         end_critical_section(S_SUPPMSGMAIN);
2773         lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2774                 msgnum, smi.meta_refcount);
2775
2776         /* If the reference count is now zero, delete the message
2777          * (and its supplementary record as well).
2778          */
2779         if (smi.meta_refcount == 0) {
2780                 lprintf(9, "Deleting message <%ld>\n", msgnum);
2781                 delnum = msgnum;
2782                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2783
2784                 /* We have to delete the metadata record too! */
2785                 delnum = (0L - msgnum);
2786                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2787         }
2788 }
2789
2790 /*
2791  * Write a generic object to this room
2792  *
2793  * Note: this could be much more efficient.  Right now we use two temporary
2794  * files, and still pull the message into memory as with all others.
2795  */
2796 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
2797                         char *content_type,     /* MIME type of this object */
2798                         char *tempfilename,     /* Where to fetch it from */
2799                         struct usersupp *is_mailbox,    /* Mailbox room? */
2800                         int is_binary,          /* Is encoding necessary? */
2801                         int is_unique,          /* Del others of this type? */
2802                         unsigned int flags      /* Internal save flags */
2803                         )
2804 {
2805
2806         FILE *fp, *tempfp;
2807         char filename[PATH_MAX];
2808         char cmdbuf[SIZ];
2809         char ch;
2810         struct quickroom qrbuf;
2811         char roomname[ROOMNAMELEN];
2812         struct CtdlMessage *msg;
2813         size_t len;
2814
2815         if (is_mailbox != NULL)
2816                 MailboxName(roomname, is_mailbox, req_room);
2817         else
2818                 safestrncpy(roomname, req_room, sizeof(roomname));
2819         lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2820
2821         strcpy(filename, tmpnam(NULL));
2822         fp = fopen(filename, "w");
2823         if (fp == NULL)
2824                 return;
2825
2826         tempfp = fopen(tempfilename, "r");
2827         if (tempfp == NULL) {
2828                 fclose(fp);
2829                 unlink(filename);
2830                 return;
2831         }
2832
2833         fprintf(fp, "Content-type: %s\n", content_type);
2834         lprintf(9, "Content-type: %s\n", content_type);
2835
2836         if (is_binary == 0) {
2837                 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2838                 while (ch = getc(tempfp), ch > 0)
2839                         putc(ch, fp);
2840                 fclose(tempfp);
2841                 putc(0, fp);
2842                 fclose(fp);
2843         } else {
2844                 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2845                 fclose(tempfp);
2846                 fclose(fp);
2847                 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2848                         tempfilename, filename);
2849                 system(cmdbuf);
2850         }
2851
2852         lprintf(9, "Allocating\n");
2853         msg = mallok(sizeof(struct CtdlMessage));
2854         memset(msg, 0, sizeof(struct CtdlMessage));
2855         msg->cm_magic = CTDLMESSAGE_MAGIC;
2856         msg->cm_anon_type = MES_NORMAL;
2857         msg->cm_format_type = 4;
2858         msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2859         msg->cm_fields['O'] = strdoop(req_room);
2860         msg->cm_fields['N'] = strdoop(config.c_nodename);
2861         msg->cm_fields['H'] = strdoop(config.c_humannode);
2862         msg->cm_flags = flags;
2863         
2864         lprintf(9, "Loading\n");
2865         fp = fopen(filename, "rb");
2866         fseek(fp, 0L, SEEK_END);
2867         len = ftell(fp);
2868         rewind(fp);
2869         msg->cm_fields['M'] = mallok(len);
2870         fread(msg->cm_fields['M'], len, 1, fp);
2871         fclose(fp);
2872         unlink(filename);
2873
2874         /* Create the requested room if we have to. */
2875         if (getroom(&qrbuf, roomname) != 0) {
2876                 create_room(roomname, 
2877                         ( (is_mailbox != NULL) ? 5 : 3 ),
2878                         "", 0, 1);
2879         }
2880         /* If the caller specified this object as unique, delete all
2881          * other objects of this type that are currently in the room.
2882          */
2883         if (is_unique) {
2884                 lprintf(9, "Deleted %d other msgs of this type\n",
2885                         CtdlDeleteMessages(roomname, 0L, content_type));
2886         }
2887         /* Now write the data */
2888         CtdlSubmitMsg(msg, NULL, roomname);
2889         CtdlFreeMessage(msg);
2890 }
2891
2892
2893
2894
2895
2896
2897 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2898         config_msgnum = msgnum;
2899 }
2900
2901
2902 char *CtdlGetSysConfig(char *sysconfname) {
2903         char hold_rm[ROOMNAMELEN];
2904         long msgnum;
2905         char *conf;
2906         struct CtdlMessage *msg;
2907         char buf[SIZ];
2908         
2909         strcpy(hold_rm, CC->quickroom.QRname);
2910         if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2911                 getroom(&CC->quickroom, hold_rm);
2912                 return NULL;
2913         }
2914
2915
2916         /* We want the last (and probably only) config in this room */
2917         begin_critical_section(S_CONFIG);
2918         config_msgnum = (-1L);
2919         CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2920                 CtdlGetSysConfigBackend, NULL);
2921         msgnum = config_msgnum;
2922         end_critical_section(S_CONFIG);
2923
2924         if (msgnum < 0L) {
2925                 conf = NULL;
2926         }
2927         else {
2928                 msg = CtdlFetchMessage(msgnum);
2929                 if (msg != NULL) {
2930                         conf = strdoop(msg->cm_fields['M']);
2931                         CtdlFreeMessage(msg);
2932                 }
2933                 else {
2934                         conf = NULL;
2935                 }
2936         }
2937
2938         getroom(&CC->quickroom, hold_rm);
2939
2940         if (conf != NULL) do {
2941                 extract_token(buf, conf, 0, '\n');
2942                 strcpy(conf, &conf[strlen(buf)+1]);
2943         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2944
2945         return(conf);
2946 }
2947
2948 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2949         char temp[PATH_MAX];
2950         FILE *fp;
2951
2952         strcpy(temp, tmpnam(NULL));
2953
2954         fp = fopen(temp, "w");
2955         if (fp == NULL) return;
2956         fprintf(fp, "%s", sysconfdata);
2957         fclose(fp);
2958
2959         /* this handy API function does all the work for us */
2960         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
2961         unlink(temp);
2962 }