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