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