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