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