]> code.citadel.org Git - citadel.git/blob - citadel/msgbase.c
* "Path:" removed for now because it confuses brain-dead Microsoft shitware
[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, *mime_download, NULL, 0);
885                         /* If there's no file open by this time, the requested
886                          * section wasn't found, so print an error
887                          */
888                         if (CC->download_fp == NULL) {
889                                 if (do_proto) cprintf(
890                                         "%d Section %s not found.\n",
891                                         ERROR + FILE_NOT_FOUND,
892                                         desired_section);
893                         }
894                 }
895                 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
896         }
897
898         /* now for the user-mode message reading loops */
899         if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
900
901         /* Tell the client which format type we're using.  If this is a
902          * MIME message, *lie* about it and tell the user it's fixed-format.
903          */
904         if (mode == MT_CITADEL) {
905                 if (TheMessage->cm_format_type == FMT_RFC822) {
906                         if (do_proto) cprintf("type=1\n");
907                 }
908                 else {
909                         if (do_proto) cprintf("type=%d\n",
910                                 TheMessage->cm_format_type);
911                 }
912         }
913
914         /* nhdr=yes means that we're only displaying headers, no body */
915         if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
916                 if (do_proto) cprintf("nhdr=yes\n");
917         }
918
919         /* begin header processing loop for Citadel message format */
920
921         if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
922
923                 strcpy(display_name, "<unknown>");
924                 if (TheMessage->cm_fields['A']) {
925                         strcpy(buf, TheMessage->cm_fields['A']);
926                         PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
927                         if (TheMessage->cm_anon_type == MES_ANON)
928                                 strcpy(display_name, "****");
929                         else if (TheMessage->cm_anon_type == MES_AN2)
930                                 strcpy(display_name, "anonymous");
931                         else
932                                 strcpy(display_name, buf);
933                         if ((is_room_aide())
934                             && ((TheMessage->cm_anon_type == MES_ANON)
935                              || (TheMessage->cm_anon_type == MES_AN2))) {
936                                 sprintf(&display_name[strlen(display_name)],
937                                         " [%s]", buf);
938                         }
939                 }
940
941                 strcpy(allkeys, FORDER);
942                 for (i=0; i<strlen(allkeys); ++i) {
943                         k = (int) allkeys[i];
944                         if (k != 'M') {
945                                 if (TheMessage->cm_fields[k] != NULL) {
946                                         if (k == 'A') {
947                                                 if (do_proto) cprintf("%s=%s\n",
948                                                         msgkeys[k],
949                                                         display_name);
950                                         }
951                                         else {
952                                                 if (do_proto) cprintf("%s=%s\n",
953                                                         msgkeys[k],
954                                                         TheMessage->cm_fields[k]
955                                         );
956                                         }
957                                 }
958                         }
959                 }
960
961         }
962
963         /* begin header processing loop for RFC822 transfer format */
964
965         strcpy(suser, "");
966         strcpy(luser, "");
967         strcpy(fuser, "");
968         strcpy(snode, NODENAME);
969         strcpy(lnode, HUMANNODE);
970         if (mode == MT_RFC822) {
971                 cprintf("X-UIDL: %ld%s", msg_num, nl);
972                 for (i = 0; i < 256; ++i) {
973                         if (TheMessage->cm_fields[i]) {
974                                 mptr = TheMessage->cm_fields[i];
975
976                                 if (i == 'A') {
977                                         strcpy(luser, mptr);
978                                         strcpy(suser, mptr);
979                                 }
980 /****
981  "Path:" removed for now because it confuses brain-dead Microsoft shitware
982  into thinking that mail messages are newsgroup messages instead.  When we
983  add NNTP support back into Citadel we'll have to add code to only output
984  this field when appropriate.
985                                 else if (i == 'P') {
986                                         cprintf("Path: %s%s", mptr, nl);
987                                 }
988  ****/
989                                 else if (i == 'U')
990                                         cprintf("Subject: %s%s", mptr, nl);
991                                 else if (i == 'I')
992                                         strcpy(mid, mptr);
993                                 else if (i == 'H')
994                                         strcpy(lnode, mptr);
995                                 else if (i == 'O')
996                                         cprintf("X-Citadel-Room: %s%s",
997                                                 mptr, nl);
998                                 else if (i == 'N')
999                                         strcpy(snode, mptr);
1000                                 else if (i == 'R')
1001                                         cprintf("To: %s%s", mptr, nl);
1002                                 else if (i == 'T') {
1003                                         datestring(datestamp, atol(mptr),
1004                                                 DATESTRING_RFC822 );
1005                                         cprintf("Date: %s%s", datestamp, nl);
1006                                 }
1007                         }
1008                 }
1009         }
1010
1011         for (i=0; i<strlen(suser); ++i) {
1012                 suser[i] = tolower(suser[i]);
1013                 if (!isalnum(suser[i])) suser[i]='_';
1014         }
1015
1016         if (mode == MT_RFC822) {
1017                 if (!strcasecmp(snode, NODENAME)) {
1018                         strcpy(snode, FQDN);
1019                 }
1020
1021                 /* Construct a fun message id */
1022                 cprintf("Message-ID: <%s", mid);
1023                 if (strchr(mid, '@')==NULL) {
1024                         cprintf("@%s", snode);
1025                 }
1026                 cprintf(">%s", nl);
1027
1028                 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1029
1030                 if (strlen(fuser) > 0) {
1031                         cprintf("From: %s (%s)%s", fuser, luser, nl);
1032                 }
1033                 else {
1034                         cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1035                 }
1036
1037                 cprintf("Organization: %s%s", lnode, nl);
1038         }
1039
1040         /* end header processing loop ... at this point, we're in the text */
1041
1042         mptr = TheMessage->cm_fields['M'];
1043
1044         /* Tell the client about the MIME parts in this message */
1045         if (TheMessage->cm_format_type == FMT_RFC822) {
1046                 if (mode == MT_CITADEL) {
1047                         mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1048                 }
1049                 else if (mode == MT_MIME) {     /* list parts only */
1050                         mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1051                         if (do_proto) cprintf("000\n");
1052                         return(om_ok);
1053                 }
1054                 else if (mode == MT_RFC822) {   /* unparsed RFC822 dump */
1055                         /* FIXME ... we have to put some code in here to avoid
1056                          * printing duplicate header information when both
1057                          * Citadel and RFC822 headers exist.  Preference should
1058                          * probably be given to the RFC822 headers.
1059                          */
1060                         while (ch=*(mptr++), ch!=0) {
1061                                 if (ch==13) ;
1062                                 else if (ch==10) cprintf("%s", nl);
1063                                 else cprintf("%c", ch);
1064                         }
1065                         if (do_proto) cprintf("000\n");
1066                         return(om_ok);
1067                 }
1068         }
1069
1070         if (headers_only) {
1071                 if (do_proto) cprintf("000\n");
1072                 return(om_ok);
1073         }
1074
1075         /* signify start of msg text */
1076         if (mode == MT_CITADEL)
1077                 if (do_proto) cprintf("text\n");
1078         if (mode == MT_RFC822) {
1079                 if (TheMessage->cm_fields['U'] == NULL) {
1080                         cprintf("Subject: (no subject)%s", nl);
1081                 }
1082                 cprintf("%s", nl);
1083         }
1084
1085         /* If the format type on disk is 1 (fixed-format), then we want
1086          * everything to be output completely literally ... regardless of
1087          * what message transfer format is in use.
1088          */
1089         if (TheMessage->cm_format_type == FMT_FIXED) {
1090                 strcpy(buf, "");
1091                 while (ch = *mptr++, ch > 0) {
1092                         if (ch == 13)
1093                                 ch = 10;
1094                         if ((ch == 10) || (strlen(buf) > 250)) {
1095                                 cprintf("%s%s", buf, nl);
1096                                 strcpy(buf, "");
1097                         } else {
1098                                 buf[strlen(buf) + 1] = 0;
1099                                 buf[strlen(buf)] = ch;
1100                         }
1101                 }
1102                 if (strlen(buf) > 0)
1103                         cprintf("%s%s", buf, nl);
1104         }
1105
1106         /* If the message on disk is format 0 (Citadel vari-format), we
1107          * output using the formatter at 80 columns.  This is the final output
1108          * form if the transfer format is RFC822, but if the transfer format
1109          * is Citadel proprietary, it'll still work, because the indentation
1110          * for new paragraphs is correct and the client will reformat the
1111          * message to the reader's screen width.
1112          */
1113         if (TheMessage->cm_format_type == FMT_CITADEL) {
1114                 memfmout(80, mptr, 0, nl);
1115         }
1116
1117         /* If the message on disk is format 4 (MIME), we've gotta hand it
1118          * off to the MIME parser.  The client has already been told that
1119          * this message is format 1 (fixed format), so the callback function
1120          * we use will display those parts as-is.
1121          */
1122         if (TheMessage->cm_format_type == FMT_RFC822) {
1123                 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1124                 memset(ma, 0, sizeof(struct ma_info));
1125                 mime_parser(mptr, NULL, *fixed_output, NULL, 0);
1126         }
1127
1128         /* now we're done */
1129         if (do_proto) cprintf("000\n");
1130         return(om_ok);
1131 }
1132
1133
1134
1135 /*
1136  * display a message (mode 0 - Citadel proprietary)
1137  */
1138 void cmd_msg0(char *cmdbuf)
1139 {
1140         long msgid;
1141         int headers_only = 0;
1142
1143         msgid = extract_long(cmdbuf, 0);
1144         headers_only = extract_int(cmdbuf, 1);
1145
1146         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1147         return;
1148 }
1149
1150
1151 /*
1152  * display a message (mode 2 - RFC822)
1153  */
1154 void cmd_msg2(char *cmdbuf)
1155 {
1156         long msgid;
1157         int headers_only = 0;
1158
1159         msgid = extract_long(cmdbuf, 0);
1160         headers_only = extract_int(cmdbuf, 1);
1161
1162         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1163 }
1164
1165
1166
1167 /* 
1168  * display a message (mode 3 - IGnet raw format - internal programs only)
1169  */
1170 void cmd_msg3(char *cmdbuf)
1171 {
1172         long msgnum;
1173         struct CtdlMessage *msg;
1174         struct ser_ret smr;
1175
1176         if (CC->internal_pgm == 0) {
1177                 cprintf("%d This command is for internal programs only.\n",
1178                         ERROR);
1179                 return;
1180         }
1181
1182         msgnum = extract_long(cmdbuf, 0);
1183         msg = CtdlFetchMessage(msgnum);
1184         if (msg == NULL) {
1185                 cprintf("%d Message %ld not found.\n", 
1186                         ERROR, msgnum);
1187                 return;
1188         }
1189
1190         serialize_message(&smr, msg);
1191         CtdlFreeMessage(msg);
1192
1193         if (smr.len == 0) {
1194                 cprintf("%d Unable to serialize message\n",
1195                         ERROR+INTERNAL_ERROR);
1196                 return;
1197         }
1198
1199         cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1200         client_write(smr.ser, smr.len);
1201         phree(smr.ser);
1202 }
1203
1204
1205
1206 /* 
1207  * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1208  */
1209 void cmd_msg4(char *cmdbuf)
1210 {
1211         long msgid;
1212
1213         msgid = extract_long(cmdbuf, 0);
1214         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1215 }
1216
1217 /*
1218  * Open a component of a MIME message as a download file 
1219  */
1220 void cmd_opna(char *cmdbuf)
1221 {
1222         long msgid;
1223
1224         CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1225
1226         msgid = extract_long(cmdbuf, 0);
1227         extract(desired_section, cmdbuf, 1);
1228
1229         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1230 }                       
1231
1232
1233 /*
1234  * Save a message pointer into a specified room
1235  * (Returns 0 for success, nonzero for failure)
1236  * roomname may be NULL to use the current room
1237  */
1238 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1239         int i;
1240         char hold_rm[ROOMNAMELEN];
1241         struct cdbdata *cdbfr;
1242         int num_msgs;
1243         long *msglist;
1244         long highest_msg = 0L;
1245         struct CtdlMessage *msg = NULL;
1246
1247         lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1248                 roomname, msgid, flags);
1249
1250         strcpy(hold_rm, CC->quickroom.QRname);
1251
1252         /* We may need to check to see if this message is real */
1253         if (  (flags & SM_VERIFY_GOODNESS)
1254            || (flags & SM_DO_REPL_CHECK)
1255            ) {
1256                 msg = CtdlFetchMessage(msgid);
1257                 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1258         }
1259
1260         /* Perform replication checks if necessary */
1261         if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1262
1263                 if (getroom(&CC->quickroom,
1264                    ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1265                    != 0) {
1266                         lprintf(9, "No such room <%s>\n", roomname);
1267                         if (msg != NULL) CtdlFreeMessage(msg);
1268                         return(ERROR + ROOM_NOT_FOUND);
1269                 }
1270
1271                 if (ReplicationChecks(msg) != 0) {
1272                         getroom(&CC->quickroom, hold_rm);
1273                         if (msg != NULL) CtdlFreeMessage(msg);
1274                         lprintf(9, "Did replication, and newer exists\n");
1275                         return(0);
1276                 }
1277         }
1278
1279         /* Now the regular stuff */
1280         if (lgetroom(&CC->quickroom,
1281            ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1282            != 0) {
1283                 lprintf(9, "No such room <%s>\n", roomname);
1284                 if (msg != NULL) CtdlFreeMessage(msg);
1285                 return(ERROR + ROOM_NOT_FOUND);
1286         }
1287
1288         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1289         if (cdbfr == NULL) {
1290                 msglist = NULL;
1291                 num_msgs = 0;
1292         } else {
1293                 msglist = mallok(cdbfr->len);
1294                 if (msglist == NULL)
1295                         lprintf(3, "ERROR malloc msglist!\n");
1296                 num_msgs = cdbfr->len / sizeof(long);
1297                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1298                 cdb_free(cdbfr);
1299         }
1300
1301
1302         /* Make sure the message doesn't already exist in this room.  It
1303          * is absolutely taboo to have more than one reference to the same
1304          * message in a room.
1305          */
1306         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1307                 if (msglist[i] == msgid) {
1308                         lputroom(&CC->quickroom);       /* unlock the room */
1309                         getroom(&CC->quickroom, hold_rm);
1310                         if (msg != NULL) CtdlFreeMessage(msg);
1311                         return(ERROR + ALREADY_EXISTS);
1312                 }
1313         }
1314
1315         /* Now add the new message */
1316         ++num_msgs;
1317         msglist = reallok(msglist,
1318                           (num_msgs * sizeof(long)));
1319
1320         if (msglist == NULL) {
1321                 lprintf(3, "ERROR: can't realloc message list!\n");
1322         }
1323         msglist[num_msgs - 1] = msgid;
1324
1325         /* Sort the message list, so all the msgid's are in order */
1326         num_msgs = sort_msglist(msglist, num_msgs);
1327
1328         /* Determine the highest message number */
1329         highest_msg = msglist[num_msgs - 1];
1330
1331         /* Write it back to disk. */
1332         cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1333                   msglist, num_msgs * sizeof(long));
1334
1335         /* Free up the memory we used. */
1336         phree(msglist);
1337
1338         /* Update the highest-message pointer and unlock the room. */
1339         CC->quickroom.QRhighest = highest_msg;
1340         lputroom(&CC->quickroom);
1341         getroom(&CC->quickroom, hold_rm);
1342
1343         /* Bump the reference count for this message. */
1344         if ((flags & SM_DONT_BUMP_REF)==0) {
1345                 AdjRefCount(msgid, +1);
1346         }
1347
1348         /* Return success. */
1349         if (msg != NULL) CtdlFreeMessage(msg);
1350         return (0);
1351 }
1352
1353
1354
1355 /*
1356  * Message base operation to send a message to the master file
1357  * (returns new message number)
1358  *
1359  * This is the back end for CtdlSaveMsg() and should not be directly
1360  * called by server-side modules.
1361  *
1362  */
1363 long send_message(struct CtdlMessage *msg,      /* pointer to buffer */
1364                 FILE *save_a_copy)              /* save a copy to disk? */
1365 {
1366         long newmsgid;
1367         long retval;
1368         char msgidbuf[SIZ];
1369         struct ser_ret smr;
1370
1371         /* Get a new message number */
1372         newmsgid = get_new_message_number();
1373         sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1374
1375         /* Generate an ID if we don't have one already */
1376         if (msg->cm_fields['I']==NULL) {
1377                 msg->cm_fields['I'] = strdoop(msgidbuf);
1378         }
1379         
1380         serialize_message(&smr, msg);
1381
1382         if (smr.len == 0) {
1383                 cprintf("%d Unable to serialize message\n",
1384                         ERROR+INTERNAL_ERROR);
1385                 return (-1L);
1386         }
1387
1388         /* Write our little bundle of joy into the message base */
1389         if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1390                       smr.ser, smr.len) < 0) {
1391                 lprintf(2, "Can't store message\n");
1392                 retval = 0L;
1393         } else {
1394                 retval = newmsgid;
1395         }
1396
1397         /* If the caller specified that a copy should be saved to a particular
1398          * file handle, do that now too.
1399          */
1400         if (save_a_copy != NULL) {
1401                 fwrite(smr.ser, smr.len, 1, save_a_copy);
1402         }
1403
1404         /* Free the memory we used for the serialized message */
1405         phree(smr.ser);
1406
1407         /* Return the *local* message ID to the caller
1408          * (even if we're storing an incoming network message)
1409          */
1410         return(retval);
1411 }
1412
1413
1414
1415 /*
1416  * Serialize a struct CtdlMessage into the format used on disk and network.
1417  * 
1418  * This function loads up a "struct ser_ret" (defined in server.h) which
1419  * contains the length of the serialized message and a pointer to the
1420  * serialized message in memory.  THE LATTER MUST BE FREED BY THE CALLER.
1421  */
1422 void serialize_message(struct ser_ret *ret,             /* return values */
1423                         struct CtdlMessage *msg)        /* unserialized msg */
1424 {
1425         size_t wlen;
1426         int i;
1427         static char *forder = FORDER;
1428
1429         if (is_valid_message(msg) == 0) return;         /* self check */
1430
1431         ret->len = 3;
1432         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1433                 ret->len = ret->len +
1434                         strlen(msg->cm_fields[(int)forder[i]]) + 2;
1435
1436         lprintf(9, "calling malloc(%d)\n", ret->len);
1437         ret->ser = mallok(ret->len);
1438         if (ret->ser == NULL) {
1439                 ret->len = 0;
1440                 return;
1441         }
1442
1443         ret->ser[0] = 0xFF;
1444         ret->ser[1] = msg->cm_anon_type;
1445         ret->ser[2] = msg->cm_format_type;
1446         wlen = 3;
1447
1448         for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1449                 ret->ser[wlen++] = (char)forder[i];
1450                 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1451                 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1452         }
1453         if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1454                 ret->len, wlen);
1455
1456         return;
1457 }
1458
1459
1460
1461 /*
1462  * Back end for the ReplicationChecks() function
1463  */
1464 void check_repl(long msgnum, void *userdata) {
1465         struct CtdlMessage *msg;
1466         time_t timestamp = (-1L);
1467
1468         lprintf(9, "check_repl() found message %ld\n", msgnum);
1469         msg = CtdlFetchMessage(msgnum);
1470         if (msg == NULL) return;
1471         if (msg->cm_fields['T'] != NULL) {
1472                 timestamp = atol(msg->cm_fields['T']);
1473         }
1474         CtdlFreeMessage(msg);
1475
1476         if (timestamp > msg_repl->highest) {
1477                 msg_repl->highest = timestamp;  /* newer! */
1478                 lprintf(9, "newer!\n");
1479                 return;
1480         }
1481         lprintf(9, "older!\n");
1482
1483         /* Existing isn't newer?  Then delete the old one(s). */
1484         CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1485 }
1486
1487
1488 /*
1489  * Check to see if any messages already exist which carry the same Extended ID
1490  * as this one.  
1491  *
1492  * If any are found:
1493  * -> With older timestamps: delete them and return 0.  Message will be saved.
1494  * -> With newer timestamps: return 1.  Message save will be aborted.
1495  */
1496 int ReplicationChecks(struct CtdlMessage *msg) {
1497         struct CtdlMessage *template;
1498         int abort_this = 0;
1499
1500         lprintf(9, "ReplicationChecks() started\n");
1501         /* No extended id?  Don't do anything. */
1502         if (msg->cm_fields['E'] == NULL) return 0;
1503         if (strlen(msg->cm_fields['E']) == 0) return 0;
1504         lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1505
1506         CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1507         strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1508         msg_repl->highest = atol(msg->cm_fields['T']);
1509
1510         template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1511         memset(template, 0, sizeof(struct CtdlMessage));
1512         template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1513
1514         CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1515                 check_repl, NULL);
1516
1517         /* If a newer message exists with the same Extended ID, abort
1518          * this save.
1519          */
1520         if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1521                 abort_this = 1;
1522                 }
1523
1524         CtdlFreeMessage(template);
1525         lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1526         return(abort_this);
1527 }
1528
1529
1530
1531
1532 /*
1533  * Save a message to disk
1534  */
1535 long CtdlSaveMsg(struct CtdlMessage *msg,       /* message to save */
1536                 char *rec,                      /* Recipient (mail) */
1537                 char *force,                    /* force a particular room? */
1538                 int supplied_mailtype)          /* local or remote type */
1539 {
1540         char aaa[100];
1541         char hold_rm[ROOMNAMELEN];
1542         char actual_rm[ROOMNAMELEN];
1543         char force_room[ROOMNAMELEN];
1544         char content_type[SIZ];                 /* We have to learn this */
1545         char recipient[SIZ];
1546         long newmsgid;
1547         char *mptr = NULL;
1548         struct usersupp userbuf;
1549         int a;
1550         struct SuppMsgInfo smi;
1551         FILE *network_fp = NULL;
1552         static int seqnum = 1;
1553         struct CtdlMessage *imsg;
1554         char *instr;
1555         int mailtype;
1556
1557         lprintf(9, "CtdlSaveMsg() called\n");
1558         if (is_valid_message(msg) == 0) return(-1);     /* self check */
1559         mailtype = supplied_mailtype;
1560
1561         /* If this message has no timestamp, we take the liberty of
1562          * giving it one, right now.
1563          */
1564         if (msg->cm_fields['T'] == NULL) {
1565                 lprintf(9, "Generating timestamp\n");
1566                 sprintf(aaa, "%ld", time(NULL));
1567                 msg->cm_fields['T'] = strdoop(aaa);
1568         }
1569
1570         /* If this message has no path, we generate one.
1571          */
1572         if (msg->cm_fields['P'] == NULL) {
1573                 lprintf(9, "Generating path\n");
1574                 if (msg->cm_fields['A'] != NULL) {
1575                         msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1576                         for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1577                                 if (isspace(msg->cm_fields['P'][a])) {
1578                                         msg->cm_fields['P'][a] = ' ';
1579                                 }
1580                         }
1581                 }
1582                 else {
1583                         msg->cm_fields['P'] = strdoop("unknown");
1584                 }
1585         }
1586
1587         strcpy(force_room, force);
1588
1589         /* Strip non-printable characters out of the recipient name */
1590         lprintf(9, "Checking recipient (if present)\n");
1591         strcpy(recipient, rec);
1592         for (a = 0; a < strlen(recipient); ++a)
1593                 if (!isprint(recipient[a]))
1594                         strcpy(&recipient[a], &recipient[a + 1]);
1595
1596         /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1597         for (a=0; a<strlen(recipient); ++a) {
1598                 if (recipient[a] == '@') {
1599                         if (CtdlHostAlias(&recipient[a+1]) 
1600                            == hostalias_localhost) {
1601                                 recipient[a] = 0;
1602                                 lprintf(7, "Changed to <%s>\n", recipient);
1603                                 mailtype = MES_LOCAL;
1604                         }
1605                 }
1606         }
1607
1608         lprintf(9, "Recipient is <%s>\n", recipient);
1609
1610         /* Learn about what's inside, because it's what's inside that counts */
1611         lprintf(9, "Learning what's inside\n");
1612         if (msg->cm_fields['M'] == NULL) {
1613                 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1614         }
1615
1616         switch (msg->cm_format_type) {
1617         case 0:
1618                 strcpy(content_type, "text/x-citadel-variformat");
1619                 break;
1620         case 1:
1621                 strcpy(content_type, "text/plain");
1622                 break;
1623         case 4:
1624                 strcpy(content_type, "text/plain");
1625                 /* advance past header fields */
1626                 mptr = msg->cm_fields['M'];
1627                 a = strlen(mptr);
1628                 while ((--a) > 0) {
1629                         if (!strncasecmp(mptr, "Content-type: ", 14)) {
1630                                 safestrncpy(content_type, mptr,
1631                                             sizeof(content_type));
1632                                 strcpy(content_type, &content_type[14]);
1633                                 for (a = 0; a < strlen(content_type); ++a)
1634                                         if ((content_type[a] == ';')
1635                                             || (content_type[a] == ' ')
1636                                             || (content_type[a] == 13)
1637                                             || (content_type[a] == 10))
1638                                                 content_type[a] = 0;
1639                                 break;
1640                         }
1641                         ++mptr;
1642                 }
1643         }
1644
1645         /* Goto the correct room */
1646         lprintf(9, "Switching rooms\n");
1647         strcpy(hold_rm, CC->quickroom.QRname);
1648         strcpy(actual_rm, CC->quickroom.QRname);
1649
1650         /* If the user is a twit, move to the twit room for posting */
1651         lprintf(9, "Handling twit stuff\n");
1652         if (TWITDETECT) {
1653                 if (CC->usersupp.axlevel == 2) {
1654                         strcpy(hold_rm, actual_rm);
1655                         strcpy(actual_rm, config.c_twitroom);
1656                 }
1657         }
1658
1659         /* ...or if this message is destined for Aide> then go there. */
1660         if (strlen(force_room) > 0) {
1661                 strcpy(actual_rm, force_room);
1662         }
1663
1664         lprintf(9, "Possibly relocating\n");
1665         if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1666                 getroom(&CC->quickroom, actual_rm);
1667         }
1668
1669         /*
1670          * If this message has no O (room) field, generate one.
1671          */
1672         if (msg->cm_fields['O'] == NULL) {
1673                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1674         }
1675
1676         /* Perform "before save" hooks (aborting if any return nonzero) */
1677         lprintf(9, "Performing before-save hooks\n");
1678         if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1679
1680         /* If this message has an Extended ID, perform replication checks */
1681         lprintf(9, "Performing replication checks\n");
1682         if (ReplicationChecks(msg) > 0) return(-1);
1683
1684         /* Network mail - send a copy to the network program. */
1685         if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1686                 lprintf(9, "Sending network spool\n");
1687                 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1688                         (long) getpid(), CC->cs_pid, ++seqnum);
1689                 lprintf(9, "Saving a copy to %s\n", aaa);
1690                 network_fp = fopen(aaa, "ab+");
1691                 if (network_fp == NULL)
1692                         lprintf(2, "ERROR: %s\n", strerror(errno));
1693         }
1694
1695         /* Save it to disk */
1696         lprintf(9, "Saving to disk\n");
1697         newmsgid = send_message(msg, network_fp);
1698         if (network_fp != NULL) {
1699                 fclose(network_fp);
1700                 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1701         }
1702
1703         if (newmsgid <= 0L) return(-1);
1704
1705         /* Write a supplemental message info record.  This doesn't have to
1706          * be a critical section because nobody else knows about this message
1707          * yet.
1708          */
1709         lprintf(9, "Creating SuppMsgInfo record\n");
1710         memset(&smi, 0, sizeof(struct SuppMsgInfo));
1711         smi.smi_msgnum = newmsgid;
1712         smi.smi_refcount = 0;
1713         safestrncpy(smi.smi_content_type, content_type, 64);
1714         PutSuppMsgInfo(&smi);
1715
1716         /* Now figure out where to store the pointers */
1717         lprintf(9, "Storing pointers\n");
1718
1719         /* If this is being done by the networker delivering a private
1720          * message, we want to BYPASS saving the sender's copy (because there
1721          * is no local sender; it would otherwise go to the Trashcan).
1722          */
1723         if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1724                 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1725                         lprintf(3, "ERROR saving message pointer!\n");
1726                         CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1727                 }
1728         }
1729
1730         /* For internet mail, drop a copy in the outbound queue room */
1731         if (mailtype == MES_INTERNET) {
1732                 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1733         }
1734
1735         /* Bump this user's messages posted counter. */
1736         lprintf(9, "Updating user\n");
1737         lgetuser(&CC->usersupp, CC->curr_user);
1738         CC->usersupp.posted = CC->usersupp.posted + 1;
1739         lputuser(&CC->usersupp);
1740
1741         /* If this is private, local mail, make a copy in the
1742          * recipient's mailbox and bump the reference count.
1743          */
1744         if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1745                 if (getuser(&userbuf, recipient) == 0) {
1746                         lprintf(9, "Delivering private mail\n");
1747                         MailboxName(actual_rm, &userbuf, MAILROOM);
1748                         CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1749                 }
1750                 else {
1751                         lprintf(9, "No user <%s>, saving in %s> instead\n",
1752                                 recipient, AIDEROOM);
1753                         CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1754                 }
1755         }
1756
1757         /* Perform "after save" hooks */
1758         lprintf(9, "Performing after-save hooks\n");
1759         PerformMessageHooks(msg, EVT_AFTERSAVE);
1760
1761         /* */
1762         lprintf(9, "Returning to original room\n");
1763         if (strcasecmp(hold_rm, CC->quickroom.QRname))
1764                 getroom(&CC->quickroom, hold_rm);
1765
1766         /* For internet mail, generate delivery instructions 
1767          * (Yes, this is recursive!   Deal with it!)
1768          */
1769         if (mailtype == MES_INTERNET) {
1770                 lprintf(9, "Generating delivery instructions\n");
1771                 instr = mallok(2048);
1772                 sprintf(instr,
1773                         "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1774                         "bounceto|%s@%s\n"
1775                         "remote|%s|0||\n",
1776                         SPOOLMIME, newmsgid, time(NULL),
1777                         msg->cm_fields['A'], msg->cm_fields['N'],
1778                         recipient );
1779
1780                 imsg = mallok(sizeof(struct CtdlMessage));
1781                 memset(imsg, 0, sizeof(struct CtdlMessage));
1782                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1783                 imsg->cm_anon_type = MES_NORMAL;
1784                 imsg->cm_format_type = FMT_RFC822;
1785                 imsg->cm_fields['A'] = strdoop("Citadel");
1786                 imsg->cm_fields['M'] = instr;
1787                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1788                 CtdlFreeMessage(imsg);
1789         }
1790
1791         return(newmsgid);
1792 }
1793
1794
1795
1796 /*
1797  * Convenience function for generating small administrative messages.
1798  */
1799 void quickie_message(char *from, char *to, char *room, char *text)
1800 {
1801         struct CtdlMessage *msg;
1802
1803         msg = mallok(sizeof(struct CtdlMessage));
1804         memset(msg, 0, sizeof(struct CtdlMessage));
1805         msg->cm_magic = CTDLMESSAGE_MAGIC;
1806         msg->cm_anon_type = MES_NORMAL;
1807         msg->cm_format_type = 0;
1808         msg->cm_fields['A'] = strdoop(from);
1809         msg->cm_fields['O'] = strdoop(room);
1810         msg->cm_fields['N'] = strdoop(NODENAME);
1811         if (to != NULL)
1812                 msg->cm_fields['R'] = strdoop(to);
1813         msg->cm_fields['M'] = strdoop(text);
1814
1815         CtdlSaveMsg(msg, "", room, MES_LOCAL);
1816         CtdlFreeMessage(msg);
1817         syslog(LOG_NOTICE, text);
1818 }
1819
1820
1821
1822 /*
1823  * Back end function used by make_message() and similar functions
1824  */
1825 char *CtdlReadMessageBody(char *terminator,     /* token signalling EOT */
1826                         size_t maxlen,          /* maximum message length */
1827                         char *exist             /* if non-null, append to it;
1828                                                    exist is ALWAYS freed  */
1829                         ) {
1830         char buf[SIZ];
1831         int linelen;
1832         size_t message_len = 0;
1833         size_t buffer_len = 0;
1834         char *ptr;
1835         char *m;
1836
1837         if (exist == NULL) {
1838                 m = mallok(4096);
1839         }
1840         else {
1841                 m = reallok(exist, strlen(exist) + 4096);
1842                 if (m == NULL) phree(exist);
1843         }
1844         if (m == NULL) {
1845                 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1846                 return(NULL);
1847         } else {
1848                 buffer_len = 4096;
1849                 m[0] = 0;
1850                 message_len = 0;
1851         }
1852         /* read in the lines of message text one by one */
1853         while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1854
1855                 /* strip trailing newline type stuff */
1856                 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1857                 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1858
1859                 linelen = strlen(buf);
1860
1861                 /* augment the buffer if we have to */
1862                 if ((message_len + linelen + 2) > buffer_len) {
1863                         lprintf(9, "realloking\n");
1864                         ptr = reallok(m, (buffer_len * 2) );
1865                         if (ptr == NULL) {      /* flush if can't allocate */
1866                                 while ( (client_gets(buf)>0) &&
1867                                         strcmp(buf, terminator)) ;;
1868                                 return(m);
1869                         } else {
1870                                 buffer_len = (buffer_len * 2);
1871                                 m = ptr;
1872                                 lprintf(9, "buffer_len is %d\n", buffer_len);
1873                         }
1874                 }
1875
1876                 /* Add the new line to the buffer.  We avoid using strcat()
1877                  * because that would involve traversing the entire message
1878                  * after each line, and this function needs to run fast.
1879                  */
1880                 strcpy(&m[message_len], buf);
1881                 m[message_len + linelen] = '\n';
1882                 m[message_len + linelen + 1] = 0;
1883                 message_len = message_len + linelen + 1;
1884
1885                 /* if we've hit the max msg length, flush the rest */
1886                 if (message_len >= maxlen) {
1887                         while ( (client_gets(buf)>0)
1888                                 && strcmp(buf, terminator)) ;;
1889                         return(m);
1890                 }
1891         }
1892         return(m);
1893 }
1894
1895
1896
1897
1898 /*
1899  * Build a binary message to be saved on disk.
1900  */
1901
1902 struct CtdlMessage *make_message(
1903         struct usersupp *author,        /* author's usersupp structure */
1904         char *recipient,                /* NULL if it's not mail */
1905         char *room,                     /* room where it's going */
1906         int type,                       /* see MES_ types in header file */
1907         int net_type,                   /* see MES_ types in header file */
1908         int format_type,                /* local or remote (see citadel.h) */
1909         char *fake_name)                /* who we're masquerading as */
1910 {
1911
1912         int a;
1913         char dest_node[32];
1914         char buf[SIZ];
1915         struct CtdlMessage *msg;
1916
1917         msg = mallok(sizeof(struct CtdlMessage));
1918         memset(msg, 0, sizeof(struct CtdlMessage));
1919         msg->cm_magic = CTDLMESSAGE_MAGIC;
1920         msg->cm_anon_type = type;
1921         msg->cm_format_type = format_type;
1922
1923         /* Don't confuse the poor folks if it's not routed mail. */
1924         strcpy(dest_node, "");
1925
1926         /* If net_type is MES_BINARY, split out the destination node. */
1927         if (net_type == MES_BINARY) {
1928                 strcpy(dest_node, NODENAME);
1929                 for (a = 0; a < strlen(recipient); ++a) {
1930                         if (recipient[a] == '@') {
1931                                 recipient[a] = 0;
1932                                 strcpy(dest_node, &recipient[a + 1]);
1933                         }
1934                 }
1935         }
1936
1937         /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1938         if (net_type == MES_INTERNET) {
1939                 strcpy(dest_node, "internet");
1940         }
1941
1942         while (isspace(recipient[strlen(recipient) - 1]))
1943                 recipient[strlen(recipient) - 1] = 0;
1944
1945         sprintf(buf, "cit%ld", author->usernum);                /* Path */
1946         msg->cm_fields['P'] = strdoop(buf);
1947
1948         sprintf(buf, "%ld", time(NULL));                        /* timestamp */
1949         msg->cm_fields['T'] = strdoop(buf);
1950
1951         if (fake_name[0])                                       /* author */
1952                 msg->cm_fields['A'] = strdoop(fake_name);
1953         else
1954                 msg->cm_fields['A'] = strdoop(author->fullname);
1955
1956         if (CC->quickroom.QRflags & QR_MAILBOX)                 /* room */
1957                 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1958         else
1959                 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1960
1961         msg->cm_fields['N'] = strdoop(NODENAME);                /* nodename */
1962         msg->cm_fields['H'] = strdoop(HUMANNODE);               /* hnodename */
1963
1964         if (recipient[0] != 0)
1965                 msg->cm_fields['R'] = strdoop(recipient);
1966         if (dest_node[0] != 0)
1967                 msg->cm_fields['D'] = strdoop(dest_node);
1968
1969
1970         msg->cm_fields['M'] = CtdlReadMessageBody("000",
1971                                                 config.c_maxmsglen, NULL);
1972
1973
1974         return(msg);
1975 }
1976
1977
1978
1979
1980
1981 /*
1982  * message entry  -  mode 0 (normal)
1983  */
1984 void cmd_ent0(char *entargs)
1985 {
1986         int post = 0;
1987         char recipient[SIZ];
1988         int anon_flag = 0;
1989         int format_type = 0;
1990         char newusername[SIZ];
1991         struct CtdlMessage *msg;
1992         int a, b;
1993         int e = 0;
1994         int mtsflag = 0;
1995         struct usersupp tempUS;
1996         char buf[SIZ];
1997
1998         post = extract_int(entargs, 0);
1999         extract(recipient, entargs, 1);
2000         anon_flag = extract_int(entargs, 2);
2001         format_type = extract_int(entargs, 3);
2002
2003         /* first check to make sure the request is valid. */
2004
2005         if (!(CC->logged_in)) {
2006                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
2007                 return;
2008         }
2009         if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2010                 cprintf("%d Need to be validated to enter ",
2011                         ERROR + HIGHER_ACCESS_REQUIRED);
2012                 cprintf("(except in %s> to sysop)\n", MAILROOM);
2013                 return;
2014         }
2015         if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
2016                 cprintf("%d Need net privileges to enter here.\n",
2017                         ERROR + HIGHER_ACCESS_REQUIRED);
2018                 return;
2019         }
2020         if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
2021                 cprintf("%d Sorry, this is a read-only room.\n",
2022                         ERROR + HIGHER_ACCESS_REQUIRED);
2023                 return;
2024         }
2025         mtsflag = 0;
2026
2027
2028         if (post == 2) {
2029                 if (CC->usersupp.axlevel < 6) {
2030                         cprintf("%d You don't have permission to masquerade.\n",
2031                                 ERROR + HIGHER_ACCESS_REQUIRED);
2032                         return;
2033                 }
2034                 extract(newusername, entargs, 4);
2035                 memset(CC->fake_postname, 0, 32);
2036                 strcpy(CC->fake_postname, newusername);
2037                 cprintf("%d Ok\n", OK);
2038                 return;
2039         }
2040         CC->cs_flags |= CS_POSTING;
2041
2042         buf[0] = 0;
2043         if (CC->quickroom.QRflags & QR_MAILBOX) {
2044                 if (CC->usersupp.axlevel >= 2) {
2045                         strcpy(buf, recipient);
2046                 } else
2047                         strcpy(buf, "sysop");
2048                 e = alias(buf); /* alias and mail type */
2049                 if ((buf[0] == 0) || (e == MES_ERROR)) {
2050                         cprintf("%d Unknown address - cannot send message.\n",
2051                                 ERROR + NO_SUCH_USER);
2052                         return;
2053                 }
2054                 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2055                         cprintf("%d Net privileges required for network mail.\n",
2056                                 ERROR + HIGHER_ACCESS_REQUIRED);
2057                         return;
2058                 }
2059                 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2060                     && ((CC->usersupp.flags & US_INTERNET) == 0)
2061                     && (!CC->internal_pgm)) {
2062                         cprintf("%d You don't have access to Internet mail.\n",
2063                                 ERROR + HIGHER_ACCESS_REQUIRED);
2064                         return;
2065                 }
2066                 if (!strcasecmp(buf, "sysop")) {
2067                         mtsflag = 1;
2068                 }
2069                 else if (e == MES_LOCAL) {      /* don't search local file */
2070                         if (!strcasecmp(buf, CC->usersupp.fullname)) {
2071                                 cprintf("%d Can't send mail to yourself!\n",
2072                                         ERROR + NO_SUCH_USER);
2073                                 return;
2074                         }
2075                         /* Check to make sure the user exists; also get the correct
2076                          * upper/lower casing of the name.
2077                          */
2078                         a = getuser(&tempUS, buf);
2079                         if (a != 0) {
2080                                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2081                                 return;
2082                         }
2083                         strcpy(buf, tempUS.fullname);
2084                 }
2085         }
2086
2087         b = MES_NORMAL;
2088         if (CC->quickroom.QRflags & QR_ANONONLY)
2089                 b = MES_ANON;
2090         if (CC->quickroom.QRflags & QR_ANONOPT) {
2091                 if (anon_flag == 1)
2092                         b = MES_AN2;
2093         }
2094         if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2095                 buf[0] = 0;
2096
2097         /* If we're only checking the validity of the request, return
2098          * success without creating the message.
2099          */
2100         if (post == 0) {
2101                 cprintf("%d %s\n", OK, buf);
2102                 return;
2103         }
2104
2105         cprintf("%d send message\n", SEND_LISTING);
2106
2107         /* Read in the message from the client. */
2108         if (CC->fake_postname[0])
2109                 msg = make_message(&CC->usersupp, buf,
2110                         CC->quickroom.QRname, b, e, format_type,
2111                         CC->fake_postname);
2112         else if (CC->fake_username[0])
2113                 msg = make_message(&CC->usersupp, buf,
2114                         CC->quickroom.QRname, b, e, format_type,
2115                         CC->fake_username);
2116         else
2117                 msg = make_message(&CC->usersupp, buf,
2118                         CC->quickroom.QRname, b, e, format_type, "");
2119
2120         if (msg != NULL)
2121                 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2122                 CtdlFreeMessage(msg);
2123         CC->fake_postname[0] = '\0';
2124         return;
2125 }
2126
2127
2128
2129 /* 
2130  * message entry - mode 3 (raw)
2131  */
2132 void cmd_ent3(char *entargs)
2133 {
2134         char recp[SIZ];
2135         int a;
2136         int e = 0;
2137         int valid_msg = 1;
2138         unsigned char ch, which_field;
2139         struct usersupp tempUS;
2140         long msglen;
2141         struct CtdlMessage *msg;
2142         char *tempbuf;
2143
2144         if (CC->internal_pgm == 0) {
2145                 cprintf("%d This command is for internal programs only.\n",
2146                         ERROR);
2147                 return;
2148         }
2149
2150         /* See if there's a recipient, but make sure it's a real one */
2151         extract(recp, entargs, 1);
2152         for (a = 0; a < strlen(recp); ++a)
2153                 if (!isprint(recp[a]))
2154                         strcpy(&recp[a], &recp[a + 1]);
2155         while (isspace(recp[0]))
2156                 strcpy(recp, &recp[1]);
2157         while (isspace(recp[strlen(recp) - 1]))
2158                 recp[strlen(recp) - 1] = 0;
2159
2160         /* If we're in Mail, check the recipient */
2161         if (strlen(recp) > 0) {
2162                 e = alias(recp);        /* alias and mail type */
2163                 if ((recp[0] == 0) || (e == MES_ERROR)) {
2164                         cprintf("%d Unknown address - cannot send message.\n",
2165                                 ERROR + NO_SUCH_USER);
2166                         return;
2167                 }
2168                 if (e == MES_LOCAL) {
2169                         a = getuser(&tempUS, recp);
2170                         if (a != 0) {
2171                                 cprintf("%d No such user.\n",
2172                                         ERROR + NO_SUCH_USER);
2173                                 return;
2174                         }
2175                 }
2176         }
2177
2178         /* At this point, message has been approved. */
2179         if (extract_int(entargs, 0) == 0) {
2180                 cprintf("%d OK to send\n", OK);
2181                 return;
2182         }
2183
2184         msglen = extract_long(entargs, 2);
2185         msg = mallok(sizeof(struct CtdlMessage));
2186         if (msg == NULL) {
2187                 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2188                 return;
2189         }
2190
2191         memset(msg, 0, sizeof(struct CtdlMessage));
2192         tempbuf = mallok(msglen);
2193         if (tempbuf == NULL) {
2194                 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2195                 phree(msg);
2196                 return;
2197         }
2198
2199         cprintf("%d %ld\n", SEND_BINARY, msglen);
2200
2201         client_read(&ch, 1);                            /* 0xFF magic number */
2202         msg->cm_magic = CTDLMESSAGE_MAGIC;
2203         client_read(&ch, 1);                            /* anon type */
2204         msg->cm_anon_type = ch;
2205         client_read(&ch, 1);                            /* format type */
2206         msg->cm_format_type = ch;
2207         msglen = msglen - 3;
2208
2209         while (msglen > 0) {
2210                 client_read(&which_field, 1);
2211                 if (!isalpha(which_field)) valid_msg = 0;
2212                 --msglen;
2213                 tempbuf[0] = 0;
2214                 do {
2215                         client_read(&ch, 1);
2216                         --msglen;
2217                         a = strlen(tempbuf);
2218                         tempbuf[a+1] = 0;
2219                         tempbuf[a] = ch;
2220                 } while ( (ch != 0) && (msglen > 0) );
2221                 if (valid_msg)
2222                         msg->cm_fields[which_field] = strdoop(tempbuf);
2223         }
2224
2225         msg->cm_flags = CM_SKIP_HOOKS;
2226         if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2227         CtdlFreeMessage(msg);
2228         phree(tempbuf);
2229 }
2230
2231
2232 /*
2233  * API function to delete messages which match a set of criteria
2234  * (returns the actual number of messages deleted)
2235  */
2236 int CtdlDeleteMessages(char *room_name,         /* which room */
2237                        long dmsgnum,            /* or "0" for any */
2238                        char *content_type       /* or "" for any */
2239 )
2240 {
2241
2242         struct quickroom qrbuf;
2243         struct cdbdata *cdbfr;
2244         long *msglist = NULL;
2245         int num_msgs = 0;
2246         int i;
2247         int num_deleted = 0;
2248         int delete_this;
2249         struct SuppMsgInfo smi;
2250
2251         lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2252                 room_name, dmsgnum, content_type);
2253
2254         /* get room record, obtaining a lock... */
2255         if (lgetroom(&qrbuf, room_name) != 0) {
2256                 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2257                         room_name);
2258                 return (0);     /* room not found */
2259         }
2260         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2261
2262         if (cdbfr != NULL) {
2263                 msglist = mallok(cdbfr->len);
2264                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2265                 num_msgs = cdbfr->len / sizeof(long);
2266                 cdb_free(cdbfr);
2267         }
2268         if (num_msgs > 0) {
2269                 for (i = 0; i < num_msgs; ++i) {
2270                         delete_this = 0x00;
2271
2272                         /* Set/clear a bit for each criterion */
2273
2274                         if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2275                                 delete_this |= 0x01;
2276                         }
2277                         if (strlen(content_type) == 0) {
2278                                 delete_this |= 0x02;
2279                         } else {
2280                                 GetSuppMsgInfo(&smi, msglist[i]);
2281                                 if (!strcasecmp(smi.smi_content_type,
2282                                                 content_type)) {
2283                                         delete_this |= 0x02;
2284                                 }
2285                         }
2286
2287                         /* Delete message only if all bits are set */
2288                         if (delete_this == 0x03) {
2289                                 AdjRefCount(msglist[i], -1);
2290                                 msglist[i] = 0L;
2291                                 ++num_deleted;
2292                         }
2293                 }
2294
2295                 num_msgs = sort_msglist(msglist, num_msgs);
2296                 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2297                           msglist, (num_msgs * sizeof(long)));
2298
2299                 qrbuf.QRhighest = msglist[num_msgs - 1];
2300                 phree(msglist);
2301         }
2302         lputroom(&qrbuf);
2303         lprintf(9, "%d message(s) deleted.\n", num_deleted);
2304         return (num_deleted);
2305 }
2306
2307
2308
2309 /*
2310  * Delete message from current room
2311  */
2312 void cmd_dele(char *delstr)
2313 {
2314         long delnum;
2315         int num_deleted;
2316
2317         getuser(&CC->usersupp, CC->curr_user);
2318         if ((CC->usersupp.axlevel < 6)
2319             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2320             && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2321             && (!(CC->internal_pgm))) {
2322                 cprintf("%d Higher access required.\n",
2323                         ERROR + HIGHER_ACCESS_REQUIRED);
2324                 return;
2325         }
2326         delnum = extract_long(delstr, 0);
2327
2328         num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2329
2330         if (num_deleted) {
2331                 cprintf("%d %d message%s deleted.\n", OK,
2332                         num_deleted, ((num_deleted != 1) ? "s" : ""));
2333         } else {
2334                 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2335         }
2336 }
2337
2338
2339 /*
2340  * move or copy a message to another room
2341  */
2342 void cmd_move(char *args)
2343 {
2344         long num;
2345         char targ[SIZ];
2346         struct quickroom qtemp;
2347         int err;
2348         int is_copy = 0;
2349
2350         num = extract_long(args, 0);
2351         extract(targ, args, 1);
2352         targ[ROOMNAMELEN - 1] = 0;
2353         is_copy = extract_int(args, 2);
2354
2355         getuser(&CC->usersupp, CC->curr_user);
2356         if ((CC->usersupp.axlevel < 6)
2357             && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2358                 cprintf("%d Higher access required.\n",
2359                         ERROR + HIGHER_ACCESS_REQUIRED);
2360                 return;
2361         }
2362
2363         if (getroom(&qtemp, targ) != 0) {
2364                 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2365                 return;
2366         }
2367
2368         err = CtdlSaveMsgPointerInRoom(targ, num,
2369                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2370         if (err != 0) {
2371                 cprintf("%d Cannot store message in %s: error %d\n",
2372                         err, targ, err);
2373                 return;
2374         }
2375
2376         /* Now delete the message from the source room,
2377          * if this is a 'move' rather than a 'copy' operation.
2378          */
2379         if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2380
2381         cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2382 }
2383
2384
2385
2386 /*
2387  * GetSuppMsgInfo()  -  Get the supplementary record for a message
2388  */
2389 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2390 {
2391
2392         struct cdbdata *cdbsmi;
2393         long TheIndex;
2394
2395         memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2396         smibuf->smi_msgnum = msgnum;
2397         smibuf->smi_refcount = 1;       /* Default reference count is 1 */
2398
2399         /* Use the negative of the message number for its supp record index */
2400         TheIndex = (0L - msgnum);
2401
2402         cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2403         if (cdbsmi == NULL) {
2404                 return;         /* record not found; go with defaults */
2405         }
2406         memcpy(smibuf, cdbsmi->ptr,
2407                ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2408                 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2409         cdb_free(cdbsmi);
2410         return;
2411 }
2412
2413
2414 /*
2415  * PutSuppMsgInfo()  -  (re)write supplementary record for a message
2416  */
2417 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2418 {
2419         long TheIndex;
2420
2421         /* Use the negative of the message number for its supp record index */
2422         TheIndex = (0L - smibuf->smi_msgnum);
2423
2424         lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2425                 smibuf->smi_msgnum, smibuf->smi_refcount);
2426
2427         cdb_store(CDB_MSGMAIN,
2428                   &TheIndex, sizeof(long),
2429                   smibuf, sizeof(struct SuppMsgInfo));
2430
2431 }
2432
2433 /*
2434  * AdjRefCount  -  change the reference count for a message;
2435  *                 delete the message if it reaches zero
2436  */
2437 void AdjRefCount(long msgnum, int incr)
2438 {
2439
2440         struct SuppMsgInfo smi;
2441         long delnum;
2442
2443         /* This is a *tight* critical section; please keep it that way, as
2444          * it may get called while nested in other critical sections.  
2445          * Complicating this any further will surely cause deadlock!
2446          */
2447         begin_critical_section(S_SUPPMSGMAIN);
2448         GetSuppMsgInfo(&smi, msgnum);
2449         lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2450                 msgnum, smi.smi_refcount);
2451         smi.smi_refcount += incr;
2452         PutSuppMsgInfo(&smi);
2453         end_critical_section(S_SUPPMSGMAIN);
2454         lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2455                 msgnum, smi.smi_refcount);
2456
2457         /* If the reference count is now zero, delete the message
2458          * (and its supplementary record as well).
2459          */
2460         if (smi.smi_refcount == 0) {
2461                 lprintf(9, "Deleting message <%ld>\n", msgnum);
2462                 delnum = msgnum;
2463                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2464                 delnum = (0L - msgnum);
2465                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2466         }
2467 }
2468
2469 /*
2470  * Write a generic object to this room
2471  *
2472  * Note: this could be much more efficient.  Right now we use two temporary
2473  * files, and still pull the message into memory as with all others.
2474  */
2475 void CtdlWriteObject(char *req_room,            /* Room to stuff it in */
2476                         char *content_type,     /* MIME type of this object */
2477                         char *tempfilename,     /* Where to fetch it from */
2478                         struct usersupp *is_mailbox,    /* Mailbox room? */
2479                         int is_binary,          /* Is encoding necessary? */
2480                         int is_unique,          /* Del others of this type? */
2481                         unsigned int flags      /* Internal save flags */
2482                         )
2483 {
2484
2485         FILE *fp, *tempfp;
2486         char filename[PATH_MAX];
2487         char cmdbuf[SIZ];
2488         char ch;
2489         struct quickroom qrbuf;
2490         char roomname[ROOMNAMELEN];
2491         struct CtdlMessage *msg;
2492         size_t len;
2493
2494         if (is_mailbox != NULL)
2495                 MailboxName(roomname, is_mailbox, req_room);
2496         else
2497                 safestrncpy(roomname, req_room, sizeof(roomname));
2498         lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2499
2500         strcpy(filename, tmpnam(NULL));
2501         fp = fopen(filename, "w");
2502         if (fp == NULL)
2503                 return;
2504
2505         tempfp = fopen(tempfilename, "r");
2506         if (tempfp == NULL) {
2507                 fclose(fp);
2508                 unlink(filename);
2509                 return;
2510         }
2511
2512         fprintf(fp, "Content-type: %s\n", content_type);
2513         lprintf(9, "Content-type: %s\n", content_type);
2514
2515         if (is_binary == 0) {
2516                 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2517                 while (ch = getc(tempfp), ch > 0)
2518                         putc(ch, fp);
2519                 fclose(tempfp);
2520                 putc(0, fp);
2521                 fclose(fp);
2522         } else {
2523                 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2524                 fclose(tempfp);
2525                 fclose(fp);
2526                 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2527                         tempfilename, filename);
2528                 system(cmdbuf);
2529         }
2530
2531         lprintf(9, "Allocating\n");
2532         msg = mallok(sizeof(struct CtdlMessage));
2533         memset(msg, 0, sizeof(struct CtdlMessage));
2534         msg->cm_magic = CTDLMESSAGE_MAGIC;
2535         msg->cm_anon_type = MES_NORMAL;
2536         msg->cm_format_type = 4;
2537         msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2538         msg->cm_fields['O'] = strdoop(req_room);
2539         msg->cm_fields['N'] = strdoop(config.c_nodename);
2540         msg->cm_fields['H'] = strdoop(config.c_humannode);
2541         msg->cm_flags = flags;
2542         
2543         lprintf(9, "Loading\n");
2544         fp = fopen(filename, "rb");
2545         fseek(fp, 0L, SEEK_END);
2546         len = ftell(fp);
2547         rewind(fp);
2548         msg->cm_fields['M'] = mallok(len);
2549         fread(msg->cm_fields['M'], len, 1, fp);
2550         fclose(fp);
2551         unlink(filename);
2552
2553         /* Create the requested room if we have to. */
2554         if (getroom(&qrbuf, roomname) != 0) {
2555                 create_room(roomname, 
2556                         ( (is_mailbox != NULL) ? 4 : 3 ),
2557                         "", 0);
2558         }
2559         /* If the caller specified this object as unique, delete all
2560          * other objects of this type that are currently in the room.
2561          */
2562         if (is_unique) {
2563                 lprintf(9, "Deleted %d other msgs of this type\n",
2564                         CtdlDeleteMessages(roomname, 0L, content_type));
2565         }
2566         /* Now write the data */
2567         CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2568         CtdlFreeMessage(msg);
2569 }
2570
2571
2572
2573
2574
2575
2576 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2577         config_msgnum = msgnum;
2578 }
2579
2580
2581 char *CtdlGetSysConfig(char *sysconfname) {
2582         char hold_rm[ROOMNAMELEN];
2583         long msgnum;
2584         char *conf;
2585         struct CtdlMessage *msg;
2586         char buf[SIZ];
2587         
2588         strcpy(hold_rm, CC->quickroom.QRname);
2589         if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2590                 getroom(&CC->quickroom, hold_rm);
2591                 return NULL;
2592         }
2593
2594
2595         /* We want the last (and probably only) config in this room */
2596         begin_critical_section(S_CONFIG);
2597         config_msgnum = (-1L);
2598         CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2599                 CtdlGetSysConfigBackend, NULL);
2600         msgnum = config_msgnum;
2601         end_critical_section(S_CONFIG);
2602
2603         if (msgnum < 0L) {
2604                 conf = NULL;
2605         }
2606         else {
2607                 msg = CtdlFetchMessage(msgnum);
2608                 if (msg != NULL) {
2609                         conf = strdoop(msg->cm_fields['M']);
2610                         CtdlFreeMessage(msg);
2611                 }
2612                 else {
2613                         conf = NULL;
2614                 }
2615         }
2616
2617         getroom(&CC->quickroom, hold_rm);
2618
2619         lprintf(9, "eggstracting...\n");
2620         if (conf != NULL) do {
2621                 extract_token(buf, conf, 0, '\n');
2622                 lprintf(9, "eggstracted <%s>\n", buf);
2623                 strcpy(conf, &conf[strlen(buf)+1]);
2624         } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2625
2626         return(conf);
2627 }
2628
2629 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2630         char temp[PATH_MAX];
2631         FILE *fp;
2632
2633         strcpy(temp, tmpnam(NULL));
2634
2635         fp = fopen(temp, "w");
2636         if (fp == NULL) return;
2637         fprintf(fp, "%s", sysconfdata);
2638         fclose(fp);
2639
2640         /* this handy API function does all the work for us */
2641         CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
2642         unlink(temp);
2643 }