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