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