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)
91 if (isspace(name[i - 1])) {
92 strcpy(&name[i - 1], &name[i]);
95 while (isspace(name[i + 1])) {
96 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];
113 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
116 fp = fopen("network/mail.aliases", "r");
118 fp = fopen("/dev/null", "r");
124 while (fgets(aaa, sizeof aaa, fp) != NULL) {
125 while (isspace(name[0]))
126 strcpy(name, &name[1]);
127 aaa[strlen(aaa) - 1] = 0;
129 for (a = 0; a < strlen(aaa); ++a) {
131 strcpy(bbb, &aaa[a + 1]);
135 if (!strcasecmp(name, aaa))
140 lprintf(7, "Mail is being forwarded to %s\n", name);
142 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
143 for (a=0; a<strlen(name); ++a) {
144 if (name[a] == '@') {
145 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
147 lprintf(7, "Changed to <%s>\n", name);
152 /* determine local or remote type, see citadel.h */
154 for (a = 0; a < strlen(name); ++a)
156 return (MES_INTERNET);
158 for (a = 0; a < strlen(name); ++a)
160 for (b = a; b < strlen(name); ++b)
162 return (MES_INTERNET);
165 for (a = 0; a < strlen(name); ++a)
169 lprintf(7, "Too many @'s in address\n");
173 for (a = 0; a < strlen(name); ++a)
175 strcpy(bbb, &name[a + 1]);
177 strcpy(bbb, &bbb[1]);
178 fp = fopen("network/mail.sysinfo", "r");
182 a = getstring(fp, aaa);
183 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
185 a = getstring(fp, aaa);
186 if (!strncmp(aaa, "use ", 4)) {
187 strcpy(bbb, &aaa[4]);
193 if (!strncmp(aaa, "uum", 3)) {
195 for (a = 0; a < strlen(bbb); ++a) {
201 while (bbb[strlen(bbb) - 1] == '_')
202 bbb[strlen(bbb) - 1] = 0;
203 sprintf(name, &aaa[4], bbb);
204 lprintf(9, "returning MES_INTERNET\n");
205 return (MES_INTERNET);
207 if (!strncmp(aaa, "bin", 3)) {
210 while (aaa[strlen(aaa) - 1] != '@')
211 aaa[strlen(aaa) - 1] = 0;
212 aaa[strlen(aaa) - 1] = 0;
213 while (aaa[strlen(aaa) - 1] == ' ')
214 aaa[strlen(aaa) - 1] = 0;
215 while (bbb[0] != '@')
216 strcpy(bbb, &bbb[1]);
217 strcpy(bbb, &bbb[1]);
218 while (bbb[0] == ' ')
219 strcpy(bbb, &bbb[1]);
220 sprintf(name, "%s @%s", aaa, bbb);
221 lprintf(9, "returning MES_BINARY\n");
227 lprintf(9, "returning MES_LOCAL\n");
236 fp = fopen("citadel.control", "r");
237 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
243 void simple_listing(long msgnum)
245 cprintf("%ld\n", msgnum);
250 /* Determine if a given message matches the fields in a message template.
251 * Return 0 for a successful match.
253 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
256 /* If there aren't any fields in the template, all messages will
259 if (template == NULL) return(0);
261 /* Null messages are bogus. */
262 if (msg == NULL) return(1);
264 for (i='A'; i<='Z'; ++i) {
265 if (template->cm_fields[i] != NULL) {
266 if (msg->cm_fields[i] == NULL) {
269 if (strcasecmp(msg->cm_fields[i],
270 template->cm_fields[i])) return 1;
274 /* All compares succeeded: we have a match! */
282 * API function to perform an operation for each qualifying message in the
283 * current room. (Returns the number of messages processed.)
285 int CtdlForEachMessage(int mode, long ref,
286 int moderation_level,
288 struct CtdlMessage *compare,
289 void (*CallBack) (long msgnum))
294 struct cdbdata *cdbfr;
295 long *msglist = NULL;
297 int num_processed = 0;
299 struct SuppMsgInfo smi;
300 struct CtdlMessage *msg;
302 /* Learn about the user and room in question */
304 getuser(&CC->usersupp, CC->curr_user);
305 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
307 /* Load the message list */
308 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
310 msglist = mallok(cdbfr->len);
311 memcpy(msglist, cdbfr->ptr, cdbfr->len);
312 num_msgs = cdbfr->len / sizeof(long);
315 return 0; /* No messages at all? No further action. */
319 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
320 GetSuppMsgInfo(&smi, msglist[a]);
322 /* Filter out messages that are moderated below the level
323 * currently being viewed at.
325 if (smi.smi_mod < moderation_level) {
329 /* If the caller is looking for a specific MIME type, filter
330 * out all messages which are not of the type requested.
332 if (content_type != NULL) if (strlen(content_type) > 0) {
333 if (strcasecmp(smi.smi_content_type, content_type)) {
339 num_msgs = sort_msglist(msglist, num_msgs);
341 /* If a template was supplied, filter out the messages which
342 * don't match. (This could induce some delays!)
345 if (compare != NULL) {
346 for (a = 0; a < num_msgs; ++a) {
347 msg = CtdlFetchMessage(msglist[a]);
349 if (CtdlMsgCmp(msg, compare)) {
352 CtdlFreeMessage(msg);
360 * Now iterate through the message list, according to the
361 * criteria supplied by the caller.
364 for (a = 0; a < num_msgs; ++a) {
365 thismsg = msglist[a];
370 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
371 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
372 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
373 && (CC->usersupp.flags & US_LASTOLD))
374 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
375 || ((mode == MSGS_FIRST) && (a < ref))
376 || ((mode == MSGS_GT) && (thismsg > ref))
377 || ((mode == MSGS_EQ) && (thismsg == ref))
380 if (CallBack) CallBack(thismsg);
384 phree(msglist); /* Clean up */
385 return num_processed;
391 * cmd_msgs() - get list of message #'s in this room
392 * implements the MSGS server command using CtdlForEachMessage()
394 void cmd_msgs(char *cmdbuf)
403 int with_template = 0;
404 struct CtdlMessage *template = NULL;
406 extract(which, cmdbuf, 0);
407 cm_ref = extract_int(cmdbuf, 1);
408 with_template = extract_int(cmdbuf, 2);
412 if (!strncasecmp(which, "OLD", 3))
414 else if (!strncasecmp(which, "NEW", 3))
416 else if (!strncasecmp(which, "FIRST", 5))
418 else if (!strncasecmp(which, "LAST", 4))
420 else if (!strncasecmp(which, "GT", 2))
423 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
424 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
429 cprintf("%d Send template then receive message list\n",
431 template = (struct CtdlMessage *)
432 mallok(sizeof(struct CtdlMessage));
433 memset(template, 0, sizeof(struct CtdlMessage));
434 while(client_gets(buf), strcmp(buf,"000")) {
435 extract(tfield, buf, 0);
436 extract(tvalue, buf, 1);
437 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
438 if (!strcasecmp(tfield, msgkeys[i])) {
439 template->cm_fields[i] =
446 cprintf("%d Message list...\n", LISTING_FOLLOWS);
449 CtdlForEachMessage(mode, cm_ref,
450 CC->usersupp.moderation_filter,
451 NULL, template, simple_listing);
452 if (template != NULL) CtdlFreeMessage(template);
460 * help_subst() - support routine for help file viewer
462 void help_subst(char *strbuf, char *source, char *dest)
467 while (p = pattern2(strbuf, source), (p >= 0)) {
468 strcpy(workbuf, &strbuf[p + strlen(source)]);
469 strcpy(&strbuf[p], dest);
470 strcat(strbuf, workbuf);
475 void do_help_subst(char *buffer)
479 help_subst(buffer, "^nodename", config.c_nodename);
480 help_subst(buffer, "^humannode", config.c_humannode);
481 help_subst(buffer, "^fqdn", config.c_fqdn);
482 help_subst(buffer, "^username", CC->usersupp.fullname);
483 sprintf(buf2, "%ld", CC->usersupp.usernum);
484 help_subst(buffer, "^usernum", buf2);
485 help_subst(buffer, "^sysadm", config.c_sysadm);
486 help_subst(buffer, "^variantname", CITADEL);
487 sprintf(buf2, "%d", config.c_maxsessions);
488 help_subst(buffer, "^maxsessions", buf2);
494 * memfmout() - Citadel text formatter and paginator.
495 * Although the original purpose of this routine was to format
496 * text to the reader's screen width, all we're really using it
497 * for here is to format text out to 80 columns before sending it
498 * to the client. The client software may reformat it again.
501 int width, /* screen width to use */
502 char *mptr, /* where are we going to get our text from? */
503 char subst, /* nonzero if we should do substitutions */
504 char *nl) /* string to terminate lines with */
516 c = 1; /* c is the current pos */
519 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
521 buffer[strlen(buffer) + 1] = 0;
522 buffer[strlen(buffer)] = ch;
525 if (buffer[0] == '^')
526 do_help_subst(buffer);
528 buffer[strlen(buffer) + 1] = 0;
530 strcpy(buffer, &buffer[1]);
540 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
542 if (((old == 13) || (old == 10)) && (isspace(real))) {
550 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
551 cprintf("%s%s", nl, aaa);
560 if ((strlen(aaa) + c) > (width - 5)) {
570 if ((ch == 13) || (ch == 10)) {
571 cprintf("%s%s", aaa, nl);
578 FMTEND: cprintf("%s%s", aaa, nl);
584 * Callback function for mime parser that simply lists the part
586 void list_this_part(char *name, char *filename, char *partnum, char *disp,
587 void *content, char *cbtype, size_t length)
590 cprintf("part=%s|%s|%s|%s|%s|%d\n",
591 name, filename, partnum, disp, cbtype, length);
596 * Callback function for mime parser that opens a section for downloading
598 void mime_download(char *name, char *filename, char *partnum, char *disp,
599 void *content, char *cbtype, size_t length)
602 /* Silently go away if there's already a download open... */
603 if (CC->download_fp != NULL)
606 /* ...or if this is not the desired section */
607 if (strcasecmp(desired_section, partnum))
610 CC->download_fp = tmpfile();
611 if (CC->download_fp == NULL)
614 fwrite(content, length, 1, CC->download_fp);
615 fflush(CC->download_fp);
616 rewind(CC->download_fp);
618 OpenCmdResult(filename, cbtype);
624 * Load a message from disk into memory.
625 * This is used by CtdlOutputMsg() and other fetch functions.
627 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
628 * using the CtdlMessageFree() function.
630 struct CtdlMessage *CtdlFetchMessage(long msgnum)
632 struct cdbdata *dmsgtext;
633 struct CtdlMessage *ret = NULL;
636 CIT_UBYTE field_header;
639 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
640 if (dmsgtext == NULL) {
643 mptr = dmsgtext->ptr;
645 /* Parse the three bytes that begin EVERY message on disk.
646 * The first is always 0xFF, the on-disk magic number.
647 * The second is the anonymous/public type byte.
648 * The third is the format type byte (vari, fixed, or MIME).
652 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
656 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
657 memset(ret, 0, sizeof(struct CtdlMessage));
659 ret->cm_magic = CTDLMESSAGE_MAGIC;
660 ret->cm_anon_type = *mptr++; /* Anon type byte */
661 ret->cm_format_type = *mptr++; /* Format type byte */
664 * The rest is zero or more arbitrary fields. Load them in.
665 * We're done when we encounter either a zero-length field or
666 * have just processed the 'M' (message text) field.
669 field_length = strlen(mptr);
670 if (field_length == 0)
672 field_header = *mptr++;
673 ret->cm_fields[field_header] = mallok(field_length);
674 strcpy(ret->cm_fields[field_header], mptr);
676 while (*mptr++ != 0); /* advance to next field */
678 } while ((field_length > 0) && (field_header != 'M'));
682 /* Always make sure there's something in the msg text field */
683 if (ret->cm_fields['M'] == NULL)
684 ret->cm_fields['M'] = strdoop("<no text>\n");
686 /* Perform "before read" hooks (aborting if any return nonzero) */
687 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
688 CtdlFreeMessage(ret);
697 * Returns 1 if the supplied pointer points to a valid Citadel message.
698 * If the pointer is NULL or the magic number check fails, returns 0.
700 int is_valid_message(struct CtdlMessage *msg) {
703 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
704 lprintf(3, "is_valid_message() -- self-check failed\n");
712 * 'Destructor' for struct CtdlMessage
714 void CtdlFreeMessage(struct CtdlMessage *msg)
718 if (is_valid_message(msg) == 0) return;
720 for (i = 0; i < 256; ++i)
721 if (msg->cm_fields[i] != NULL) {
722 phree(msg->cm_fields[i]);
725 msg->cm_magic = 0; /* just in case */
731 * Callback function for mime parser that wants to display text
733 void fixed_output(char *name, char *filename, char *partnum, char *disp,
734 void *content, char *cbtype, size_t length)
741 if (!strcasecmp(cbtype, "multipart/alternative")) {
742 strcpy(ma->prefix, partnum);
743 strcat(ma->prefix, ".");
749 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
751 && (ma->did_print == 1) ) {
752 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
758 if ( (!strcasecmp(cbtype, "text/plain"))
759 || (strlen(cbtype)==0) ) {
764 if (ch==10) cprintf("\r\n");
765 else cprintf("%c", ch);
768 else if (!strcasecmp(cbtype, "text/html")) {
769 ptr = html_to_ascii(content, 80, 0);
774 if (ch==10) cprintf("\r\n");
775 else cprintf("%c", ch);
779 else if (strncasecmp(cbtype, "multipart/", 10)) {
780 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
781 partnum, filename, cbtype, length);
787 * Get a message off disk. (returns om_* values found in msgbase.h)
790 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
791 int mode, /* how would you like that message? */
792 int headers_only, /* eschew the message body? */
793 int do_proto, /* do Citadel protocol responses? */
794 int crlf /* Use CRLF newlines instead of LF? */
800 char display_name[256];
801 struct CtdlMessage *TheMessage;
803 char *nl; /* newline string */
805 /* buffers needed for RFC822 translation */
815 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
819 sprintf(mid, "%ld", msg_num);
820 nl = (crlf ? "\r\n" : "\n");
822 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
823 if (do_proto) cprintf("%d Not logged in.\n",
824 ERROR + NOT_LOGGED_IN);
825 return(om_not_logged_in);
828 /* FIXME ... small security issue
829 * We need to check to make sure the requested message is actually
830 * in the current room, and set msg_ok to 1 only if it is. This
831 * functionality is currently missing because I'm in a hurry to replace
832 * broken production code with nonbroken pre-beta code. :( -- ajc
835 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
837 return(om_no_such_msg);
842 * Fetch the message from disk
844 TheMessage = CtdlFetchMessage(msg_num);
845 if (TheMessage == NULL) {
846 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
848 return(om_no_such_msg);
851 /* Are we downloading a MIME component? */
852 if (mode == MT_DOWNLOAD) {
853 if (TheMessage->cm_format_type != FMT_RFC822) {
855 cprintf("%d This is not a MIME message.\n",
857 } else if (CC->download_fp != NULL) {
858 if (do_proto) cprintf(
859 "%d You already have a download open.\n",
862 /* Parse the message text component */
863 mptr = TheMessage->cm_fields['M'];
864 mime_parser(mptr, NULL, *mime_download);
865 /* If there's no file open by this time, the requested
866 * section wasn't found, so print an error
868 if (CC->download_fp == NULL) {
869 if (do_proto) cprintf(
870 "%d Section %s not found.\n",
871 ERROR + FILE_NOT_FOUND,
875 CtdlFreeMessage(TheMessage);
876 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
879 /* now for the user-mode message reading loops */
880 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
882 /* Tell the client which format type we're using. If this is a
883 * MIME message, *lie* about it and tell the user it's fixed-format.
885 if (mode == MT_CITADEL) {
886 if (TheMessage->cm_format_type == FMT_RFC822) {
887 if (do_proto) cprintf("type=1\n");
890 if (do_proto) cprintf("type=%d\n",
891 TheMessage->cm_format_type);
895 /* nhdr=yes means that we're only displaying headers, no body */
896 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
897 if (do_proto) cprintf("nhdr=yes\n");
900 /* begin header processing loop for Citadel message format */
902 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
904 strcpy(display_name, "<unknown>");
905 if (TheMessage->cm_fields['A']) {
906 strcpy(buf, TheMessage->cm_fields['A']);
907 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
908 if (TheMessage->cm_anon_type == MES_ANON)
909 strcpy(display_name, "****");
910 else if (TheMessage->cm_anon_type == MES_AN2)
911 strcpy(display_name, "anonymous");
913 strcpy(display_name, buf);
915 && ((TheMessage->cm_anon_type == MES_ANON)
916 || (TheMessage->cm_anon_type == MES_AN2))) {
917 sprintf(&display_name[strlen(display_name)],
922 strcpy(allkeys, FORDER);
923 for (i=0; i<strlen(allkeys); ++i) {
924 k = (int) allkeys[i];
926 if (TheMessage->cm_fields[k] != NULL) {
928 if (do_proto) cprintf("%s=%s\n",
933 if (do_proto) cprintf("%s=%s\n",
935 TheMessage->cm_fields[k]
944 /* begin header processing loop for RFC822 transfer format */
949 strcpy(snode, NODENAME);
950 strcpy(lnode, HUMANNODE);
951 if (mode == MT_RFC822) {
952 cprintf("X-UIDL: %ld%s", msg_num, nl);
953 for (i = 0; i < 256; ++i) {
954 if (TheMessage->cm_fields[i]) {
955 mptr = TheMessage->cm_fields[i];
962 cprintf("Path: %s%s", mptr, nl);
965 cprintf("Subject: %s%s", mptr, nl);
971 cprintf("X-Citadel-Room: %s%s",
976 cprintf("To: %s%s", mptr, nl);
978 generate_rfc822_datestamp(datestamp,
980 cprintf("Date: %s%s", datestamp, nl);
986 for (i=0; i<strlen(suser); ++i) {
987 suser[i] = tolower(suser[i]);
988 if (!isalnum(suser[i])) suser[i]='_';
991 if (mode == MT_RFC822) {
992 if (!strcasecmp(snode, NODENAME)) {
996 /* Construct a fun message id */
997 cprintf("Message-ID: <%s", mid);
998 if (strchr(mid, '@')==NULL) {
999 cprintf("@%s", snode);
1003 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1005 if (strlen(fuser) > 0) {
1006 cprintf("From: %s (%s)%s", fuser, luser, nl);
1009 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1012 cprintf("Organization: %s%s", lnode, nl);
1015 /* end header processing loop ... at this point, we're in the text */
1017 mptr = TheMessage->cm_fields['M'];
1019 /* Tell the client about the MIME parts in this message */
1020 if (TheMessage->cm_format_type == FMT_RFC822) {
1021 if (mode == MT_CITADEL) {
1022 mime_parser(mptr, NULL, *list_this_part);
1024 else if (mode == MT_MIME) { /* list parts only */
1025 mime_parser(mptr, NULL, *list_this_part);
1026 if (do_proto) cprintf("000\n");
1027 CtdlFreeMessage(TheMessage);
1030 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1031 /* FIXME ... we have to put some code in here to avoid
1032 * printing duplicate header information when both
1033 * Citadel and RFC822 headers exist. Preference should
1034 * probably be given to the RFC822 headers.
1036 while (ch=*(mptr++), ch!=0) {
1038 else if (ch==10) cprintf("%s", nl);
1039 else cprintf("%c", ch);
1041 if (do_proto) cprintf("000\n");
1042 CtdlFreeMessage(TheMessage);
1048 if (do_proto) cprintf("000\n");
1049 CtdlFreeMessage(TheMessage);
1053 /* signify start of msg text */
1054 if (mode == MT_CITADEL)
1055 if (do_proto) cprintf("text\n");
1056 if (mode == MT_RFC822) {
1057 if (TheMessage->cm_fields['U'] == NULL) {
1058 cprintf("Subject: (no subject)%s", nl);
1063 /* If the format type on disk is 1 (fixed-format), then we want
1064 * everything to be output completely literally ... regardless of
1065 * what message transfer format is in use.
1067 if (TheMessage->cm_format_type == FMT_FIXED) {
1069 while (ch = *mptr++, ch > 0) {
1072 if ((ch == 10) || (strlen(buf) > 250)) {
1073 cprintf("%s%s", buf, nl);
1076 buf[strlen(buf) + 1] = 0;
1077 buf[strlen(buf)] = ch;
1080 if (strlen(buf) > 0)
1081 cprintf("%s%s", buf, nl);
1084 /* If the message on disk is format 0 (Citadel vari-format), we
1085 * output using the formatter at 80 columns. This is the final output
1086 * form if the transfer format is RFC822, but if the transfer format
1087 * is Citadel proprietary, it'll still work, because the indentation
1088 * for new paragraphs is correct and the client will reformat the
1089 * message to the reader's screen width.
1091 if (TheMessage->cm_format_type == FMT_CITADEL) {
1092 memfmout(80, mptr, 0, nl);
1095 /* If the message on disk is format 4 (MIME), we've gotta hand it
1096 * off to the MIME parser. The client has already been told that
1097 * this message is format 1 (fixed format), so the callback function
1098 * we use will display those parts as-is.
1100 if (TheMessage->cm_format_type == FMT_RFC822) {
1101 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1102 memset(ma, 0, sizeof(struct ma_info));
1103 mime_parser(mptr, NULL, *fixed_output);
1106 /* now we're done */
1107 if (do_proto) cprintf("000\n");
1108 CtdlFreeMessage(TheMessage);
1115 * display a message (mode 0 - Citadel proprietary)
1117 void cmd_msg0(char *cmdbuf)
1120 int headers_only = 0;
1122 msgid = extract_long(cmdbuf, 0);
1123 headers_only = extract_int(cmdbuf, 1);
1125 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1131 * display a message (mode 2 - RFC822)
1133 void cmd_msg2(char *cmdbuf)
1136 int headers_only = 0;
1138 msgid = extract_long(cmdbuf, 0);
1139 headers_only = extract_int(cmdbuf, 1);
1141 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1147 * display a message (mode 3 - IGnet raw format - internal programs only)
1149 void cmd_msg3(char *cmdbuf)
1152 struct CtdlMessage *msg;
1155 if (CC->internal_pgm == 0) {
1156 cprintf("%d This command is for internal programs only.\n",
1161 msgnum = extract_long(cmdbuf, 0);
1162 msg = CtdlFetchMessage(msgnum);
1164 cprintf("%d Message %ld not found.\n",
1169 serialize_message(&smr, msg);
1170 CtdlFreeMessage(msg);
1173 cprintf("%d Unable to serialize message\n",
1174 ERROR+INTERNAL_ERROR);
1178 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1179 client_write(smr.ser, smr.len);
1186 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1188 void cmd_msg4(char *cmdbuf)
1192 msgid = extract_long(cmdbuf, 0);
1193 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1197 * Open a component of a MIME message as a download file
1199 void cmd_opna(char *cmdbuf)
1203 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1205 msgid = extract_long(cmdbuf, 0);
1206 extract(desired_section, cmdbuf, 1);
1208 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1213 * Save a message pointer into a specified room
1214 * (Returns 0 for success, nonzero for failure)
1215 * roomname may be NULL to use the current room
1217 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1219 char hold_rm[ROOMNAMELEN];
1220 struct cdbdata *cdbfr;
1223 long highest_msg = 0L;
1224 struct CtdlMessage *msg = NULL;
1226 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1227 roomname, msgid, flags);
1229 strcpy(hold_rm, CC->quickroom.QRname);
1231 /* We may need to check to see if this message is real */
1232 if ( (flags & SM_VERIFY_GOODNESS)
1233 || (flags & SM_DO_REPL_CHECK)
1235 msg = CtdlFetchMessage(msgid);
1236 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1239 /* Perform replication checks if necessary */
1240 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1242 if (getroom(&CC->quickroom,
1243 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1245 lprintf(9, "No such room <%s>\n", roomname);
1246 if (msg != NULL) CtdlFreeMessage(msg);
1247 return(ERROR + ROOM_NOT_FOUND);
1250 if (ReplicationChecks(msg) != 0) {
1251 getroom(&CC->quickroom, hold_rm);
1252 if (msg != NULL) CtdlFreeMessage(msg);
1253 lprintf(9, "Did replication, and newer exists\n");
1258 /* Now the regular stuff */
1259 if (lgetroom(&CC->quickroom,
1260 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1262 lprintf(9, "No such room <%s>\n", roomname);
1263 if (msg != NULL) CtdlFreeMessage(msg);
1264 return(ERROR + ROOM_NOT_FOUND);
1267 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1268 if (cdbfr == NULL) {
1272 msglist = mallok(cdbfr->len);
1273 if (msglist == NULL)
1274 lprintf(3, "ERROR malloc msglist!\n");
1275 num_msgs = cdbfr->len / sizeof(long);
1276 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1281 /* Make sure the message doesn't already exist in this room. It
1282 * is absolutely taboo to have more than one reference to the same
1283 * message in a room.
1285 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1286 if (msglist[i] == msgid) {
1287 lputroom(&CC->quickroom); /* unlock the room */
1288 getroom(&CC->quickroom, hold_rm);
1289 if (msg != NULL) CtdlFreeMessage(msg);
1290 return(ERROR + ALREADY_EXISTS);
1294 /* Now add the new message */
1296 msglist = reallok(msglist,
1297 (num_msgs * sizeof(long)));
1299 if (msglist == NULL) {
1300 lprintf(3, "ERROR: can't realloc message list!\n");
1302 msglist[num_msgs - 1] = msgid;
1304 /* Sort the message list, so all the msgid's are in order */
1305 num_msgs = sort_msglist(msglist, num_msgs);
1307 /* Determine the highest message number */
1308 highest_msg = msglist[num_msgs - 1];
1310 /* Write it back to disk. */
1311 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1312 msglist, num_msgs * sizeof(long));
1314 /* Free up the memory we used. */
1317 /* Update the highest-message pointer and unlock the room. */
1318 CC->quickroom.QRhighest = highest_msg;
1319 lputroom(&CC->quickroom);
1320 getroom(&CC->quickroom, hold_rm);
1322 /* Bump the reference count for this message. */
1323 if ((flags & SM_DONT_BUMP_REF)==0) {
1324 AdjRefCount(msgid, +1);
1327 /* Return success. */
1328 if (msg != NULL) CtdlFreeMessage(msg);
1335 * Message base operation to send a message to the master file
1336 * (returns new message number)
1338 * This is the back end for CtdlSaveMsg() and should not be directly
1339 * called by server-side modules.
1342 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1343 FILE *save_a_copy) /* save a copy to disk? */
1350 /* Get a new message number */
1351 newmsgid = get_new_message_number();
1352 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1354 /* Generate an ID if we don't have one already */
1355 if (msg->cm_fields['I']==NULL) {
1356 msg->cm_fields['I'] = strdoop(msgidbuf);
1359 serialize_message(&smr, msg);
1362 cprintf("%d Unable to serialize message\n",
1363 ERROR+INTERNAL_ERROR);
1367 /* Write our little bundle of joy into the message base */
1368 begin_critical_section(S_MSGMAIN);
1369 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1370 smr.ser, smr.len) < 0) {
1371 lprintf(2, "Can't store message\n");
1376 end_critical_section(S_MSGMAIN);
1378 /* If the caller specified that a copy should be saved to a particular
1379 * file handle, do that now too.
1381 if (save_a_copy != NULL) {
1382 fwrite(smr.ser, smr.len, 1, save_a_copy);
1385 /* Free the memory we used for the serialized message */
1388 /* Return the *local* message ID to the caller
1389 * (even if we're storing an incoming network message)
1397 * Serialize a struct CtdlMessage into the format used on disk and network.
1399 * This function loads up a "struct ser_ret" (defined in server.h) which
1400 * contains the length of the serialized message and a pointer to the
1401 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1403 void serialize_message(struct ser_ret *ret, /* return values */
1404 struct CtdlMessage *msg) /* unserialized msg */
1408 static char *forder = FORDER;
1410 if (is_valid_message(msg) == 0) return; /* self check */
1413 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1414 ret->len = ret->len +
1415 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1417 lprintf(9, "calling malloc(%d)\n", ret->len);
1418 ret->ser = mallok(ret->len);
1419 if (ret->ser == NULL) {
1425 ret->ser[1] = msg->cm_anon_type;
1426 ret->ser[2] = msg->cm_format_type;
1429 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1430 ret->ser[wlen++] = (char)forder[i];
1431 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1432 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1434 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1443 * Back end for the ReplicationChecks() function
1445 void check_repl(long msgnum) {
1446 struct CtdlMessage *msg;
1447 time_t timestamp = (-1L);
1449 lprintf(9, "check_repl() found message %ld\n", msgnum);
1450 msg = CtdlFetchMessage(msgnum);
1451 if (msg == NULL) return;
1452 if (msg->cm_fields['T'] != NULL) {
1453 timestamp = atol(msg->cm_fields['T']);
1455 CtdlFreeMessage(msg);
1457 if (timestamp > msg_repl->highest) {
1458 msg_repl->highest = timestamp; /* newer! */
1459 lprintf(9, "newer!\n");
1462 lprintf(9, "older!\n");
1464 /* Existing isn't newer? Then delete the old one(s). */
1465 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1470 * Check to see if any messages already exist which carry the same Extended ID
1474 * -> With older timestamps: delete them and return 0. Message will be saved.
1475 * -> With newer timestamps: return 1. Message save will be aborted.
1477 int ReplicationChecks(struct CtdlMessage *msg) {
1478 struct CtdlMessage *template;
1481 lprintf(9, "ReplicationChecks() started\n");
1482 /* No extended id? Don't do anything. */
1483 if (msg->cm_fields['E'] == NULL) return 0;
1484 if (strlen(msg->cm_fields['E']) == 0) return 0;
1485 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1487 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1488 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1489 msg_repl->highest = atol(msg->cm_fields['T']);
1491 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1492 memset(template, 0, sizeof(struct CtdlMessage));
1493 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1495 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template, check_repl);
1497 /* If a newer message exists with the same Extended ID, abort
1500 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1504 CtdlFreeMessage(template);
1505 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1513 * Save a message to disk
1515 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1516 char *rec, /* Recipient (mail) */
1517 char *force, /* force a particular room? */
1518 int supplied_mailtype) /* local or remote type */
1521 char hold_rm[ROOMNAMELEN];
1522 char actual_rm[ROOMNAMELEN];
1523 char force_room[ROOMNAMELEN];
1524 char content_type[256]; /* We have to learn this */
1525 char recipient[256];
1528 struct usersupp userbuf;
1530 struct SuppMsgInfo smi;
1531 FILE *network_fp = NULL;
1532 static int seqnum = 1;
1533 struct CtdlMessage *imsg;
1537 lprintf(9, "CtdlSaveMsg() called\n");
1538 if (is_valid_message(msg) == 0) return(-1); /* self check */
1539 mailtype = supplied_mailtype;
1541 /* If this message has no timestamp, we take the liberty of
1542 * giving it one, right now.
1544 if (msg->cm_fields['T'] == NULL) {
1545 lprintf(9, "Generating timestamp\n");
1546 sprintf(aaa, "%ld", time(NULL));
1547 msg->cm_fields['T'] = strdoop(aaa);
1550 /* If this message has no path, we generate one.
1552 if (msg->cm_fields['P'] == NULL) {
1553 lprintf(9, "Generating path\n");
1554 if (msg->cm_fields['A'] != NULL) {
1555 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1556 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1557 if (isspace(msg->cm_fields['P'][a])) {
1558 msg->cm_fields['P'][a] = ' ';
1563 msg->cm_fields['P'] = strdoop("unknown");
1567 strcpy(force_room, force);
1569 /* Strip non-printable characters out of the recipient name */
1570 lprintf(9, "Checking recipient (if present)\n");
1571 strcpy(recipient, rec);
1572 for (a = 0; a < strlen(recipient); ++a)
1573 if (!isprint(recipient[a]))
1574 strcpy(&recipient[a], &recipient[a + 1]);
1576 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1577 for (a=0; a<strlen(recipient); ++a) {
1578 if (recipient[a] == '@') {
1579 if (CtdlHostAlias(&recipient[a+1])
1580 == hostalias_localhost) {
1582 lprintf(7, "Changed to <%s>\n", recipient);
1583 mailtype = MES_LOCAL;
1588 lprintf(9, "Recipient is <%s>\n", recipient);
1590 /* Learn about what's inside, because it's what's inside that counts */
1591 lprintf(9, "Learning what's inside\n");
1592 if (msg->cm_fields['M'] == NULL) {
1593 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1596 switch (msg->cm_format_type) {
1598 strcpy(content_type, "text/x-citadel-variformat");
1601 strcpy(content_type, "text/plain");
1604 strcpy(content_type, "text/plain");
1605 /* advance past header fields */
1606 mptr = msg->cm_fields['M'];
1609 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1610 safestrncpy(content_type, mptr,
1611 sizeof(content_type));
1612 strcpy(content_type, &content_type[14]);
1613 for (a = 0; a < strlen(content_type); ++a)
1614 if ((content_type[a] == ';')
1615 || (content_type[a] == ' ')
1616 || (content_type[a] == 13)
1617 || (content_type[a] == 10))
1618 content_type[a] = 0;
1625 /* Goto the correct room */
1626 lprintf(9, "Switching rooms\n");
1627 strcpy(hold_rm, CC->quickroom.QRname);
1628 strcpy(actual_rm, CC->quickroom.QRname);
1630 /* If the user is a twit, move to the twit room for posting */
1631 lprintf(9, "Handling twit stuff\n");
1633 if (CC->usersupp.axlevel == 2) {
1634 strcpy(hold_rm, actual_rm);
1635 strcpy(actual_rm, config.c_twitroom);
1639 /* ...or if this message is destined for Aide> then go there. */
1640 if (strlen(force_room) > 0) {
1641 strcpy(actual_rm, force_room);
1644 lprintf(9, "Possibly relocating\n");
1645 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1646 getroom(&CC->quickroom, actual_rm);
1650 * If this message has no O (room) field, generate one.
1652 if (msg->cm_fields['O'] == NULL) {
1653 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1656 /* Perform "before save" hooks (aborting if any return nonzero) */
1657 lprintf(9, "Performing before-save hooks\n");
1658 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1660 /* If this message has an Extended ID, perform replication checks */
1661 lprintf(9, "Performing replication checks\n");
1662 if (ReplicationChecks(msg) > 0) return(-1);
1664 /* Network mail - send a copy to the network program. */
1665 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1666 lprintf(9, "Sending network spool\n");
1667 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1668 (long) getpid(), CC->cs_pid, ++seqnum);
1669 lprintf(9, "Saving a copy to %s\n", aaa);
1670 network_fp = fopen(aaa, "ab+");
1671 if (network_fp == NULL)
1672 lprintf(2, "ERROR: %s\n", strerror(errno));
1675 /* Save it to disk */
1676 lprintf(9, "Saving to disk\n");
1677 newmsgid = send_message(msg, network_fp);
1678 if (network_fp != NULL) {
1680 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1683 if (newmsgid <= 0L) return(-1);
1685 /* Write a supplemental message info record. This doesn't have to
1686 * be a critical section because nobody else knows about this message
1689 lprintf(9, "Creating SuppMsgInfo record\n");
1690 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1691 smi.smi_msgnum = newmsgid;
1692 smi.smi_refcount = 0;
1693 safestrncpy(smi.smi_content_type, content_type, 64);
1694 PutSuppMsgInfo(&smi);
1696 /* Now figure out where to store the pointers */
1697 lprintf(9, "Storing pointers\n");
1699 /* If this is being done by the networker delivering a private
1700 * message, we want to BYPASS saving the sender's copy (because there
1701 * is no local sender; it would otherwise go to the Trashcan).
1703 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1704 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1705 lprintf(3, "ERROR saving message pointer!\n");
1706 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1710 /* For internet mail, drop a copy in the outbound queue room */
1711 if (mailtype == MES_INTERNET) {
1712 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1715 /* Bump this user's messages posted counter. */
1716 lprintf(9, "Updating user\n");
1717 lgetuser(&CC->usersupp, CC->curr_user);
1718 CC->usersupp.posted = CC->usersupp.posted + 1;
1719 lputuser(&CC->usersupp);
1721 /* If this is private, local mail, make a copy in the
1722 * recipient's mailbox and bump the reference count.
1724 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1725 if (getuser(&userbuf, recipient) == 0) {
1726 lprintf(9, "Delivering private mail\n");
1727 MailboxName(actual_rm, &userbuf, MAILROOM);
1728 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1731 lprintf(9, "No user <%s>, saving in %s> instead\n",
1732 recipient, AIDEROOM);
1733 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1737 /* Perform "after save" hooks */
1738 lprintf(9, "Performing after-save hooks\n");
1739 PerformMessageHooks(msg, EVT_AFTERSAVE);
1742 lprintf(9, "Returning to original room\n");
1743 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1744 getroom(&CC->quickroom, hold_rm);
1746 /* For internet mail, generate delivery instructions
1747 * (Yes, this is recursive! Deal with it!)
1749 if (mailtype == MES_INTERNET) {
1750 lprintf(9, "Generating delivery instructions\n");
1751 instr = mallok(2048);
1753 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1756 SPOOLMIME, newmsgid, time(NULL),
1757 msg->cm_fields['A'], msg->cm_fields['N'],
1760 imsg = mallok(sizeof(struct CtdlMessage));
1761 memset(imsg, 0, sizeof(struct CtdlMessage));
1762 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1763 imsg->cm_anon_type = MES_NORMAL;
1764 imsg->cm_format_type = FMT_RFC822;
1765 imsg->cm_fields['A'] = strdoop("Citadel");
1766 imsg->cm_fields['M'] = instr;
1767 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1768 CtdlFreeMessage(imsg);
1777 * Convenience function for generating small administrative messages.
1779 void quickie_message(char *from, char *to, char *room, char *text)
1781 struct CtdlMessage *msg;
1783 msg = mallok(sizeof(struct CtdlMessage));
1784 memset(msg, 0, sizeof(struct CtdlMessage));
1785 msg->cm_magic = CTDLMESSAGE_MAGIC;
1786 msg->cm_anon_type = MES_NORMAL;
1787 msg->cm_format_type = 0;
1788 msg->cm_fields['A'] = strdoop(from);
1789 msg->cm_fields['O'] = strdoop(room);
1790 msg->cm_fields['N'] = strdoop(NODENAME);
1792 msg->cm_fields['R'] = strdoop(to);
1793 msg->cm_fields['M'] = strdoop(text);
1795 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1796 CtdlFreeMessage(msg);
1797 syslog(LOG_NOTICE, text);
1803 * Back end function used by make_message() and similar functions
1805 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1806 size_t maxlen, /* maximum message length */
1807 char *exist /* if non-null, append to it;
1808 exist is ALWAYS freed */
1811 size_t message_len = 0;
1812 size_t buffer_len = 0;
1816 if (exist == NULL) {
1820 m = reallok(exist, strlen(exist) + 4096);
1821 if (m == NULL) phree(exist);
1824 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1831 /* read in the lines of message text one by one */
1833 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1835 /* augment the buffer if we have to */
1836 if ((message_len + strlen(buf) + 2) > buffer_len) {
1837 lprintf(9, "realloking\n");
1838 ptr = reallok(m, (buffer_len * 2) );
1839 if (ptr == NULL) { /* flush if can't allocate */
1840 while ( (client_gets(buf)>0) &&
1841 strcmp(buf, terminator)) ;;
1844 buffer_len = (buffer_len * 2);
1847 lprintf(9, "buffer_len is %d\n", buffer_len);
1851 if (append == NULL) append = m;
1852 while (strlen(append) > 0) ++append;
1853 strcpy(append, buf);
1854 strcat(append, "\n");
1855 message_len = message_len + strlen(buf) + 1;
1857 /* if we've hit the max msg length, flush the rest */
1858 if (message_len >= maxlen) {
1859 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1870 * Build a binary message to be saved on disk.
1873 struct CtdlMessage *make_message(
1874 struct usersupp *author, /* author's usersupp structure */
1875 char *recipient, /* NULL if it's not mail */
1876 char *room, /* room where it's going */
1877 int type, /* see MES_ types in header file */
1878 int net_type, /* see MES_ types in header file */
1879 int format_type, /* local or remote (see citadel.h) */
1880 char *fake_name) /* who we're masquerading as */
1886 struct CtdlMessage *msg;
1888 msg = mallok(sizeof(struct CtdlMessage));
1889 memset(msg, 0, sizeof(struct CtdlMessage));
1890 msg->cm_magic = CTDLMESSAGE_MAGIC;
1891 msg->cm_anon_type = type;
1892 msg->cm_format_type = format_type;
1894 /* Don't confuse the poor folks if it's not routed mail. */
1895 strcpy(dest_node, "");
1897 /* If net_type is MES_BINARY, split out the destination node. */
1898 if (net_type == MES_BINARY) {
1899 strcpy(dest_node, NODENAME);
1900 for (a = 0; a < strlen(recipient); ++a) {
1901 if (recipient[a] == '@') {
1903 strcpy(dest_node, &recipient[a + 1]);
1908 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1909 if (net_type == MES_INTERNET) {
1910 strcpy(dest_node, "internet");
1913 while (isspace(recipient[strlen(recipient) - 1]))
1914 recipient[strlen(recipient) - 1] = 0;
1916 sprintf(buf, "cit%ld", author->usernum); /* Path */
1917 msg->cm_fields['P'] = strdoop(buf);
1919 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1920 msg->cm_fields['T'] = strdoop(buf);
1922 if (fake_name[0]) /* author */
1923 msg->cm_fields['A'] = strdoop(fake_name);
1925 msg->cm_fields['A'] = strdoop(author->fullname);
1927 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1928 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1930 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1932 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1933 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1935 if (recipient[0] != 0)
1936 msg->cm_fields['R'] = strdoop(recipient);
1937 if (dest_node[0] != 0)
1938 msg->cm_fields['D'] = strdoop(dest_node);
1941 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1942 config.c_maxmsglen, NULL);
1953 * message entry - mode 0 (normal)
1955 void cmd_ent0(char *entargs)
1958 char recipient[256];
1960 int format_type = 0;
1961 char newusername[256];
1962 struct CtdlMessage *msg;
1966 struct usersupp tempUS;
1969 post = extract_int(entargs, 0);
1970 extract(recipient, entargs, 1);
1971 anon_flag = extract_int(entargs, 2);
1972 format_type = extract_int(entargs, 3);
1974 /* first check to make sure the request is valid. */
1976 if (!(CC->logged_in)) {
1977 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1980 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1981 cprintf("%d Need to be validated to enter ",
1982 ERROR + HIGHER_ACCESS_REQUIRED);
1983 cprintf("(except in %s> to sysop)\n", MAILROOM);
1986 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1987 cprintf("%d Need net privileges to enter here.\n",
1988 ERROR + HIGHER_ACCESS_REQUIRED);
1991 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1992 cprintf("%d Sorry, this is a read-only room.\n",
1993 ERROR + HIGHER_ACCESS_REQUIRED);
2000 if (CC->usersupp.axlevel < 6) {
2001 cprintf("%d You don't have permission to masquerade.\n",
2002 ERROR + HIGHER_ACCESS_REQUIRED);
2005 extract(newusername, entargs, 4);
2006 memset(CC->fake_postname, 0, 32);
2007 strcpy(CC->fake_postname, newusername);
2008 cprintf("%d Ok\n", OK);
2011 CC->cs_flags |= CS_POSTING;
2014 if (CC->quickroom.QRflags & QR_MAILBOX) {
2015 if (CC->usersupp.axlevel >= 2) {
2016 strcpy(buf, recipient);
2018 strcpy(buf, "sysop");
2019 e = alias(buf); /* alias and mail type */
2020 if ((buf[0] == 0) || (e == MES_ERROR)) {
2021 cprintf("%d Unknown address - cannot send message.\n",
2022 ERROR + NO_SUCH_USER);
2025 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2026 cprintf("%d Net privileges required for network mail.\n",
2027 ERROR + HIGHER_ACCESS_REQUIRED);
2030 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2031 && ((CC->usersupp.flags & US_INTERNET) == 0)
2032 && (!CC->internal_pgm)) {
2033 cprintf("%d You don't have access to Internet mail.\n",
2034 ERROR + HIGHER_ACCESS_REQUIRED);
2037 if (!strcasecmp(buf, "sysop")) {
2042 goto SKFALL; /* don't search local file */
2043 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2044 cprintf("%d Can't send mail to yourself!\n",
2045 ERROR + NO_SUCH_USER);
2048 /* Check to make sure the user exists; also get the correct
2049 * upper/lower casing of the name.
2051 a = getuser(&tempUS, buf);
2053 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2056 strcpy(buf, tempUS.fullname);
2059 SKFALL: b = MES_NORMAL;
2060 if (CC->quickroom.QRflags & QR_ANONONLY)
2062 if (CC->quickroom.QRflags & QR_ANONOPT) {
2066 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2069 /* If we're only checking the validity of the request, return
2070 * success without creating the message.
2073 cprintf("%d %s\n", OK, buf);
2077 cprintf("%d send message\n", SEND_LISTING);
2079 /* Read in the message from the client. */
2080 if (CC->fake_postname[0])
2081 msg = make_message(&CC->usersupp, buf,
2082 CC->quickroom.QRname, b, e, format_type,
2084 else if (CC->fake_username[0])
2085 msg = make_message(&CC->usersupp, buf,
2086 CC->quickroom.QRname, b, e, format_type,
2089 msg = make_message(&CC->usersupp, buf,
2090 CC->quickroom.QRname, b, e, format_type, "");
2093 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2094 CtdlFreeMessage(msg);
2095 CC->fake_postname[0] = '\0';
2102 * message entry - mode 3 (raw)
2104 void cmd_ent3(char *entargs)
2110 unsigned char ch, which_field;
2111 struct usersupp tempUS;
2113 struct CtdlMessage *msg;
2116 if (CC->internal_pgm == 0) {
2117 cprintf("%d This command is for internal programs only.\n",
2122 /* See if there's a recipient, but make sure it's a real one */
2123 extract(recp, entargs, 1);
2124 for (a = 0; a < strlen(recp); ++a)
2125 if (!isprint(recp[a]))
2126 strcpy(&recp[a], &recp[a + 1]);
2127 while (isspace(recp[0]))
2128 strcpy(recp, &recp[1]);
2129 while (isspace(recp[strlen(recp) - 1]))
2130 recp[strlen(recp) - 1] = 0;
2132 /* If we're in Mail, check the recipient */
2133 if (strlen(recp) > 0) {
2134 e = alias(recp); /* alias and mail type */
2135 if ((recp[0] == 0) || (e == MES_ERROR)) {
2136 cprintf("%d Unknown address - cannot send message.\n",
2137 ERROR + NO_SUCH_USER);
2140 if (e == MES_LOCAL) {
2141 a = getuser(&tempUS, recp);
2143 cprintf("%d No such user.\n",
2144 ERROR + NO_SUCH_USER);
2150 /* At this point, message has been approved. */
2151 if (extract_int(entargs, 0) == 0) {
2152 cprintf("%d OK to send\n", OK);
2156 msglen = extract_long(entargs, 2);
2157 msg = mallok(sizeof(struct CtdlMessage));
2159 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2163 memset(msg, 0, sizeof(struct CtdlMessage));
2164 tempbuf = mallok(msglen);
2165 if (tempbuf == NULL) {
2166 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2171 cprintf("%d %ld\n", SEND_BINARY, msglen);
2173 client_read(&ch, 1); /* 0xFF magic number */
2174 msg->cm_magic = CTDLMESSAGE_MAGIC;
2175 client_read(&ch, 1); /* anon type */
2176 msg->cm_anon_type = ch;
2177 client_read(&ch, 1); /* format type */
2178 msg->cm_format_type = ch;
2179 msglen = msglen - 3;
2181 while (msglen > 0) {
2182 client_read(&which_field, 1);
2183 if (!isalpha(which_field)) valid_msg = 0;
2187 client_read(&ch, 1);
2189 a = strlen(tempbuf);
2192 } while ( (ch != 0) && (msglen > 0) );
2194 msg->cm_fields[which_field] = strdoop(tempbuf);
2197 msg->cm_flags = CM_SKIP_HOOKS;
2198 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2199 CtdlFreeMessage(msg);
2205 * API function to delete messages which match a set of criteria
2206 * (returns the actual number of messages deleted)
2208 int CtdlDeleteMessages(char *room_name, /* which room */
2209 long dmsgnum, /* or "0" for any */
2210 char *content_type /* or "" for any */
2214 struct quickroom qrbuf;
2215 struct cdbdata *cdbfr;
2216 long *msglist = NULL;
2219 int num_deleted = 0;
2221 struct SuppMsgInfo smi;
2223 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2224 room_name, dmsgnum, content_type);
2226 /* get room record, obtaining a lock... */
2227 if (lgetroom(&qrbuf, room_name) != 0) {
2228 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2230 return (0); /* room not found */
2232 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2234 if (cdbfr != NULL) {
2235 msglist = mallok(cdbfr->len);
2236 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2237 num_msgs = cdbfr->len / sizeof(long);
2241 for (i = 0; i < num_msgs; ++i) {
2244 /* Set/clear a bit for each criterion */
2246 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2247 delete_this |= 0x01;
2249 if (strlen(content_type) == 0) {
2250 delete_this |= 0x02;
2252 GetSuppMsgInfo(&smi, msglist[i]);
2253 if (!strcasecmp(smi.smi_content_type,
2255 delete_this |= 0x02;
2259 /* Delete message only if all bits are set */
2260 if (delete_this == 0x03) {
2261 AdjRefCount(msglist[i], -1);
2267 num_msgs = sort_msglist(msglist, num_msgs);
2268 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2269 msglist, (num_msgs * sizeof(long)));
2271 qrbuf.QRhighest = msglist[num_msgs - 1];
2275 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2276 return (num_deleted);
2282 * Delete message from current room
2284 void cmd_dele(char *delstr)
2289 getuser(&CC->usersupp, CC->curr_user);
2290 if ((CC->usersupp.axlevel < 6)
2291 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2292 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2293 && (!(CC->internal_pgm))) {
2294 cprintf("%d Higher access required.\n",
2295 ERROR + HIGHER_ACCESS_REQUIRED);
2298 delnum = extract_long(delstr, 0);
2300 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2303 cprintf("%d %d message%s deleted.\n", OK,
2304 num_deleted, ((num_deleted != 1) ? "s" : ""));
2306 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2312 * move or copy a message to another room
2314 void cmd_move(char *args)
2318 struct quickroom qtemp;
2322 num = extract_long(args, 0);
2323 extract(targ, args, 1);
2324 targ[ROOMNAMELEN - 1] = 0;
2325 is_copy = extract_int(args, 2);
2327 getuser(&CC->usersupp, CC->curr_user);
2328 if ((CC->usersupp.axlevel < 6)
2329 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2330 cprintf("%d Higher access required.\n",
2331 ERROR + HIGHER_ACCESS_REQUIRED);
2335 if (getroom(&qtemp, targ) != 0) {
2336 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2340 err = CtdlSaveMsgPointerInRoom(targ, num,
2341 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2343 cprintf("%d Cannot store message in %s: error %d\n",
2348 /* Now delete the message from the source room,
2349 * if this is a 'move' rather than a 'copy' operation.
2351 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2353 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2359 * GetSuppMsgInfo() - Get the supplementary record for a message
2361 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2364 struct cdbdata *cdbsmi;
2367 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2368 smibuf->smi_msgnum = msgnum;
2369 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2371 /* Use the negative of the message number for its supp record index */
2372 TheIndex = (0L - msgnum);
2374 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2375 if (cdbsmi == NULL) {
2376 return; /* record not found; go with defaults */
2378 memcpy(smibuf, cdbsmi->ptr,
2379 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2380 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2387 * PutSuppMsgInfo() - (re)write supplementary record for a message
2389 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2393 /* Use the negative of the message number for its supp record index */
2394 TheIndex = (0L - smibuf->smi_msgnum);
2396 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2397 smibuf->smi_msgnum, smibuf->smi_refcount);
2399 cdb_store(CDB_MSGMAIN,
2400 &TheIndex, sizeof(long),
2401 smibuf, sizeof(struct SuppMsgInfo));
2406 * AdjRefCount - change the reference count for a message;
2407 * delete the message if it reaches zero
2409 void AdjRefCount(long msgnum, int incr)
2412 struct SuppMsgInfo smi;
2415 /* This is a *tight* critical section; please keep it that way, as
2416 * it may get called while nested in other critical sections.
2417 * Complicating this any further will surely cause deadlock!
2419 begin_critical_section(S_SUPPMSGMAIN);
2420 GetSuppMsgInfo(&smi, msgnum);
2421 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2422 msgnum, smi.smi_refcount);
2423 smi.smi_refcount += incr;
2424 PutSuppMsgInfo(&smi);
2425 end_critical_section(S_SUPPMSGMAIN);
2426 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2427 msgnum, smi.smi_refcount);
2429 /* If the reference count is now zero, delete the message
2430 * (and its supplementary record as well).
2432 if (smi.smi_refcount == 0) {
2433 lprintf(9, "Deleting message <%ld>\n", msgnum);
2435 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2436 delnum = (0L - msgnum);
2437 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2442 * Write a generic object to this room
2444 * Note: this could be much more efficient. Right now we use two temporary
2445 * files, and still pull the message into memory as with all others.
2447 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2448 char *content_type, /* MIME type of this object */
2449 char *tempfilename, /* Where to fetch it from */
2450 struct usersupp *is_mailbox, /* Mailbox room? */
2451 int is_binary, /* Is encoding necessary? */
2452 int is_unique, /* Del others of this type? */
2453 unsigned int flags /* Internal save flags */
2458 char filename[PATH_MAX];
2461 struct quickroom qrbuf;
2462 char roomname[ROOMNAMELEN];
2463 struct CtdlMessage *msg;
2466 if (is_mailbox != NULL)
2467 MailboxName(roomname, is_mailbox, req_room);
2469 safestrncpy(roomname, req_room, sizeof(roomname));
2470 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2472 strcpy(filename, tmpnam(NULL));
2473 fp = fopen(filename, "w");
2477 tempfp = fopen(tempfilename, "r");
2478 if (tempfp == NULL) {
2484 fprintf(fp, "Content-type: %s\n", content_type);
2485 lprintf(9, "Content-type: %s\n", content_type);
2487 if (is_binary == 0) {
2488 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2489 while (ch = getc(tempfp), ch > 0)
2495 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2498 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2499 tempfilename, filename);
2503 lprintf(9, "Allocating\n");
2504 msg = mallok(sizeof(struct CtdlMessage));
2505 memset(msg, 0, sizeof(struct CtdlMessage));
2506 msg->cm_magic = CTDLMESSAGE_MAGIC;
2507 msg->cm_anon_type = MES_NORMAL;
2508 msg->cm_format_type = 4;
2509 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2510 msg->cm_fields['O'] = strdoop(req_room);
2511 msg->cm_fields['N'] = strdoop(config.c_nodename);
2512 msg->cm_fields['H'] = strdoop(config.c_humannode);
2513 msg->cm_flags = flags;
2515 lprintf(9, "Loading\n");
2516 fp = fopen(filename, "rb");
2517 fseek(fp, 0L, SEEK_END);
2520 msg->cm_fields['M'] = mallok(len);
2521 fread(msg->cm_fields['M'], len, 1, fp);
2525 /* Create the requested room if we have to. */
2526 if (getroom(&qrbuf, roomname) != 0) {
2527 create_room(roomname,
2528 ( (is_mailbox != NULL) ? 4 : 3 ),
2531 /* If the caller specified this object as unique, delete all
2532 * other objects of this type that are currently in the room.
2535 lprintf(9, "Deleted %d other msgs of this type\n",
2536 CtdlDeleteMessages(roomname, 0L, content_type));
2538 /* Now write the data */
2539 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2540 CtdlFreeMessage(msg);
2548 void CtdlGetSysConfigBackend(long msgnum) {
2549 config_msgnum = msgnum;
2553 char *CtdlGetSysConfig(char *sysconfname) {
2554 char hold_rm[ROOMNAMELEN];
2557 struct CtdlMessage *msg;
2560 strcpy(hold_rm, CC->quickroom.QRname);
2561 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2562 getroom(&CC->quickroom, hold_rm);
2567 /* We want the last (and probably only) config in this room */
2568 begin_critical_section(S_CONFIG);
2569 config_msgnum = (-1L);
2570 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2571 CtdlGetSysConfigBackend);
2572 msgnum = config_msgnum;
2573 end_critical_section(S_CONFIG);
2579 msg = CtdlFetchMessage(msgnum);
2581 conf = strdoop(msg->cm_fields['M']);
2582 CtdlFreeMessage(msg);
2589 getroom(&CC->quickroom, hold_rm);
2591 lprintf(9, "eggstracting...\n");
2592 if (conf != NULL) do {
2593 extract_token(buf, conf, 0, '\n');
2594 lprintf(9, "eggstracted <%s>\n", buf);
2595 strcpy(conf, &conf[strlen(buf)+1]);
2596 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2601 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2602 char temp[PATH_MAX];
2605 strcpy(temp, tmpnam(NULL));
2607 fp = fopen(temp, "w");
2608 if (fp == NULL) return;
2609 fprintf(fp, "%s", sysconfdata);
2612 /* this handy API function does all the work for us */
2613 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);