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