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