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