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