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