4 * Implements the message store.
26 #include "sysdep_decls.h"
27 #include "citserver.h"
32 #include "dynloader.h"
34 #include "mime_parser.h"
37 #include "internet_addressing.h"
39 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
40 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
41 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
43 extern struct config config;
47 "", "", "", "", "", "", "", "",
48 "", "", "", "", "", "", "", "",
49 "", "", "", "", "", "", "", "",
50 "", "", "", "", "", "", "", "",
51 "", "", "", "", "", "", "", "",
52 "", "", "", "", "", "", "", "",
53 "", "", "", "", "", "", "", "",
54 "", "", "", "", "", "", "", "",
81 * This function is self explanatory.
82 * (What can I say, I'm in a weird mood today...)
84 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
88 for (i = 0; i < strlen(name); ++i) {
90 while (isspace(name[i - 1]) && i > 0) {
91 strcpy(&name[i - 1], &name[i]);
94 while (isspace(name[i + 1])) {
95 strcpy(&name[i + 1], &name[i + 2]);
103 * Aliasing for network mail.
104 * (Error messages have been commented out, because this is a server.)
106 int alias(char *name)
107 { /* process alias and routing info for mail */
110 char aaa[300], bbb[300];
112 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
114 fp = fopen("network/mail.aliases", "r");
116 fp = fopen("/dev/null", "r");
121 while (fgets(aaa, sizeof aaa, fp) != NULL) {
122 while (isspace(name[0]))
123 strcpy(name, &name[1]);
124 aaa[strlen(aaa) - 1] = 0;
126 for (a = 0; a < strlen(aaa); ++a) {
128 strcpy(bbb, &aaa[a + 1]);
132 if (!strcasecmp(name, aaa))
136 lprintf(7, "Mail is being forwarded to %s\n", name);
138 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
139 for (a=0; a<strlen(name); ++a) {
140 if (name[a] == '@') {
141 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
143 lprintf(7, "Changed to <%s>\n", name);
148 /* determine local or remote type, see citadel.h */
149 for (a = 0; a < strlen(name); ++a)
151 return (MES_INTERNET);
152 for (a = 0; a < strlen(name); ++a)
154 for (b = a; b < strlen(name); ++b)
156 return (MES_INTERNET);
158 for (a = 0; a < strlen(name); ++a)
162 lprintf(7, "Too many @'s in address\n");
166 for (a = 0; a < strlen(name); ++a)
168 strcpy(bbb, &name[a + 1]);
170 strcpy(bbb, &bbb[1]);
171 fp = fopen("network/mail.sysinfo", "r");
175 a = getstring(fp, aaa);
176 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
177 a = getstring(fp, aaa);
178 if (!strncmp(aaa, "use ", 4)) {
179 strcpy(bbb, &aaa[4]);
184 if (!strncmp(aaa, "uum", 3)) {
186 for (a = 0; a < strlen(bbb); ++a) {
192 while (bbb[strlen(bbb) - 1] == '_')
193 bbb[strlen(bbb) - 1] = 0;
194 sprintf(name, &aaa[4], bbb);
195 lprintf(9, "returning MES_INTERNET\n");
196 return (MES_INTERNET);
198 if (!strncmp(aaa, "bin", 3)) {
201 while (aaa[strlen(aaa) - 1] != '@')
202 aaa[strlen(aaa) - 1] = 0;
203 aaa[strlen(aaa) - 1] = 0;
204 while (aaa[strlen(aaa) - 1] == ' ')
205 aaa[strlen(aaa) - 1] = 0;
206 while (bbb[0] != '@')
207 strcpy(bbb, &bbb[1]);
208 strcpy(bbb, &bbb[1]);
209 while (bbb[0] == ' ')
210 strcpy(bbb, &bbb[1]);
211 sprintf(name, "%s @%s", aaa, bbb);
212 lprintf(9, "returning MES_BINARY\n");
217 lprintf(9, "returning MES_LOCAL\n");
226 fp = fopen("citadel.control", "r");
227 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
233 void simple_listing(long msgnum, void *userdata)
235 cprintf("%ld\n", msgnum);
240 /* Determine if a given message matches the fields in a message template.
241 * Return 0 for a successful match.
243 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
246 /* If there aren't any fields in the template, all messages will
249 if (template == NULL) return(0);
251 /* Null messages are bogus. */
252 if (msg == NULL) return(1);
254 for (i='A'; i<='Z'; ++i) {
255 if (template->cm_fields[i] != NULL) {
256 if (msg->cm_fields[i] == NULL) {
259 if (strcasecmp(msg->cm_fields[i],
260 template->cm_fields[i])) return 1;
264 /* All compares succeeded: we have a match! */
272 * API function to perform an operation for each qualifying message in the
273 * current room. (Returns the number of messages processed.)
275 int CtdlForEachMessage(int mode, long ref,
276 int moderation_level,
278 struct CtdlMessage *compare,
279 void (*CallBack) (long, void *),
285 struct cdbdata *cdbfr;
286 long *msglist = NULL;
288 int num_processed = 0;
290 struct SuppMsgInfo smi;
291 struct CtdlMessage *msg;
293 /* Learn about the user and room in question */
295 getuser(&CC->usersupp, CC->curr_user);
296 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
298 /* Load the message list */
299 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
301 msglist = mallok(cdbfr->len);
302 memcpy(msglist, cdbfr->ptr, cdbfr->len);
303 num_msgs = cdbfr->len / sizeof(long);
306 return 0; /* No messages at all? No further action. */
310 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
311 GetSuppMsgInfo(&smi, msglist[a]);
313 /* Filter out messages that are moderated below the level
314 * currently being viewed at.
316 if (smi.smi_mod < moderation_level) {
320 /* If the caller is looking for a specific MIME type, filter
321 * out all messages which are not of the type requested.
323 if (content_type != NULL) if (strlen(content_type) > 0) {
324 if (strcasecmp(smi.smi_content_type, content_type)) {
330 num_msgs = sort_msglist(msglist, num_msgs);
332 /* If a template was supplied, filter out the messages which
333 * don't match. (This could induce some delays!)
336 if (compare != NULL) {
337 for (a = 0; a < num_msgs; ++a) {
338 msg = CtdlFetchMessage(msglist[a]);
340 if (CtdlMsgCmp(msg, compare)) {
343 CtdlFreeMessage(msg);
351 * Now iterate through the message list, according to the
352 * criteria supplied by the caller.
355 for (a = 0; a < num_msgs; ++a) {
356 thismsg = msglist[a];
361 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
362 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
363 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
364 && (CC->usersupp.flags & US_LASTOLD))
365 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
366 || ((mode == MSGS_FIRST) && (a < ref))
367 || ((mode == MSGS_GT) && (thismsg > ref))
368 || ((mode == MSGS_EQ) && (thismsg == ref))
371 if (CallBack) CallBack(thismsg, userdata);
375 phree(msglist); /* Clean up */
376 return num_processed;
382 * cmd_msgs() - get list of message #'s in this room
383 * implements the MSGS server command using CtdlForEachMessage()
385 void cmd_msgs(char *cmdbuf)
394 int with_template = 0;
395 struct CtdlMessage *template = NULL;
397 extract(which, cmdbuf, 0);
398 cm_ref = extract_int(cmdbuf, 1);
399 with_template = extract_int(cmdbuf, 2);
403 if (!strncasecmp(which, "OLD", 3))
405 else if (!strncasecmp(which, "NEW", 3))
407 else if (!strncasecmp(which, "FIRST", 5))
409 else if (!strncasecmp(which, "LAST", 4))
411 else if (!strncasecmp(which, "GT", 2))
414 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
415 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
420 cprintf("%d Send template then receive message list\n",
422 template = (struct CtdlMessage *)
423 mallok(sizeof(struct CtdlMessage));
424 memset(template, 0, sizeof(struct CtdlMessage));
425 while(client_gets(buf), strcmp(buf,"000")) {
426 extract(tfield, buf, 0);
427 extract(tvalue, buf, 1);
428 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
429 if (!strcasecmp(tfield, msgkeys[i])) {
430 template->cm_fields[i] =
437 cprintf("%d Message list...\n", LISTING_FOLLOWS);
440 CtdlForEachMessage(mode, cm_ref,
441 CC->usersupp.moderation_filter,
442 NULL, template, simple_listing, NULL);
443 if (template != NULL) CtdlFreeMessage(template);
451 * help_subst() - support routine for help file viewer
453 void help_subst(char *strbuf, char *source, char *dest)
458 while (p = pattern2(strbuf, source), (p >= 0)) {
459 strcpy(workbuf, &strbuf[p + strlen(source)]);
460 strcpy(&strbuf[p], dest);
461 strcat(strbuf, workbuf);
466 void do_help_subst(char *buffer)
470 help_subst(buffer, "^nodename", config.c_nodename);
471 help_subst(buffer, "^humannode", config.c_humannode);
472 help_subst(buffer, "^fqdn", config.c_fqdn);
473 help_subst(buffer, "^username", CC->usersupp.fullname);
474 sprintf(buf2, "%ld", CC->usersupp.usernum);
475 help_subst(buffer, "^usernum", buf2);
476 help_subst(buffer, "^sysadm", config.c_sysadm);
477 help_subst(buffer, "^variantname", CITADEL);
478 sprintf(buf2, "%d", config.c_maxsessions);
479 help_subst(buffer, "^maxsessions", buf2);
485 * memfmout() - Citadel text formatter and paginator.
486 * Although the original purpose of this routine was to format
487 * text to the reader's screen width, all we're really using it
488 * for here is to format text out to 80 columns before sending it
489 * to the client. The client software may reformat it again.
492 int width, /* screen width to use */
493 char *mptr, /* where are we going to get our text from? */
494 char subst, /* nonzero if we should do substitutions */
495 char *nl) /* string to terminate lines with */
507 c = 1; /* c is the current pos */
511 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
513 buffer[strlen(buffer) + 1] = 0;
514 buffer[strlen(buffer)] = ch;
517 if (buffer[0] == '^')
518 do_help_subst(buffer);
520 buffer[strlen(buffer) + 1] = 0;
522 strcpy(buffer, &buffer[1]);
530 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
532 if (((old == 13) || (old == 10)) && (isspace(real))) {
540 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
541 cprintf("%s%s", nl, aaa);
550 if ((strlen(aaa) + c) > (width - 5)) {
559 if ((ch == 13) || (ch == 10)) {
560 cprintf("%s%s", aaa, nl);
567 cprintf("%s%s", aaa, nl);
573 * Callback function for mime parser that simply lists the part
575 void list_this_part(char *name, char *filename, char *partnum, char *disp,
576 void *content, char *cbtype, size_t length, char *encoding,
580 cprintf("part=%s|%s|%s|%s|%s|%d\n",
581 name, filename, partnum, disp, cbtype, length);
586 * Callback function for mime parser that opens a section for downloading
588 void mime_download(char *name, char *filename, char *partnum, char *disp,
589 void *content, char *cbtype, size_t length, char *encoding,
593 /* Silently go away if there's already a download open... */
594 if (CC->download_fp != NULL)
597 /* ...or if this is not the desired section */
598 if (strcasecmp(desired_section, partnum))
601 CC->download_fp = tmpfile();
602 if (CC->download_fp == NULL)
605 fwrite(content, length, 1, CC->download_fp);
606 fflush(CC->download_fp);
607 rewind(CC->download_fp);
609 OpenCmdResult(filename, cbtype);
615 * Load a message from disk into memory.
616 * This is used by CtdlOutputMsg() and other fetch functions.
618 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
619 * using the CtdlMessageFree() function.
621 struct CtdlMessage *CtdlFetchMessage(long msgnum)
623 struct cdbdata *dmsgtext;
624 struct CtdlMessage *ret = NULL;
627 CIT_UBYTE field_header;
630 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
631 if (dmsgtext == NULL) {
634 mptr = dmsgtext->ptr;
636 /* Parse the three bytes that begin EVERY message on disk.
637 * The first is always 0xFF, the on-disk magic number.
638 * The second is the anonymous/public type byte.
639 * The third is the format type byte (vari, fixed, or MIME).
643 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
647 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
648 memset(ret, 0, sizeof(struct CtdlMessage));
650 ret->cm_magic = CTDLMESSAGE_MAGIC;
651 ret->cm_anon_type = *mptr++; /* Anon type byte */
652 ret->cm_format_type = *mptr++; /* Format type byte */
655 * The rest is zero or more arbitrary fields. Load them in.
656 * We're done when we encounter either a zero-length field or
657 * have just processed the 'M' (message text) field.
660 field_length = strlen(mptr);
661 if (field_length == 0)
663 field_header = *mptr++;
664 ret->cm_fields[field_header] = mallok(field_length);
665 strcpy(ret->cm_fields[field_header], mptr);
667 while (*mptr++ != 0); /* advance to next field */
669 } while ((field_length > 0) && (field_header != 'M'));
673 /* Always make sure there's something in the msg text field */
674 if (ret->cm_fields['M'] == NULL)
675 ret->cm_fields['M'] = strdoop("<no text>\n");
677 /* Perform "before read" hooks (aborting if any return nonzero) */
678 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
679 CtdlFreeMessage(ret);
688 * Returns 1 if the supplied pointer points to a valid Citadel message.
689 * If the pointer is NULL or the magic number check fails, returns 0.
691 int is_valid_message(struct CtdlMessage *msg) {
694 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
695 lprintf(3, "is_valid_message() -- self-check failed\n");
703 * 'Destructor' for struct CtdlMessage
705 void CtdlFreeMessage(struct CtdlMessage *msg)
709 if (is_valid_message(msg) == 0) return;
711 for (i = 0; i < 256; ++i)
712 if (msg->cm_fields[i] != NULL) {
713 phree(msg->cm_fields[i]);
716 msg->cm_magic = 0; /* just in case */
722 * Callback function for mime parser that wants to display text
724 void fixed_output(char *name, char *filename, char *partnum, char *disp,
725 void *content, char *cbtype, size_t length, char *encoding,
733 if (!strcasecmp(cbtype, "multipart/alternative")) {
734 strcpy(ma->prefix, partnum);
735 strcat(ma->prefix, ".");
741 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
743 && (ma->did_print == 1) ) {
744 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
750 if ( (!strcasecmp(cbtype, "text/plain"))
751 || (strlen(cbtype)==0) ) {
756 if (ch==10) cprintf("\r\n");
757 else cprintf("%c", ch);
760 else if (!strcasecmp(cbtype, "text/html")) {
761 ptr = html_to_ascii(content, 80, 0);
766 if (ch==10) cprintf("\r\n");
767 else cprintf("%c", ch);
771 else if (strncasecmp(cbtype, "multipart/", 10)) {
772 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
773 partnum, filename, cbtype, length);
779 * Get a message off disk. (returns om_* values found in msgbase.h)
782 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
783 int mode, /* how would you like that message? */
784 int headers_only, /* eschew the message body? */
785 int do_proto, /* do Citadel protocol responses? */
786 int crlf /* Use CRLF newlines instead of LF? */
788 struct CtdlMessage *TheMessage;
791 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
796 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
797 if (do_proto) cprintf("%d Not logged in.\n",
798 ERROR + NOT_LOGGED_IN);
799 return(om_not_logged_in);
802 /* FIXME ... small security issue
803 * We need to check to make sure the requested message is actually
804 * in the current room, and set msg_ok to 1 only if it is. This
805 * functionality is currently missing because I'm in a hurry to replace
806 * broken production code with nonbroken pre-beta code. :( -- ajc
809 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
811 return(om_no_such_msg);
816 * Fetch the message from disk
818 TheMessage = CtdlFetchMessage(msg_num);
819 if (TheMessage == NULL) {
820 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
822 return(om_no_such_msg);
825 retcode = CtdlOutputPreLoadedMsg(
826 TheMessage, msg_num, mode,
827 headers_only, do_proto, crlf);
829 CtdlFreeMessage(TheMessage);
835 * Get a message off disk. (returns om_* values found in msgbase.h)
838 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
840 int mode, /* how would you like that message? */
841 int headers_only, /* eschew the message body? */
842 int do_proto, /* do Citadel protocol responses? */
843 int crlf /* Use CRLF newlines instead of LF? */
849 char display_name[SIZ];
851 char *nl; /* newline string */
853 /* buffers needed for RFC822 translation */
863 sprintf(mid, "%ld", msg_num);
864 nl = (crlf ? "\r\n" : "\n");
866 if (!is_valid_message(TheMessage)) {
867 lprintf(1, "ERROR: invalid preloaded message for output\n");
868 return(om_no_such_msg);
871 /* Are we downloading a MIME component? */
872 if (mode == MT_DOWNLOAD) {
873 if (TheMessage->cm_format_type != FMT_RFC822) {
875 cprintf("%d This is not a MIME message.\n",
877 } else if (CC->download_fp != NULL) {
878 if (do_proto) cprintf(
879 "%d You already have a download open.\n",
882 /* Parse the message text component */
883 mptr = TheMessage->cm_fields['M'];
884 mime_parser(mptr, NULL, *mime_download, NULL, 0);
885 /* If there's no file open by this time, the requested
886 * section wasn't found, so print an error
888 if (CC->download_fp == NULL) {
889 if (do_proto) cprintf(
890 "%d Section %s not found.\n",
891 ERROR + FILE_NOT_FOUND,
895 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
898 /* now for the user-mode message reading loops */
899 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
901 /* Tell the client which format type we're using. If this is a
902 * MIME message, *lie* about it and tell the user it's fixed-format.
904 if (mode == MT_CITADEL) {
905 if (TheMessage->cm_format_type == FMT_RFC822) {
906 if (do_proto) cprintf("type=1\n");
909 if (do_proto) cprintf("type=%d\n",
910 TheMessage->cm_format_type);
914 /* nhdr=yes means that we're only displaying headers, no body */
915 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
916 if (do_proto) cprintf("nhdr=yes\n");
919 /* begin header processing loop for Citadel message format */
921 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
923 strcpy(display_name, "<unknown>");
924 if (TheMessage->cm_fields['A']) {
925 strcpy(buf, TheMessage->cm_fields['A']);
926 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
927 if (TheMessage->cm_anon_type == MES_ANON)
928 strcpy(display_name, "****");
929 else if (TheMessage->cm_anon_type == MES_AN2)
930 strcpy(display_name, "anonymous");
932 strcpy(display_name, buf);
934 && ((TheMessage->cm_anon_type == MES_ANON)
935 || (TheMessage->cm_anon_type == MES_AN2))) {
936 sprintf(&display_name[strlen(display_name)],
941 strcpy(allkeys, FORDER);
942 for (i=0; i<strlen(allkeys); ++i) {
943 k = (int) allkeys[i];
945 if (TheMessage->cm_fields[k] != NULL) {
947 if (do_proto) cprintf("%s=%s\n",
952 if (do_proto) cprintf("%s=%s\n",
954 TheMessage->cm_fields[k]
963 /* begin header processing loop for RFC822 transfer format */
968 strcpy(snode, NODENAME);
969 strcpy(lnode, HUMANNODE);
970 if (mode == MT_RFC822) {
971 cprintf("X-UIDL: %ld%s", msg_num, nl);
972 for (i = 0; i < 256; ++i) {
973 if (TheMessage->cm_fields[i]) {
974 mptr = TheMessage->cm_fields[i];
981 cprintf("Path: %s%s", mptr, nl);
984 cprintf("Subject: %s%s", mptr, nl);
990 cprintf("X-Citadel-Room: %s%s",
995 cprintf("To: %s%s", mptr, nl);
997 datestring(datestamp, atol(mptr),
999 cprintf("Date: %s%s", datestamp, nl);
1005 for (i=0; i<strlen(suser); ++i) {
1006 suser[i] = tolower(suser[i]);
1007 if (!isalnum(suser[i])) suser[i]='_';
1010 if (mode == MT_RFC822) {
1011 if (!strcasecmp(snode, NODENAME)) {
1012 strcpy(snode, FQDN);
1015 /* Construct a fun message id */
1016 cprintf("Message-ID: <%s", mid);
1017 if (strchr(mid, '@')==NULL) {
1018 cprintf("@%s", snode);
1022 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1024 if (strlen(fuser) > 0) {
1025 cprintf("From: %s (%s)%s", fuser, luser, nl);
1028 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1031 cprintf("Organization: %s%s", lnode, nl);
1034 /* end header processing loop ... at this point, we're in the text */
1036 mptr = TheMessage->cm_fields['M'];
1038 /* Tell the client about the MIME parts in this message */
1039 if (TheMessage->cm_format_type == FMT_RFC822) {
1040 if (mode == MT_CITADEL) {
1041 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1043 else if (mode == MT_MIME) { /* list parts only */
1044 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1045 if (do_proto) cprintf("000\n");
1048 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1049 /* FIXME ... we have to put some code in here to avoid
1050 * printing duplicate header information when both
1051 * Citadel and RFC822 headers exist. Preference should
1052 * probably be given to the RFC822 headers.
1054 while (ch=*(mptr++), ch!=0) {
1056 else if (ch==10) cprintf("%s", nl);
1057 else cprintf("%c", ch);
1059 if (do_proto) cprintf("000\n");
1065 if (do_proto) cprintf("000\n");
1069 /* signify start of msg text */
1070 if (mode == MT_CITADEL)
1071 if (do_proto) cprintf("text\n");
1072 if (mode == MT_RFC822) {
1073 if (TheMessage->cm_fields['U'] == NULL) {
1074 cprintf("Subject: (no subject)%s", nl);
1079 /* If the format type on disk is 1 (fixed-format), then we want
1080 * everything to be output completely literally ... regardless of
1081 * what message transfer format is in use.
1083 if (TheMessage->cm_format_type == FMT_FIXED) {
1085 while (ch = *mptr++, ch > 0) {
1088 if ((ch == 10) || (strlen(buf) > 250)) {
1089 cprintf("%s%s", buf, nl);
1092 buf[strlen(buf) + 1] = 0;
1093 buf[strlen(buf)] = ch;
1096 if (strlen(buf) > 0)
1097 cprintf("%s%s", buf, nl);
1100 /* If the message on disk is format 0 (Citadel vari-format), we
1101 * output using the formatter at 80 columns. This is the final output
1102 * form if the transfer format is RFC822, but if the transfer format
1103 * is Citadel proprietary, it'll still work, because the indentation
1104 * for new paragraphs is correct and the client will reformat the
1105 * message to the reader's screen width.
1107 if (TheMessage->cm_format_type == FMT_CITADEL) {
1108 memfmout(80, mptr, 0, nl);
1111 /* If the message on disk is format 4 (MIME), we've gotta hand it
1112 * off to the MIME parser. The client has already been told that
1113 * this message is format 1 (fixed format), so the callback function
1114 * we use will display those parts as-is.
1116 if (TheMessage->cm_format_type == FMT_RFC822) {
1117 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1118 memset(ma, 0, sizeof(struct ma_info));
1119 mime_parser(mptr, NULL, *fixed_output, NULL, 0);
1122 /* now we're done */
1123 if (do_proto) cprintf("000\n");
1130 * display a message (mode 0 - Citadel proprietary)
1132 void cmd_msg0(char *cmdbuf)
1135 int headers_only = 0;
1137 msgid = extract_long(cmdbuf, 0);
1138 headers_only = extract_int(cmdbuf, 1);
1140 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1146 * display a message (mode 2 - RFC822)
1148 void cmd_msg2(char *cmdbuf)
1151 int headers_only = 0;
1153 msgid = extract_long(cmdbuf, 0);
1154 headers_only = extract_int(cmdbuf, 1);
1156 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1162 * display a message (mode 3 - IGnet raw format - internal programs only)
1164 void cmd_msg3(char *cmdbuf)
1167 struct CtdlMessage *msg;
1170 if (CC->internal_pgm == 0) {
1171 cprintf("%d This command is for internal programs only.\n",
1176 msgnum = extract_long(cmdbuf, 0);
1177 msg = CtdlFetchMessage(msgnum);
1179 cprintf("%d Message %ld not found.\n",
1184 serialize_message(&smr, msg);
1185 CtdlFreeMessage(msg);
1188 cprintf("%d Unable to serialize message\n",
1189 ERROR+INTERNAL_ERROR);
1193 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1194 client_write(smr.ser, smr.len);
1201 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1203 void cmd_msg4(char *cmdbuf)
1207 msgid = extract_long(cmdbuf, 0);
1208 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1212 * Open a component of a MIME message as a download file
1214 void cmd_opna(char *cmdbuf)
1218 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1220 msgid = extract_long(cmdbuf, 0);
1221 extract(desired_section, cmdbuf, 1);
1223 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1228 * Save a message pointer into a specified room
1229 * (Returns 0 for success, nonzero for failure)
1230 * roomname may be NULL to use the current room
1232 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1234 char hold_rm[ROOMNAMELEN];
1235 struct cdbdata *cdbfr;
1238 long highest_msg = 0L;
1239 struct CtdlMessage *msg = NULL;
1241 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1242 roomname, msgid, flags);
1244 strcpy(hold_rm, CC->quickroom.QRname);
1246 /* We may need to check to see if this message is real */
1247 if ( (flags & SM_VERIFY_GOODNESS)
1248 || (flags & SM_DO_REPL_CHECK)
1250 msg = CtdlFetchMessage(msgid);
1251 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1254 /* Perform replication checks if necessary */
1255 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1257 if (getroom(&CC->quickroom,
1258 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1260 lprintf(9, "No such room <%s>\n", roomname);
1261 if (msg != NULL) CtdlFreeMessage(msg);
1262 return(ERROR + ROOM_NOT_FOUND);
1265 if (ReplicationChecks(msg) != 0) {
1266 getroom(&CC->quickroom, hold_rm);
1267 if (msg != NULL) CtdlFreeMessage(msg);
1268 lprintf(9, "Did replication, and newer exists\n");
1273 /* Now the regular stuff */
1274 if (lgetroom(&CC->quickroom,
1275 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1277 lprintf(9, "No such room <%s>\n", roomname);
1278 if (msg != NULL) CtdlFreeMessage(msg);
1279 return(ERROR + ROOM_NOT_FOUND);
1282 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1283 if (cdbfr == NULL) {
1287 msglist = mallok(cdbfr->len);
1288 if (msglist == NULL)
1289 lprintf(3, "ERROR malloc msglist!\n");
1290 num_msgs = cdbfr->len / sizeof(long);
1291 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1296 /* Make sure the message doesn't already exist in this room. It
1297 * is absolutely taboo to have more than one reference to the same
1298 * message in a room.
1300 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1301 if (msglist[i] == msgid) {
1302 lputroom(&CC->quickroom); /* unlock the room */
1303 getroom(&CC->quickroom, hold_rm);
1304 if (msg != NULL) CtdlFreeMessage(msg);
1305 return(ERROR + ALREADY_EXISTS);
1309 /* Now add the new message */
1311 msglist = reallok(msglist,
1312 (num_msgs * sizeof(long)));
1314 if (msglist == NULL) {
1315 lprintf(3, "ERROR: can't realloc message list!\n");
1317 msglist[num_msgs - 1] = msgid;
1319 /* Sort the message list, so all the msgid's are in order */
1320 num_msgs = sort_msglist(msglist, num_msgs);
1322 /* Determine the highest message number */
1323 highest_msg = msglist[num_msgs - 1];
1325 /* Write it back to disk. */
1326 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1327 msglist, num_msgs * sizeof(long));
1329 /* Free up the memory we used. */
1332 /* Update the highest-message pointer and unlock the room. */
1333 CC->quickroom.QRhighest = highest_msg;
1334 lputroom(&CC->quickroom);
1335 getroom(&CC->quickroom, hold_rm);
1337 /* Bump the reference count for this message. */
1338 if ((flags & SM_DONT_BUMP_REF)==0) {
1339 AdjRefCount(msgid, +1);
1342 /* Return success. */
1343 if (msg != NULL) CtdlFreeMessage(msg);
1350 * Message base operation to send a message to the master file
1351 * (returns new message number)
1353 * This is the back end for CtdlSaveMsg() and should not be directly
1354 * called by server-side modules.
1357 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1358 FILE *save_a_copy) /* save a copy to disk? */
1365 /* Get a new message number */
1366 newmsgid = get_new_message_number();
1367 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1369 /* Generate an ID if we don't have one already */
1370 if (msg->cm_fields['I']==NULL) {
1371 msg->cm_fields['I'] = strdoop(msgidbuf);
1374 serialize_message(&smr, msg);
1377 cprintf("%d Unable to serialize message\n",
1378 ERROR+INTERNAL_ERROR);
1382 /* Write our little bundle of joy into the message base */
1383 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1384 smr.ser, smr.len) < 0) {
1385 lprintf(2, "Can't store message\n");
1391 /* If the caller specified that a copy should be saved to a particular
1392 * file handle, do that now too.
1394 if (save_a_copy != NULL) {
1395 fwrite(smr.ser, smr.len, 1, save_a_copy);
1398 /* Free the memory we used for the serialized message */
1401 /* Return the *local* message ID to the caller
1402 * (even if we're storing an incoming network message)
1410 * Serialize a struct CtdlMessage into the format used on disk and network.
1412 * This function loads up a "struct ser_ret" (defined in server.h) which
1413 * contains the length of the serialized message and a pointer to the
1414 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1416 void serialize_message(struct ser_ret *ret, /* return values */
1417 struct CtdlMessage *msg) /* unserialized msg */
1421 static char *forder = FORDER;
1423 if (is_valid_message(msg) == 0) return; /* self check */
1426 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1427 ret->len = ret->len +
1428 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1430 lprintf(9, "calling malloc(%d)\n", ret->len);
1431 ret->ser = mallok(ret->len);
1432 if (ret->ser == NULL) {
1438 ret->ser[1] = msg->cm_anon_type;
1439 ret->ser[2] = msg->cm_format_type;
1442 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1443 ret->ser[wlen++] = (char)forder[i];
1444 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1445 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1447 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1456 * Back end for the ReplicationChecks() function
1458 void check_repl(long msgnum, void *userdata) {
1459 struct CtdlMessage *msg;
1460 time_t timestamp = (-1L);
1462 lprintf(9, "check_repl() found message %ld\n", msgnum);
1463 msg = CtdlFetchMessage(msgnum);
1464 if (msg == NULL) return;
1465 if (msg->cm_fields['T'] != NULL) {
1466 timestamp = atol(msg->cm_fields['T']);
1468 CtdlFreeMessage(msg);
1470 if (timestamp > msg_repl->highest) {
1471 msg_repl->highest = timestamp; /* newer! */
1472 lprintf(9, "newer!\n");
1475 lprintf(9, "older!\n");
1477 /* Existing isn't newer? Then delete the old one(s). */
1478 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1483 * Check to see if any messages already exist which carry the same Extended ID
1487 * -> With older timestamps: delete them and return 0. Message will be saved.
1488 * -> With newer timestamps: return 1. Message save will be aborted.
1490 int ReplicationChecks(struct CtdlMessage *msg) {
1491 struct CtdlMessage *template;
1494 lprintf(9, "ReplicationChecks() started\n");
1495 /* No extended id? Don't do anything. */
1496 if (msg->cm_fields['E'] == NULL) return 0;
1497 if (strlen(msg->cm_fields['E']) == 0) return 0;
1498 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1500 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1501 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1502 msg_repl->highest = atol(msg->cm_fields['T']);
1504 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1505 memset(template, 0, sizeof(struct CtdlMessage));
1506 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1508 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1511 /* If a newer message exists with the same Extended ID, abort
1514 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1518 CtdlFreeMessage(template);
1519 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1527 * Save a message to disk
1529 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1530 char *rec, /* Recipient (mail) */
1531 char *force, /* force a particular room? */
1532 int supplied_mailtype) /* local or remote type */
1535 char hold_rm[ROOMNAMELEN];
1536 char actual_rm[ROOMNAMELEN];
1537 char force_room[ROOMNAMELEN];
1538 char content_type[SIZ]; /* We have to learn this */
1539 char recipient[SIZ];
1542 struct usersupp userbuf;
1544 struct SuppMsgInfo smi;
1545 FILE *network_fp = NULL;
1546 static int seqnum = 1;
1547 struct CtdlMessage *imsg;
1551 lprintf(9, "CtdlSaveMsg() called\n");
1552 if (is_valid_message(msg) == 0) return(-1); /* self check */
1553 mailtype = supplied_mailtype;
1555 /* If this message has no timestamp, we take the liberty of
1556 * giving it one, right now.
1558 if (msg->cm_fields['T'] == NULL) {
1559 lprintf(9, "Generating timestamp\n");
1560 sprintf(aaa, "%ld", time(NULL));
1561 msg->cm_fields['T'] = strdoop(aaa);
1564 /* If this message has no path, we generate one.
1566 if (msg->cm_fields['P'] == NULL) {
1567 lprintf(9, "Generating path\n");
1568 if (msg->cm_fields['A'] != NULL) {
1569 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1570 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1571 if (isspace(msg->cm_fields['P'][a])) {
1572 msg->cm_fields['P'][a] = ' ';
1577 msg->cm_fields['P'] = strdoop("unknown");
1581 strcpy(force_room, force);
1583 /* Strip non-printable characters out of the recipient name */
1584 lprintf(9, "Checking recipient (if present)\n");
1585 strcpy(recipient, rec);
1586 for (a = 0; a < strlen(recipient); ++a)
1587 if (!isprint(recipient[a]))
1588 strcpy(&recipient[a], &recipient[a + 1]);
1590 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1591 for (a=0; a<strlen(recipient); ++a) {
1592 if (recipient[a] == '@') {
1593 if (CtdlHostAlias(&recipient[a+1])
1594 == hostalias_localhost) {
1596 lprintf(7, "Changed to <%s>\n", recipient);
1597 mailtype = MES_LOCAL;
1602 lprintf(9, "Recipient is <%s>\n", recipient);
1604 /* Learn about what's inside, because it's what's inside that counts */
1605 lprintf(9, "Learning what's inside\n");
1606 if (msg->cm_fields['M'] == NULL) {
1607 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1610 switch (msg->cm_format_type) {
1612 strcpy(content_type, "text/x-citadel-variformat");
1615 strcpy(content_type, "text/plain");
1618 strcpy(content_type, "text/plain");
1619 /* advance past header fields */
1620 mptr = msg->cm_fields['M'];
1623 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1624 safestrncpy(content_type, mptr,
1625 sizeof(content_type));
1626 strcpy(content_type, &content_type[14]);
1627 for (a = 0; a < strlen(content_type); ++a)
1628 if ((content_type[a] == ';')
1629 || (content_type[a] == ' ')
1630 || (content_type[a] == 13)
1631 || (content_type[a] == 10))
1632 content_type[a] = 0;
1639 /* Goto the correct room */
1640 lprintf(9, "Switching rooms\n");
1641 strcpy(hold_rm, CC->quickroom.QRname);
1642 strcpy(actual_rm, CC->quickroom.QRname);
1644 /* If the user is a twit, move to the twit room for posting */
1645 lprintf(9, "Handling twit stuff\n");
1647 if (CC->usersupp.axlevel == 2) {
1648 strcpy(hold_rm, actual_rm);
1649 strcpy(actual_rm, config.c_twitroom);
1653 /* ...or if this message is destined for Aide> then go there. */
1654 if (strlen(force_room) > 0) {
1655 strcpy(actual_rm, force_room);
1658 lprintf(9, "Possibly relocating\n");
1659 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1660 getroom(&CC->quickroom, actual_rm);
1664 * If this message has no O (room) field, generate one.
1666 if (msg->cm_fields['O'] == NULL) {
1667 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1670 /* Perform "before save" hooks (aborting if any return nonzero) */
1671 lprintf(9, "Performing before-save hooks\n");
1672 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1674 /* If this message has an Extended ID, perform replication checks */
1675 lprintf(9, "Performing replication checks\n");
1676 if (ReplicationChecks(msg) > 0) return(-1);
1678 /* Network mail - send a copy to the network program. */
1679 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1680 lprintf(9, "Sending network spool\n");
1681 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1682 (long) getpid(), CC->cs_pid, ++seqnum);
1683 lprintf(9, "Saving a copy to %s\n", aaa);
1684 network_fp = fopen(aaa, "ab+");
1685 if (network_fp == NULL)
1686 lprintf(2, "ERROR: %s\n", strerror(errno));
1689 /* Save it to disk */
1690 lprintf(9, "Saving to disk\n");
1691 newmsgid = send_message(msg, network_fp);
1692 if (network_fp != NULL) {
1694 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1697 if (newmsgid <= 0L) return(-1);
1699 /* Write a supplemental message info record. This doesn't have to
1700 * be a critical section because nobody else knows about this message
1703 lprintf(9, "Creating SuppMsgInfo record\n");
1704 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1705 smi.smi_msgnum = newmsgid;
1706 smi.smi_refcount = 0;
1707 safestrncpy(smi.smi_content_type, content_type, 64);
1708 PutSuppMsgInfo(&smi);
1710 /* Now figure out where to store the pointers */
1711 lprintf(9, "Storing pointers\n");
1713 /* If this is being done by the networker delivering a private
1714 * message, we want to BYPASS saving the sender's copy (because there
1715 * is no local sender; it would otherwise go to the Trashcan).
1717 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1718 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1719 lprintf(3, "ERROR saving message pointer!\n");
1720 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1724 /* For internet mail, drop a copy in the outbound queue room */
1725 if (mailtype == MES_INTERNET) {
1726 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1729 /* Bump this user's messages posted counter. */
1730 lprintf(9, "Updating user\n");
1731 lgetuser(&CC->usersupp, CC->curr_user);
1732 CC->usersupp.posted = CC->usersupp.posted + 1;
1733 lputuser(&CC->usersupp);
1735 /* If this is private, local mail, make a copy in the
1736 * recipient's mailbox and bump the reference count.
1738 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1739 if (getuser(&userbuf, recipient) == 0) {
1740 lprintf(9, "Delivering private mail\n");
1741 MailboxName(actual_rm, &userbuf, MAILROOM);
1742 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1745 lprintf(9, "No user <%s>, saving in %s> instead\n",
1746 recipient, AIDEROOM);
1747 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1751 /* Perform "after save" hooks */
1752 lprintf(9, "Performing after-save hooks\n");
1753 PerformMessageHooks(msg, EVT_AFTERSAVE);
1756 lprintf(9, "Returning to original room\n");
1757 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1758 getroom(&CC->quickroom, hold_rm);
1760 /* For internet mail, generate delivery instructions
1761 * (Yes, this is recursive! Deal with it!)
1763 if (mailtype == MES_INTERNET) {
1764 lprintf(9, "Generating delivery instructions\n");
1765 instr = mallok(2048);
1767 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1770 SPOOLMIME, newmsgid, time(NULL),
1771 msg->cm_fields['A'], msg->cm_fields['N'],
1774 imsg = mallok(sizeof(struct CtdlMessage));
1775 memset(imsg, 0, sizeof(struct CtdlMessage));
1776 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1777 imsg->cm_anon_type = MES_NORMAL;
1778 imsg->cm_format_type = FMT_RFC822;
1779 imsg->cm_fields['A'] = strdoop("Citadel");
1780 imsg->cm_fields['M'] = instr;
1781 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1782 CtdlFreeMessage(imsg);
1791 * Convenience function for generating small administrative messages.
1793 void quickie_message(char *from, char *to, char *room, char *text)
1795 struct CtdlMessage *msg;
1797 msg = mallok(sizeof(struct CtdlMessage));
1798 memset(msg, 0, sizeof(struct CtdlMessage));
1799 msg->cm_magic = CTDLMESSAGE_MAGIC;
1800 msg->cm_anon_type = MES_NORMAL;
1801 msg->cm_format_type = 0;
1802 msg->cm_fields['A'] = strdoop(from);
1803 msg->cm_fields['O'] = strdoop(room);
1804 msg->cm_fields['N'] = strdoop(NODENAME);
1806 msg->cm_fields['R'] = strdoop(to);
1807 msg->cm_fields['M'] = strdoop(text);
1809 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1810 CtdlFreeMessage(msg);
1811 syslog(LOG_NOTICE, text);
1817 * Back end function used by make_message() and similar functions
1819 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1820 size_t maxlen, /* maximum message length */
1821 char *exist /* if non-null, append to it;
1822 exist is ALWAYS freed */
1826 size_t message_len = 0;
1827 size_t buffer_len = 0;
1831 if (exist == NULL) {
1835 m = reallok(exist, strlen(exist) + 4096);
1836 if (m == NULL) phree(exist);
1839 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1846 /* read in the lines of message text one by one */
1847 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1849 /* strip trailing newline type stuff */
1850 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1851 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1853 linelen = strlen(buf);
1855 /* augment the buffer if we have to */
1856 if ((message_len + linelen + 2) > buffer_len) {
1857 lprintf(9, "realloking\n");
1858 ptr = reallok(m, (buffer_len * 2) );
1859 if (ptr == NULL) { /* flush if can't allocate */
1860 while ( (client_gets(buf)>0) &&
1861 strcmp(buf, terminator)) ;;
1864 buffer_len = (buffer_len * 2);
1866 lprintf(9, "buffer_len is %d\n", buffer_len);
1870 /* Add the new line to the buffer. We avoid using strcat()
1871 * because that would involve traversing the entire message
1872 * after each line, and this function needs to run fast.
1874 strcpy(&m[message_len], buf);
1875 m[message_len + linelen] = '\n';
1876 m[message_len + linelen + 1] = 0;
1877 message_len = message_len + linelen + 1;
1879 /* if we've hit the max msg length, flush the rest */
1880 if (message_len >= maxlen) {
1881 while ( (client_gets(buf)>0)
1882 && strcmp(buf, terminator)) ;;
1893 * Build a binary message to be saved on disk.
1896 struct CtdlMessage *make_message(
1897 struct usersupp *author, /* author's usersupp structure */
1898 char *recipient, /* NULL if it's not mail */
1899 char *room, /* room where it's going */
1900 int type, /* see MES_ types in header file */
1901 int net_type, /* see MES_ types in header file */
1902 int format_type, /* local or remote (see citadel.h) */
1903 char *fake_name) /* who we're masquerading as */
1909 struct CtdlMessage *msg;
1911 msg = mallok(sizeof(struct CtdlMessage));
1912 memset(msg, 0, sizeof(struct CtdlMessage));
1913 msg->cm_magic = CTDLMESSAGE_MAGIC;
1914 msg->cm_anon_type = type;
1915 msg->cm_format_type = format_type;
1917 /* Don't confuse the poor folks if it's not routed mail. */
1918 strcpy(dest_node, "");
1920 /* If net_type is MES_BINARY, split out the destination node. */
1921 if (net_type == MES_BINARY) {
1922 strcpy(dest_node, NODENAME);
1923 for (a = 0; a < strlen(recipient); ++a) {
1924 if (recipient[a] == '@') {
1926 strcpy(dest_node, &recipient[a + 1]);
1931 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1932 if (net_type == MES_INTERNET) {
1933 strcpy(dest_node, "internet");
1936 while (isspace(recipient[strlen(recipient) - 1]))
1937 recipient[strlen(recipient) - 1] = 0;
1939 sprintf(buf, "cit%ld", author->usernum); /* Path */
1940 msg->cm_fields['P'] = strdoop(buf);
1942 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1943 msg->cm_fields['T'] = strdoop(buf);
1945 if (fake_name[0]) /* author */
1946 msg->cm_fields['A'] = strdoop(fake_name);
1948 msg->cm_fields['A'] = strdoop(author->fullname);
1950 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1951 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1953 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1955 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1956 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1958 if (recipient[0] != 0)
1959 msg->cm_fields['R'] = strdoop(recipient);
1960 if (dest_node[0] != 0)
1961 msg->cm_fields['D'] = strdoop(dest_node);
1964 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1965 config.c_maxmsglen, NULL);
1976 * message entry - mode 0 (normal)
1978 void cmd_ent0(char *entargs)
1981 char recipient[SIZ];
1983 int format_type = 0;
1984 char newusername[SIZ];
1985 struct CtdlMessage *msg;
1989 struct usersupp tempUS;
1992 post = extract_int(entargs, 0);
1993 extract(recipient, entargs, 1);
1994 anon_flag = extract_int(entargs, 2);
1995 format_type = extract_int(entargs, 3);
1997 /* first check to make sure the request is valid. */
1999 if (!(CC->logged_in)) {
2000 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
2003 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2004 cprintf("%d Need to be validated to enter ",
2005 ERROR + HIGHER_ACCESS_REQUIRED);
2006 cprintf("(except in %s> to sysop)\n", MAILROOM);
2009 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
2010 cprintf("%d Need net privileges to enter here.\n",
2011 ERROR + HIGHER_ACCESS_REQUIRED);
2014 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
2015 cprintf("%d Sorry, this is a read-only room.\n",
2016 ERROR + HIGHER_ACCESS_REQUIRED);
2023 if (CC->usersupp.axlevel < 6) {
2024 cprintf("%d You don't have permission to masquerade.\n",
2025 ERROR + HIGHER_ACCESS_REQUIRED);
2028 extract(newusername, entargs, 4);
2029 memset(CC->fake_postname, 0, 32);
2030 strcpy(CC->fake_postname, newusername);
2031 cprintf("%d Ok\n", OK);
2034 CC->cs_flags |= CS_POSTING;
2037 if (CC->quickroom.QRflags & QR_MAILBOX) {
2038 if (CC->usersupp.axlevel >= 2) {
2039 strcpy(buf, recipient);
2041 strcpy(buf, "sysop");
2042 e = alias(buf); /* alias and mail type */
2043 if ((buf[0] == 0) || (e == MES_ERROR)) {
2044 cprintf("%d Unknown address - cannot send message.\n",
2045 ERROR + NO_SUCH_USER);
2048 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2049 cprintf("%d Net privileges required for network mail.\n",
2050 ERROR + HIGHER_ACCESS_REQUIRED);
2053 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2054 && ((CC->usersupp.flags & US_INTERNET) == 0)
2055 && (!CC->internal_pgm)) {
2056 cprintf("%d You don't have access to Internet mail.\n",
2057 ERROR + HIGHER_ACCESS_REQUIRED);
2060 if (!strcasecmp(buf, "sysop")) {
2063 else if (e == MES_LOCAL) { /* don't search local file */
2064 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2065 cprintf("%d Can't send mail to yourself!\n",
2066 ERROR + NO_SUCH_USER);
2069 /* Check to make sure the user exists; also get the correct
2070 * upper/lower casing of the name.
2072 a = getuser(&tempUS, buf);
2074 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2077 strcpy(buf, tempUS.fullname);
2082 if (CC->quickroom.QRflags & QR_ANONONLY)
2084 if (CC->quickroom.QRflags & QR_ANONOPT) {
2088 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2091 /* If we're only checking the validity of the request, return
2092 * success without creating the message.
2095 cprintf("%d %s\n", OK, buf);
2099 cprintf("%d send message\n", SEND_LISTING);
2101 /* Read in the message from the client. */
2102 if (CC->fake_postname[0])
2103 msg = make_message(&CC->usersupp, buf,
2104 CC->quickroom.QRname, b, e, format_type,
2106 else if (CC->fake_username[0])
2107 msg = make_message(&CC->usersupp, buf,
2108 CC->quickroom.QRname, b, e, format_type,
2111 msg = make_message(&CC->usersupp, buf,
2112 CC->quickroom.QRname, b, e, format_type, "");
2115 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2116 CtdlFreeMessage(msg);
2117 CC->fake_postname[0] = '\0';
2124 * message entry - mode 3 (raw)
2126 void cmd_ent3(char *entargs)
2132 unsigned char ch, which_field;
2133 struct usersupp tempUS;
2135 struct CtdlMessage *msg;
2138 if (CC->internal_pgm == 0) {
2139 cprintf("%d This command is for internal programs only.\n",
2144 /* See if there's a recipient, but make sure it's a real one */
2145 extract(recp, entargs, 1);
2146 for (a = 0; a < strlen(recp); ++a)
2147 if (!isprint(recp[a]))
2148 strcpy(&recp[a], &recp[a + 1]);
2149 while (isspace(recp[0]))
2150 strcpy(recp, &recp[1]);
2151 while (isspace(recp[strlen(recp) - 1]))
2152 recp[strlen(recp) - 1] = 0;
2154 /* If we're in Mail, check the recipient */
2155 if (strlen(recp) > 0) {
2156 e = alias(recp); /* alias and mail type */
2157 if ((recp[0] == 0) || (e == MES_ERROR)) {
2158 cprintf("%d Unknown address - cannot send message.\n",
2159 ERROR + NO_SUCH_USER);
2162 if (e == MES_LOCAL) {
2163 a = getuser(&tempUS, recp);
2165 cprintf("%d No such user.\n",
2166 ERROR + NO_SUCH_USER);
2172 /* At this point, message has been approved. */
2173 if (extract_int(entargs, 0) == 0) {
2174 cprintf("%d OK to send\n", OK);
2178 msglen = extract_long(entargs, 2);
2179 msg = mallok(sizeof(struct CtdlMessage));
2181 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2185 memset(msg, 0, sizeof(struct CtdlMessage));
2186 tempbuf = mallok(msglen);
2187 if (tempbuf == NULL) {
2188 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2193 cprintf("%d %ld\n", SEND_BINARY, msglen);
2195 client_read(&ch, 1); /* 0xFF magic number */
2196 msg->cm_magic = CTDLMESSAGE_MAGIC;
2197 client_read(&ch, 1); /* anon type */
2198 msg->cm_anon_type = ch;
2199 client_read(&ch, 1); /* format type */
2200 msg->cm_format_type = ch;
2201 msglen = msglen - 3;
2203 while (msglen > 0) {
2204 client_read(&which_field, 1);
2205 if (!isalpha(which_field)) valid_msg = 0;
2209 client_read(&ch, 1);
2211 a = strlen(tempbuf);
2214 } while ( (ch != 0) && (msglen > 0) );
2216 msg->cm_fields[which_field] = strdoop(tempbuf);
2219 msg->cm_flags = CM_SKIP_HOOKS;
2220 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2221 CtdlFreeMessage(msg);
2227 * API function to delete messages which match a set of criteria
2228 * (returns the actual number of messages deleted)
2230 int CtdlDeleteMessages(char *room_name, /* which room */
2231 long dmsgnum, /* or "0" for any */
2232 char *content_type /* or "" for any */
2236 struct quickroom qrbuf;
2237 struct cdbdata *cdbfr;
2238 long *msglist = NULL;
2241 int num_deleted = 0;
2243 struct SuppMsgInfo smi;
2245 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2246 room_name, dmsgnum, content_type);
2248 /* get room record, obtaining a lock... */
2249 if (lgetroom(&qrbuf, room_name) != 0) {
2250 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2252 return (0); /* room not found */
2254 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2256 if (cdbfr != NULL) {
2257 msglist = mallok(cdbfr->len);
2258 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2259 num_msgs = cdbfr->len / sizeof(long);
2263 for (i = 0; i < num_msgs; ++i) {
2266 /* Set/clear a bit for each criterion */
2268 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2269 delete_this |= 0x01;
2271 if (strlen(content_type) == 0) {
2272 delete_this |= 0x02;
2274 GetSuppMsgInfo(&smi, msglist[i]);
2275 if (!strcasecmp(smi.smi_content_type,
2277 delete_this |= 0x02;
2281 /* Delete message only if all bits are set */
2282 if (delete_this == 0x03) {
2283 AdjRefCount(msglist[i], -1);
2289 num_msgs = sort_msglist(msglist, num_msgs);
2290 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2291 msglist, (num_msgs * sizeof(long)));
2293 qrbuf.QRhighest = msglist[num_msgs - 1];
2297 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2298 return (num_deleted);
2304 * Delete message from current room
2306 void cmd_dele(char *delstr)
2311 getuser(&CC->usersupp, CC->curr_user);
2312 if ((CC->usersupp.axlevel < 6)
2313 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2314 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2315 && (!(CC->internal_pgm))) {
2316 cprintf("%d Higher access required.\n",
2317 ERROR + HIGHER_ACCESS_REQUIRED);
2320 delnum = extract_long(delstr, 0);
2322 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2325 cprintf("%d %d message%s deleted.\n", OK,
2326 num_deleted, ((num_deleted != 1) ? "s" : ""));
2328 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2334 * move or copy a message to another room
2336 void cmd_move(char *args)
2340 struct quickroom qtemp;
2344 num = extract_long(args, 0);
2345 extract(targ, args, 1);
2346 targ[ROOMNAMELEN - 1] = 0;
2347 is_copy = extract_int(args, 2);
2349 getuser(&CC->usersupp, CC->curr_user);
2350 if ((CC->usersupp.axlevel < 6)
2351 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2352 cprintf("%d Higher access required.\n",
2353 ERROR + HIGHER_ACCESS_REQUIRED);
2357 if (getroom(&qtemp, targ) != 0) {
2358 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2362 err = CtdlSaveMsgPointerInRoom(targ, num,
2363 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2365 cprintf("%d Cannot store message in %s: error %d\n",
2370 /* Now delete the message from the source room,
2371 * if this is a 'move' rather than a 'copy' operation.
2373 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2375 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2381 * GetSuppMsgInfo() - Get the supplementary record for a message
2383 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2386 struct cdbdata *cdbsmi;
2389 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2390 smibuf->smi_msgnum = msgnum;
2391 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2393 /* Use the negative of the message number for its supp record index */
2394 TheIndex = (0L - msgnum);
2396 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2397 if (cdbsmi == NULL) {
2398 return; /* record not found; go with defaults */
2400 memcpy(smibuf, cdbsmi->ptr,
2401 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2402 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2409 * PutSuppMsgInfo() - (re)write supplementary record for a message
2411 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2415 /* Use the negative of the message number for its supp record index */
2416 TheIndex = (0L - smibuf->smi_msgnum);
2418 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2419 smibuf->smi_msgnum, smibuf->smi_refcount);
2421 cdb_store(CDB_MSGMAIN,
2422 &TheIndex, sizeof(long),
2423 smibuf, sizeof(struct SuppMsgInfo));
2428 * AdjRefCount - change the reference count for a message;
2429 * delete the message if it reaches zero
2431 void AdjRefCount(long msgnum, int incr)
2434 struct SuppMsgInfo smi;
2437 /* This is a *tight* critical section; please keep it that way, as
2438 * it may get called while nested in other critical sections.
2439 * Complicating this any further will surely cause deadlock!
2441 begin_critical_section(S_SUPPMSGMAIN);
2442 GetSuppMsgInfo(&smi, msgnum);
2443 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2444 msgnum, smi.smi_refcount);
2445 smi.smi_refcount += incr;
2446 PutSuppMsgInfo(&smi);
2447 end_critical_section(S_SUPPMSGMAIN);
2448 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2449 msgnum, smi.smi_refcount);
2451 /* If the reference count is now zero, delete the message
2452 * (and its supplementary record as well).
2454 if (smi.smi_refcount == 0) {
2455 lprintf(9, "Deleting message <%ld>\n", msgnum);
2457 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2458 delnum = (0L - msgnum);
2459 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2464 * Write a generic object to this room
2466 * Note: this could be much more efficient. Right now we use two temporary
2467 * files, and still pull the message into memory as with all others.
2469 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2470 char *content_type, /* MIME type of this object */
2471 char *tempfilename, /* Where to fetch it from */
2472 struct usersupp *is_mailbox, /* Mailbox room? */
2473 int is_binary, /* Is encoding necessary? */
2474 int is_unique, /* Del others of this type? */
2475 unsigned int flags /* Internal save flags */
2480 char filename[PATH_MAX];
2483 struct quickroom qrbuf;
2484 char roomname[ROOMNAMELEN];
2485 struct CtdlMessage *msg;
2488 if (is_mailbox != NULL)
2489 MailboxName(roomname, is_mailbox, req_room);
2491 safestrncpy(roomname, req_room, sizeof(roomname));
2492 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2494 strcpy(filename, tmpnam(NULL));
2495 fp = fopen(filename, "w");
2499 tempfp = fopen(tempfilename, "r");
2500 if (tempfp == NULL) {
2506 fprintf(fp, "Content-type: %s\n", content_type);
2507 lprintf(9, "Content-type: %s\n", content_type);
2509 if (is_binary == 0) {
2510 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2511 while (ch = getc(tempfp), ch > 0)
2517 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2520 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2521 tempfilename, filename);
2525 lprintf(9, "Allocating\n");
2526 msg = mallok(sizeof(struct CtdlMessage));
2527 memset(msg, 0, sizeof(struct CtdlMessage));
2528 msg->cm_magic = CTDLMESSAGE_MAGIC;
2529 msg->cm_anon_type = MES_NORMAL;
2530 msg->cm_format_type = 4;
2531 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2532 msg->cm_fields['O'] = strdoop(req_room);
2533 msg->cm_fields['N'] = strdoop(config.c_nodename);
2534 msg->cm_fields['H'] = strdoop(config.c_humannode);
2535 msg->cm_flags = flags;
2537 lprintf(9, "Loading\n");
2538 fp = fopen(filename, "rb");
2539 fseek(fp, 0L, SEEK_END);
2542 msg->cm_fields['M'] = mallok(len);
2543 fread(msg->cm_fields['M'], len, 1, fp);
2547 /* Create the requested room if we have to. */
2548 if (getroom(&qrbuf, roomname) != 0) {
2549 create_room(roomname,
2550 ( (is_mailbox != NULL) ? 4 : 3 ),
2553 /* If the caller specified this object as unique, delete all
2554 * other objects of this type that are currently in the room.
2557 lprintf(9, "Deleted %d other msgs of this type\n",
2558 CtdlDeleteMessages(roomname, 0L, content_type));
2560 /* Now write the data */
2561 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2562 CtdlFreeMessage(msg);
2570 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2571 config_msgnum = msgnum;
2575 char *CtdlGetSysConfig(char *sysconfname) {
2576 char hold_rm[ROOMNAMELEN];
2579 struct CtdlMessage *msg;
2582 strcpy(hold_rm, CC->quickroom.QRname);
2583 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2584 getroom(&CC->quickroom, hold_rm);
2589 /* We want the last (and probably only) config in this room */
2590 begin_critical_section(S_CONFIG);
2591 config_msgnum = (-1L);
2592 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2593 CtdlGetSysConfigBackend, NULL);
2594 msgnum = config_msgnum;
2595 end_critical_section(S_CONFIG);
2601 msg = CtdlFetchMessage(msgnum);
2603 conf = strdoop(msg->cm_fields['M']);
2604 CtdlFreeMessage(msg);
2611 getroom(&CC->quickroom, hold_rm);
2613 lprintf(9, "eggstracting...\n");
2614 if (conf != NULL) do {
2615 extract_token(buf, conf, 0, '\n');
2616 lprintf(9, "eggstracted <%s>\n", buf);
2617 strcpy(conf, &conf[strlen(buf)+1]);
2618 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2623 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2624 char temp[PATH_MAX];
2627 strcpy(temp, tmpnam(NULL));
2629 fp = fopen(temp, "w");
2630 if (fp == NULL) return;
2631 fprintf(fp, "%s", sysconfdata);
2634 /* this handy API function does all the work for us */
2635 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);