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