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