21 #include "sysdep_decls.h"
22 #include "citserver.h"
27 #include "dynloader.h"
29 #include "mime_parser.h"
32 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
33 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
34 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
36 extern struct config config;
40 "", "", "", "", "", "", "", "",
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
47 "", "", "", "", "", "", "", "",
73 * This function is self explanatory.
74 * (What can I say, I'm in a weird mood today...)
76 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
80 for (i = 0; i < strlen(name); ++i)
83 if (isspace(name[i - 1])) {
84 strcpy(&name[i - 1], &name[i]);
87 while (isspace(name[i + 1])) {
88 strcpy(&name[i + 1], &name[i + 2]);
95 * Aliasing for network mail.
96 * (Error messages have been commented out, because this is a server.)
99 { /* process alias and routing info for mail */
102 char aaa[300], bbb[300];
104 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
106 fp = fopen("network/mail.aliases", "r");
108 fp = fopen("/dev/null", "r");
113 while (fgets(aaa, sizeof aaa, fp) != NULL) {
114 while (isspace(name[0]))
115 strcpy(name, &name[1]);
116 aaa[strlen(aaa) - 1] = 0;
118 for (a = 0; a < strlen(aaa); ++a) {
120 strcpy(bbb, &aaa[a + 1]);
124 if (!strcasecmp(name, aaa))
128 lprintf(7, "Mail is being forwarded to %s\n", name);
130 /* determine local or remote type, see citadel.h */
131 for (a = 0; a < strlen(name); ++a)
133 return (MES_INTERNET);
134 for (a = 0; a < strlen(name); ++a)
136 for (b = a; b < strlen(name); ++b)
138 return (MES_INTERNET);
140 for (a = 0; a < strlen(name); ++a)
144 lprintf(7, "Too many @'s in address\n");
148 for (a = 0; a < strlen(name); ++a)
150 strcpy(bbb, &name[a + 1]);
152 strcpy(bbb, &bbb[1]);
153 fp = fopen("network/mail.sysinfo", "r");
157 a = getstring(fp, aaa);
158 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
159 a = getstring(fp, aaa);
160 if (!strncmp(aaa, "use ", 4)) {
161 strcpy(bbb, &aaa[4]);
166 if (!strncmp(aaa, "uum", 3)) {
168 for (a = 0; a < strlen(bbb); ++a) {
174 while (bbb[strlen(bbb) - 1] == '_')
175 bbb[strlen(bbb) - 1] = 0;
176 sprintf(name, &aaa[4], bbb);
177 return (MES_INTERNET);
179 if (!strncmp(aaa, "bin", 3)) {
182 while (aaa[strlen(aaa) - 1] != '@')
183 aaa[strlen(aaa) - 1] = 0;
184 aaa[strlen(aaa) - 1] = 0;
185 while (aaa[strlen(aaa) - 1] == ' ')
186 aaa[strlen(aaa) - 1] = 0;
187 while (bbb[0] != '@')
188 strcpy(bbb, &bbb[1]);
189 strcpy(bbb, &bbb[1]);
190 while (bbb[0] == ' ')
191 strcpy(bbb, &bbb[1]);
192 sprintf(name, "%s @%s", aaa, bbb);
205 fp = fopen("citadel.control", "r");
206 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
212 void simple_listing(long msgnum)
214 cprintf("%ld\n", msgnum);
219 /* Determine if a given message matches the fields in a message template.
220 * Return 0 for a successful match.
222 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
225 /* If there aren't any fields in the template, all messages will
228 if (template == NULL) return(0);
230 /* Null messages are bogus. */
231 if (msg == NULL) return(1);
233 for (i='A'; i<='Z'; ++i) {
234 if (template->cm_fields[i] != NULL) {
235 if (msg->cm_fields[i] == NULL) {
238 if (strcasecmp(msg->cm_fields[i],
239 template->cm_fields[i])) return 1;
243 /* All compares succeeded: we have a match! */
251 * API function to perform an operation for each qualifying message in the
254 void CtdlForEachMessage(int mode, long ref,
256 struct CtdlMessage *compare,
257 void (*CallBack) (long msgnum))
262 struct cdbdata *cdbfr;
263 long *msglist = NULL;
266 struct SuppMsgInfo smi;
267 struct CtdlMessage *msg;
269 /* Learn about the user and room in question */
271 getuser(&CC->usersupp, CC->curr_user);
272 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
274 /* Load the message list */
275 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
277 msglist = mallok(cdbfr->len);
278 memcpy(msglist, cdbfr->ptr, cdbfr->len);
279 num_msgs = cdbfr->len / sizeof(long);
282 return; /* No messages at all? No further action. */
286 /* If the caller is looking for a specific MIME type, then filter
287 * out all messages which are not of the type requested.
290 if (content_type != NULL)
291 if (strlen(content_type) > 0)
292 for (a = 0; a < num_msgs; ++a) {
293 GetSuppMsgInfo(&smi, msglist[a]);
294 if (strcasecmp(smi.smi_content_type, content_type)) {
299 num_msgs = sort_msglist(msglist, num_msgs);
301 /* If a template was supplied, filter out the messages which
302 * don't match. (This could induce some delays!)
305 if (compare != NULL) {
306 for (a = 0; a < num_msgs; ++a) {
307 msg = CtdlFetchMessage(msglist[a]);
309 if (CtdlMsgCmp(msg, compare)) {
312 CtdlFreeMessage(msg);
320 * Now iterate through the message list, according to the
321 * criteria supplied by the caller.
324 for (a = 0; a < num_msgs; ++a) {
325 thismsg = msglist[a];
330 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
331 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
332 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
333 && (CC->usersupp.flags & US_LASTOLD))
334 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
335 || ((mode == MSGS_FIRST) && (a < ref))
336 || ((mode == MSGS_GT) && (thismsg > ref))
342 phree(msglist); /* Clean up */
348 * cmd_msgs() - get list of message #'s in this room
349 * implements the MSGS server command using CtdlForEachMessage()
351 void cmd_msgs(char *cmdbuf)
360 int with_template = 0;
361 struct CtdlMessage *template = NULL;
363 extract(which, cmdbuf, 0);
364 cm_ref = extract_int(cmdbuf, 1);
365 with_template = extract_int(cmdbuf, 2);
369 if (!strncasecmp(which, "OLD", 3))
371 else if (!strncasecmp(which, "NEW", 3))
373 else if (!strncasecmp(which, "FIRST", 5))
375 else if (!strncasecmp(which, "LAST", 4))
377 else if (!strncasecmp(which, "GT", 2))
380 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
381 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
386 cprintf("%d Send template then receive message list\n",
388 template = (struct CtdlMessage *)
389 mallok(sizeof(struct CtdlMessage));
390 memset(template, 0, sizeof(struct CtdlMessage));
391 while(client_gets(buf), strcmp(buf,"000")) {
392 extract(tfield, buf, 0);
393 extract(tvalue, buf, 1);
394 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
395 if (!strcasecmp(tfield, msgkeys[i])) {
396 template->cm_fields[i] =
403 cprintf("%d Message list...\n", LISTING_FOLLOWS);
406 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
407 if (template != NULL) CtdlFreeMessage(template);
415 * help_subst() - support routine for help file viewer
417 void help_subst(char *strbuf, char *source, char *dest)
422 while (p = pattern2(strbuf, source), (p >= 0)) {
423 strcpy(workbuf, &strbuf[p + strlen(source)]);
424 strcpy(&strbuf[p], dest);
425 strcat(strbuf, workbuf);
430 void do_help_subst(char *buffer)
434 help_subst(buffer, "^nodename", config.c_nodename);
435 help_subst(buffer, "^humannode", config.c_humannode);
436 help_subst(buffer, "^fqdn", config.c_fqdn);
437 help_subst(buffer, "^username", CC->usersupp.fullname);
438 sprintf(buf2, "%ld", CC->usersupp.usernum);
439 help_subst(buffer, "^usernum", buf2);
440 help_subst(buffer, "^sysadm", config.c_sysadm);
441 help_subst(buffer, "^variantname", CITADEL);
442 sprintf(buf2, "%d", config.c_maxsessions);
443 help_subst(buffer, "^maxsessions", buf2);
449 * memfmout() - Citadel text formatter and paginator.
450 * Although the original purpose of this routine was to format
451 * text to the reader's screen width, all we're really using it
452 * for here is to format text out to 80 columns before sending it
453 * to the client. The client software may reformat it again.
456 int width, /* screen width to use */
457 char *mptr, /* where are we going to get our text from? */
458 char subst) /* nonzero if we should do substitutions */
470 c = 1; /* c is the current pos */
473 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
475 buffer[strlen(buffer) + 1] = 0;
476 buffer[strlen(buffer)] = ch;
479 if (buffer[0] == '^')
480 do_help_subst(buffer);
482 buffer[strlen(buffer) + 1] = 0;
484 strcpy(buffer, &buffer[1]);
494 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
496 if (((old == 13) || (old == 10)) && (isspace(real))) {
504 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
505 cprintf("\n%s", aaa);
514 if ((strlen(aaa) + c) > (width - 5)) {
524 if ((ch == 13) || (ch == 10)) {
525 cprintf("%s\n", aaa);
532 FMTEND: cprintf("%s\n", aaa);
538 * Callback function for mime parser that simply lists the part
540 void list_this_part(char *name, char *filename, char *partnum, char *disp,
541 void *content, char *cbtype, size_t length)
544 cprintf("part=%s|%s|%s|%s|%s|%d\n",
545 name, filename, partnum, disp, cbtype, length);
550 * Callback function for mime parser that opens a section for downloading
552 void mime_download(char *name, char *filename, char *partnum, char *disp,
553 void *content, char *cbtype, size_t length)
556 /* Silently go away if there's already a download open... */
557 if (CC->download_fp != NULL)
560 /* ...or if this is not the desired section */
561 if (strcasecmp(desired_section, partnum))
564 CC->download_fp = tmpfile();
565 if (CC->download_fp == NULL)
568 fwrite(content, length, 1, CC->download_fp);
569 fflush(CC->download_fp);
570 rewind(CC->download_fp);
572 OpenCmdResult(filename, cbtype);
578 * Load a message from disk into memory.
579 * This is used by CtdlOutputMsg() and other fetch functions.
581 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
582 * using the CtdlMessageFree() function.
584 struct CtdlMessage *CtdlFetchMessage(long msgnum)
586 struct cdbdata *dmsgtext;
587 struct CtdlMessage *ret = NULL;
590 CIT_UBYTE field_header;
593 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
594 if (dmsgtext == NULL) {
597 mptr = dmsgtext->ptr;
599 /* Parse the three bytes that begin EVERY message on disk.
600 * The first is always 0xFF, the on-disk magic number.
601 * The second is the anonymous/public type byte.
602 * The third is the format type byte (vari, fixed, or MIME).
606 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
610 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
611 memset(ret, 0, sizeof(struct CtdlMessage));
613 ret->cm_magic = CTDLMESSAGE_MAGIC;
614 ret->cm_anon_type = *mptr++; /* Anon type byte */
615 ret->cm_format_type = *mptr++; /* Format type byte */
618 * The rest is zero or more arbitrary fields. Load them in.
619 * We're done when we encounter either a zero-length field or
620 * have just processed the 'M' (message text) field.
623 field_length = strlen(mptr);
624 if (field_length == 0)
626 field_header = *mptr++;
627 ret->cm_fields[field_header] = mallok(field_length);
628 strcpy(ret->cm_fields[field_header], mptr);
630 while (*mptr++ != 0); /* advance to next field */
632 } while ((field_length > 0) && (field_header != 'M'));
636 /* Always make sure there's something in the msg text field */
637 if (ret->cm_fields['M'] == NULL)
638 ret->cm_fields['M'] = strdoop("<no text>\n");
640 /* Perform "before read" hooks (aborting if any return nonzero) */
641 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
642 CtdlFreeMessage(ret);
651 * Returns 1 if the supplied pointer points to a valid Citadel message.
652 * If the pointer is NULL or the magic number check fails, returns 0.
654 int is_valid_message(struct CtdlMessage *msg) {
657 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
658 lprintf(3, "is_valid_message() -- self-check failed\n");
666 * 'Destructor' for struct CtdlMessage
668 void CtdlFreeMessage(struct CtdlMessage *msg)
672 if (is_valid_message(msg) == 0) return;
674 for (i = 0; i < 256; ++i)
675 if (msg->cm_fields[i] != NULL)
676 phree(msg->cm_fields[i]);
678 msg->cm_magic = 0; /* just in case */
684 * Get a message off disk. (returns om_* values found in msgbase.h)
687 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
688 int mode, /* how would you like that message? */
689 int headers_only, /* eschew the message body? */
690 int do_proto, /* do Citadel protocol responses? */
693 int crlf /* Use CRLF newlines instead of LF? */
700 char display_name[256];
701 struct CtdlMessage *TheMessage;
703 char *nl; /* newline string */
705 /* buffers needed for RFC822 translation */
714 /* BEGIN NESTED FUNCTION omprintf() */
715 void omprintf(const char *format, ...) {
719 va_start(arg_ptr, format);
720 if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
721 buf[sizeof buf - 2] = '\n';
723 fwrite(buf, strlen(buf), 1, outfp);
725 else if (outsock >= 0) {
726 write(outsock, buf, strlen(buf));
729 client_write(buf, strlen(buf));
733 /* END NESTED FUNCTION omprintf() */
735 /* BEGIN NESTED FUNCTION omfmout() */
736 void omfmout(char *mptr) {
747 c = 1; /* c is the current pos */
755 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
757 if (((old == 13) || (old == 10)) && (isspace(real))) {
765 if (((strlen(aaa) + c) > (75)) && (strlen(aaa) > (75))) {
766 omprintf("%s%s", nl, aaa);
775 if ((strlen(aaa) + c) > (75)) {
779 omprintf("%s ", aaa);
785 if ((ch == 13) || (ch == 10)) {
786 omprintf("%s%s", aaa, nl);
793 FMTEND: omprintf("%s%s", aaa, nl);
795 /* END NESTED FUNCTION omfmout() */
797 /* BEGIN NESTED FUNCTION fixed_output() */
799 * Callback function for mime parser that wants to display text
801 void fixed_output(char *name, char *filename, char *partnum, char *disp,
802 void *content, char *cbtype, size_t length)
808 if (!strcasecmp(cbtype, "multipart/alternative")) {
809 strcpy(ma->prefix, partnum);
810 strcat(ma->prefix, ".");
816 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
818 && (ma->did_print == 1) ) {
819 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
825 if ( (!strcasecmp(cbtype, "text/plain"))
826 || (strlen(cbtype)==0) ) {
831 if (ch==10) omprintf("%s", nl);
832 else omprintf("%c", ch);
835 else if (!strcasecmp(cbtype, "text/html")) {
836 ptr = html_to_ascii(content, 80, 0);
841 if (ch==10) omprintf("%s", nl);
842 else omprintf("%c", ch);
846 else if (strncasecmp(cbtype, "multipart/", 10)) {
847 omprintf("Part %s: %s (%s) (%d bytes)%s",
848 partnum, filename, cbtype, length, nl);
852 /* END NESTED FUNCTION fixed_output() */
854 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
858 sprintf(mid, "%ld", msg_num);
859 nl = (crlf ? "\r\n" : "\n");
861 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
862 if (do_proto) cprintf("%d Not logged in.\n",
863 ERROR + NOT_LOGGED_IN);
864 return(om_not_logged_in);
867 /* FIX ... small security issue
868 * We need to check to make sure the requested message is actually
869 * in the current room, and set msg_ok to 1 only if it is. This
870 * functionality is currently missing because I'm in a hurry to replace
871 * broken production code with nonbroken pre-beta code. :( -- ajc
874 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
876 return(om_no_such_msg);
881 * Fetch the message from disk
883 TheMessage = CtdlFetchMessage(msg_num);
884 if (TheMessage == NULL) {
885 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
887 return(om_no_such_msg);
890 /* Are we downloading a MIME component? */
891 if (mode == MT_DOWNLOAD) {
892 if (TheMessage->cm_format_type != FMT_RFC822) {
894 cprintf("%d This is not a MIME message.\n",
896 } else if (CC->download_fp != NULL) {
897 if (do_proto) cprintf(
898 "%d You already have a download open.\n",
901 /* Parse the message text component */
902 mptr = TheMessage->cm_fields['M'];
903 mime_parser(mptr, NULL, *mime_download);
904 /* If there's no file open by this time, the requested
905 * section wasn't found, so print an error
907 if (CC->download_fp == NULL) {
908 if (do_proto) cprintf(
909 "%d Section %s not found.\n",
910 ERROR + FILE_NOT_FOUND,
914 CtdlFreeMessage(TheMessage);
915 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
918 /* now for the user-mode message reading loops */
919 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
921 /* Tell the client which format type we're using. If this is a
922 * MIME message, *lie* about it and tell the user it's fixed-format.
924 if (mode == MT_CITADEL) {
925 if (TheMessage->cm_format_type == FMT_RFC822) {
926 if (do_proto) cprintf("type=1\n");
929 if (do_proto) cprintf("type=%d\n",
930 TheMessage->cm_format_type);
934 /* nhdr=yes means that we're only displaying headers, no body */
935 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
936 if (do_proto) cprintf("nhdr=yes\n");
939 /* begin header processing loop for Citadel message format */
941 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
943 strcpy(display_name, "<unknown>");
944 if (TheMessage->cm_fields['A']) {
945 strcpy(buf, TheMessage->cm_fields['A']);
946 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
947 if (TheMessage->cm_anon_type == MES_ANON)
948 strcpy(display_name, "****");
949 else if (TheMessage->cm_anon_type == MES_AN2)
950 strcpy(display_name, "anonymous");
952 strcpy(display_name, buf);
954 && ((TheMessage->cm_anon_type == MES_ANON)
955 || (TheMessage->cm_anon_type == MES_AN2))) {
956 sprintf(&display_name[strlen(display_name)],
961 strcpy(allkeys, FORDER);
962 for (i=0; i<strlen(allkeys); ++i) {
963 k = (int) allkeys[i];
965 if (TheMessage->cm_fields[k] != NULL) {
967 if (do_proto) cprintf("%s=%s\n",
972 if (do_proto) cprintf("%s=%s\n",
974 TheMessage->cm_fields[k]
983 /* begin header processing loop for RFC822 transfer format */
987 strcpy(snode, NODENAME);
988 strcpy(lnode, HUMANNODE);
989 if (mode == MT_RFC822) {
990 omprintf("X-UIDL: %ld%s", msg_num, nl);
991 for (i = 0; i < 256; ++i) {
992 if (TheMessage->cm_fields[i]) {
993 mptr = TheMessage->cm_fields[i];
997 } else if (i == 'P') {
998 omprintf("Path: %s%s", mptr, nl);
999 for (a = 0; a < strlen(mptr); ++a) {
1000 if (mptr[a] == '!') {
1001 strcpy(mptr, &mptr[a + 1]);
1005 strcpy(suser, mptr);
1006 } else if (i == 'U')
1007 omprintf("Subject: %s%s", mptr, nl);
1011 strcpy(lnode, mptr);
1013 omprintf("X-Citadel-Room: %s%s",
1016 strcpy(snode, mptr);
1018 omprintf("To: %s%s", mptr, nl);
1019 else if (i == 'T') {
1021 omprintf("Date: %s", asctime(localtime(&xtime)));
1027 if (mode == MT_RFC822) {
1028 if (!strcasecmp(snode, NODENAME)) {
1029 strcpy(snode, FQDN);
1031 omprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
1032 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1033 omprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1034 omprintf("Organization: %s%s", lnode, nl);
1037 /* end header processing loop ... at this point, we're in the text */
1039 mptr = TheMessage->cm_fields['M'];
1041 /* Tell the client about the MIME parts in this message */
1042 if (TheMessage->cm_format_type == FMT_RFC822) {
1043 if (mode == MT_CITADEL) {
1044 mime_parser(mptr, NULL, *list_this_part);
1046 else if (mode == MT_MIME) { /* list parts only */
1047 mime_parser(mptr, NULL, *list_this_part);
1048 if (do_proto) cprintf("000\n");
1049 CtdlFreeMessage(TheMessage);
1052 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1053 /* FIX ... we have to put some code in here to avoid
1054 * printing duplicate header information when both
1055 * Citadel and RFC822 headers exist. Preference should
1056 * probably be given to the RFC822 headers.
1058 while (ch=*(mptr++), ch!=0) {
1060 else if (ch==10) omprintf("%s", nl);
1061 else omprintf("%c", ch);
1063 if (do_proto) cprintf("000\n");
1064 CtdlFreeMessage(TheMessage);
1070 if (do_proto) cprintf("000\n");
1071 CtdlFreeMessage(TheMessage);
1075 /* signify start of msg text */
1076 if (mode == MT_CITADEL)
1077 if (do_proto) cprintf("text\n");
1078 if (mode == MT_RFC822) {
1082 /* If the format type on disk is 1 (fixed-format), then we want
1083 * everything to be output completely literally ... regardless of
1084 * what message transfer format is in use.
1086 if (TheMessage->cm_format_type == FMT_FIXED) {
1088 while (ch = *mptr++, ch > 0) {
1091 if ((ch == 10) || (strlen(buf) > 250)) {
1092 omprintf("%s%s", buf, nl);
1095 buf[strlen(buf) + 1] = 0;
1096 buf[strlen(buf)] = ch;
1099 if (strlen(buf) > 0)
1100 omprintf("%s%s", buf, nl);
1103 /* If the message on disk is format 0 (Citadel vari-format), we
1104 * output using the formatter at 80 columns. This is the final output
1105 * form if the transfer format is RFC822, but if the transfer format
1106 * is Citadel proprietary, it'll still work, because the indentation
1107 * for new paragraphs is correct and the client will reformat the
1108 * message to the reader's screen width.
1110 if (TheMessage->cm_format_type == FMT_CITADEL) {
1114 /* If the message on disk is format 4 (MIME), we've gotta hand it
1115 * off to the MIME parser. The client has already been told that
1116 * this message is format 1 (fixed format), so the callback function
1117 * we use will display those parts as-is.
1119 if (TheMessage->cm_format_type == FMT_RFC822) {
1120 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1121 memset(ma, 0, sizeof(struct ma_info));
1122 mime_parser(mptr, NULL, *fixed_output);
1125 /* now we're done */
1126 if (do_proto) cprintf("000\n");
1127 CtdlFreeMessage(TheMessage);
1134 * display a message (mode 0 - Citadel proprietary)
1136 void cmd_msg0(char *cmdbuf)
1139 int headers_only = 0;
1141 msgid = extract_long(cmdbuf, 0);
1142 headers_only = extract_int(cmdbuf, 1);
1144 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, NULL, -1, 0);
1150 * display a message (mode 2 - RFC822)
1152 void cmd_msg2(char *cmdbuf)
1155 int headers_only = 0;
1157 msgid = extract_long(cmdbuf, 0);
1158 headers_only = extract_int(cmdbuf, 1);
1160 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, NULL, -1, 1);
1166 * display a message (mode 3 - IGnet raw format - internal programs only)
1168 void cmd_msg3(char *cmdbuf)
1171 struct CtdlMessage *msg;
1174 if (CC->internal_pgm == 0) {
1175 cprintf("%d This command is for internal programs only.\n",
1180 msgnum = extract_long(cmdbuf, 0);
1181 msg = CtdlFetchMessage(msgnum);
1183 cprintf("%d Message %ld not found.\n",
1188 serialize_message(&smr, msg);
1189 CtdlFreeMessage(msg);
1192 cprintf("%d Unable to serialize message\n",
1193 ERROR+INTERNAL_ERROR);
1197 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1198 client_write(smr.ser, smr.len);
1205 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1207 void cmd_msg4(char *cmdbuf)
1211 msgid = extract_long(cmdbuf, 0);
1212 CtdlOutputMsg(msgid, MT_MIME, 0, 1, NULL, -1, 0);
1216 * Open a component of a MIME message as a download file
1218 void cmd_opna(char *cmdbuf)
1222 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1224 msgid = extract_long(cmdbuf, 0);
1225 extract(desired_section, cmdbuf, 1);
1227 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, NULL, -1, 1);
1232 * Save a message pointer into a specified room
1233 * (Returns 0 for success, nonzero for failure)
1234 * roomname may be NULL to use the current room
1236 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1238 char hold_rm[ROOMNAMELEN];
1239 struct cdbdata *cdbfr;
1242 long highest_msg = 0L;
1243 struct CtdlMessage *msg = NULL;
1245 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1246 roomname, msgid, flags);
1248 strcpy(hold_rm, CC->quickroom.QRname);
1250 /* We may need to check to see if this message is real */
1251 if ( (flags & SM_VERIFY_GOODNESS)
1252 || (flags & SM_DO_REPL_CHECK)
1254 msg = CtdlFetchMessage(msgid);
1255 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1258 /* Perform replication checks if necessary */
1259 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1261 if (getroom(&CC->quickroom,
1262 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1264 lprintf(9, "No such room <%s>\n", roomname);
1265 if (msg != NULL) CtdlFreeMessage(msg);
1266 return(ERROR + ROOM_NOT_FOUND);
1269 if (ReplicationChecks(msg) != 0) {
1270 getroom(&CC->quickroom, hold_rm);
1271 if (msg != NULL) CtdlFreeMessage(msg);
1272 lprintf(9, "Did replication, and newer exists\n");
1277 /* Now the regular stuff */
1278 if (lgetroom(&CC->quickroom,
1279 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1281 lprintf(9, "No such room <%s>\n", roomname);
1282 if (msg != NULL) CtdlFreeMessage(msg);
1283 return(ERROR + ROOM_NOT_FOUND);
1286 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1287 if (cdbfr == NULL) {
1291 msglist = mallok(cdbfr->len);
1292 if (msglist == NULL)
1293 lprintf(3, "ERROR malloc msglist!\n");
1294 num_msgs = cdbfr->len / sizeof(long);
1295 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1300 /* Make sure the message doesn't already exist in this room. It
1301 * is absolutely taboo to have more than one reference to the same
1302 * message in a room.
1304 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1305 if (msglist[i] == msgid) {
1306 lputroom(&CC->quickroom); /* unlock the room */
1307 getroom(&CC->quickroom, hold_rm);
1308 if (msg != NULL) CtdlFreeMessage(msg);
1309 return(ERROR + ALREADY_EXISTS);
1313 /* Now add the new message */
1315 msglist = reallok(msglist,
1316 (num_msgs * sizeof(long)));
1318 if (msglist == NULL) {
1319 lprintf(3, "ERROR: can't realloc message list!\n");
1321 msglist[num_msgs - 1] = msgid;
1323 /* Sort the message list, so all the msgid's are in order */
1324 num_msgs = sort_msglist(msglist, num_msgs);
1326 /* Determine the highest message number */
1327 highest_msg = msglist[num_msgs - 1];
1329 /* Write it back to disk. */
1330 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1331 msglist, num_msgs * sizeof(long));
1333 /* Free up the memory we used. */
1336 /* Update the highest-message pointer and unlock the room. */
1337 CC->quickroom.QRhighest = highest_msg;
1338 lputroom(&CC->quickroom);
1339 getroom(&CC->quickroom, hold_rm);
1341 /* Bump the reference count for this message. */
1342 if ((flags & SM_DONT_BUMP_REF)==0) {
1343 AdjRefCount(msgid, +1);
1346 /* Return success. */
1347 if (msg != NULL) CtdlFreeMessage(msg);
1354 * Message base operation to send a message to the master file
1355 * (returns new message number)
1357 * This is the back end for CtdlSaveMsg() and should not be directly
1358 * called by server-side modules.
1361 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1362 int generate_id, /* generate 'I' field? */
1363 FILE *save_a_copy) /* save a copy to disk? */
1370 /* Get a new message number */
1371 newmsgid = get_new_message_number();
1372 sprintf(msgidbuf, "%ld", newmsgid);
1375 msg->cm_fields['I'] = strdoop(msgidbuf);
1378 serialize_message(&smr, msg);
1381 cprintf("%d Unable to serialize message\n",
1382 ERROR+INTERNAL_ERROR);
1386 /* Write our little bundle of joy into the message base */
1387 begin_critical_section(S_MSGMAIN);
1388 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1389 smr.ser, smr.len) < 0) {
1390 lprintf(2, "Can't store message\n");
1395 end_critical_section(S_MSGMAIN);
1397 /* If the caller specified that a copy should be saved to a particular
1398 * file handle, do that now too.
1400 if (save_a_copy != NULL) {
1401 fwrite(smr.ser, smr.len, 1, save_a_copy);
1404 /* Free the memory we used for the serialized message */
1407 /* Return the *local* message ID to the caller
1408 * (even if we're storing an incoming network message)
1416 * Serialize a struct CtdlMessage into the format used on disk and network.
1418 * This function loads up a "struct ser_ret" (defined in server.h) which
1419 * contains the length of the serialized message and a pointer to the
1420 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1422 void serialize_message(struct ser_ret *ret, /* return values */
1423 struct CtdlMessage *msg) /* unserialized msg */
1427 static char *forder = FORDER;
1429 if (is_valid_message(msg) == 0) return; /* self check */
1432 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1433 ret->len = ret->len +
1434 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1436 lprintf(9, "calling malloc\n");
1437 ret->ser = mallok(ret->len);
1438 if (ret->ser == NULL) {
1444 ret->ser[1] = msg->cm_anon_type;
1445 ret->ser[2] = msg->cm_format_type;
1448 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1449 ret->ser[wlen++] = (char)forder[i];
1450 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1451 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1453 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1462 * Back end for the ReplicationChecks() function
1464 void check_repl(long msgnum) {
1465 struct CtdlMessage *msg;
1466 time_t timestamp = (-1L);
1468 lprintf(9, "check_repl() found message %ld\n", msgnum);
1469 msg = CtdlFetchMessage(msgnum);
1470 if (msg == NULL) return;
1471 if (msg->cm_fields['T'] != NULL) {
1472 timestamp = atol(msg->cm_fields['T']);
1474 CtdlFreeMessage(msg);
1476 if (timestamp > msg_repl->highest) {
1477 msg_repl->highest = timestamp; /* newer! */
1478 lprintf(9, "newer!\n");
1481 lprintf(9, "older!\n");
1483 /* Existing isn't newer? Then delete the old one(s). */
1484 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1489 * Check to see if any messages already exist which carry the same Extended ID
1493 * -> With older timestamps: delete them and return 0. Message will be saved.
1494 * -> With newer timestamps: return 1. Message save will be aborted.
1496 int ReplicationChecks(struct CtdlMessage *msg) {
1497 struct CtdlMessage *template;
1500 lprintf(9, "ReplicationChecks() started\n");
1501 /* No extended id? Don't do anything. */
1502 if (msg->cm_fields['E'] == NULL) return 0;
1503 if (strlen(msg->cm_fields['E']) == 0) return 0;
1504 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1506 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1507 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1508 msg_repl->highest = atol(msg->cm_fields['T']);
1510 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1511 memset(template, 0, sizeof(struct CtdlMessage));
1512 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1514 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1516 /* If a newer message exists with the same Extended ID, abort
1519 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1523 CtdlFreeMessage(template);
1524 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1532 * Save a message to disk
1534 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1535 char *rec, /* Recipient (mail) */
1536 char *force, /* force a particular room? */
1537 int mailtype, /* local or remote type */
1538 int generate_id) /* 1 = generate 'I' field */
1541 char hold_rm[ROOMNAMELEN];
1542 char actual_rm[ROOMNAMELEN];
1543 char force_room[ROOMNAMELEN];
1544 char content_type[256]; /* We have to learn this */
1545 char recipient[256];
1548 struct usersupp userbuf;
1550 struct SuppMsgInfo smi;
1551 FILE *network_fp = NULL;
1552 static int seqnum = 1;
1553 struct CtdlMessage *imsg;
1556 lprintf(9, "CtdlSaveMsg() called\n");
1557 if (is_valid_message(msg) == 0) return(-1); /* self check */
1559 /* If this message has no timestamp, we take the liberty of
1560 * giving it one, right now.
1562 if (msg->cm_fields['T'] == NULL) {
1563 lprintf(9, "Generating timestamp\n");
1564 sprintf(aaa, "%ld", time(NULL));
1565 msg->cm_fields['T'] = strdoop(aaa);
1568 /* If this message has no path, we generate one.
1570 if (msg->cm_fields['P'] == NULL) {
1571 lprintf(9, "Generating path\n");
1572 if (msg->cm_fields['A'] != NULL) {
1573 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1574 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1575 if (isspace(msg->cm_fields['P'][a])) {
1576 msg->cm_fields['P'][a] = ' ';
1581 msg->cm_fields['P'] = strdoop("unknown");
1585 strcpy(force_room, force);
1587 /* Strip non-printable characters out of the recipient name */
1588 strcpy(recipient, rec);
1589 for (a = 0; a < strlen(recipient); ++a)
1590 if (!isprint(recipient[a]))
1591 strcpy(&recipient[a], &recipient[a + 1]);
1593 /* Learn about what's inside, because it's what's inside that counts */
1594 lprintf(9, "Learning what's inside\n");
1595 if (msg->cm_fields['M'] == NULL) {
1596 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1599 switch (msg->cm_format_type) {
1601 strcpy(content_type, "text/x-citadel-variformat");
1604 strcpy(content_type, "text/plain");
1607 strcpy(content_type, "text/plain");
1608 /* advance past header fields */
1609 mptr = msg->cm_fields['M'];
1612 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1613 safestrncpy(content_type, mptr,
1614 sizeof(content_type));
1615 strcpy(content_type, &content_type[14]);
1616 for (a = 0; a < strlen(content_type); ++a)
1617 if ((content_type[a] == ';')
1618 || (content_type[a] == ' ')
1619 || (content_type[a] == 13)
1620 || (content_type[a] == 10))
1621 content_type[a] = 0;
1628 /* Goto the correct room */
1629 lprintf(9, "Switching rooms\n");
1630 strcpy(hold_rm, CC->quickroom.QRname);
1631 strcpy(actual_rm, CC->quickroom.QRname);
1633 /* If the user is a twit, move to the twit room for posting */
1634 lprintf(9, "Handling twit stuff\n");
1636 if (CC->usersupp.axlevel == 2) {
1637 strcpy(hold_rm, actual_rm);
1638 strcpy(actual_rm, config.c_twitroom);
1642 /* ...or if this message is destined for Aide> then go there. */
1643 if (strlen(force_room) > 0) {
1644 strcpy(actual_rm, force_room);
1647 lprintf(9, "Possibly relocating\n");
1648 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1649 getroom(&CC->quickroom, actual_rm);
1653 * If this message has no O (room) field, generate one.
1655 if (msg->cm_fields['O'] == NULL) {
1656 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1659 /* Perform "before save" hooks (aborting if any return nonzero) */
1660 lprintf(9, "Performing before-save hooks\n");
1661 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1663 /* If this message has an Extended ID, perform replication checks */
1664 lprintf(9, "Performing replication checks\n");
1665 if (ReplicationChecks(msg) > 0) return(-1);
1667 /* Network mail - send a copy to the network program. */
1668 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1669 lprintf(9, "Sending network spool\n");
1670 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1671 (long) getpid(), CC->cs_pid, ++seqnum);
1672 lprintf(9, "Saving a copy to %s\n", aaa);
1673 network_fp = fopen(aaa, "ab+");
1674 if (network_fp == NULL)
1675 lprintf(2, "ERROR: %s\n", strerror(errno));
1678 /* Save it to disk */
1679 lprintf(9, "Saving to disk\n");
1680 newmsgid = send_message(msg, generate_id, network_fp);
1681 if (network_fp != NULL) {
1683 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1686 if (newmsgid <= 0L) return(-1);
1688 /* Write a supplemental message info record. This doesn't have to
1689 * be a critical section because nobody else knows about this message
1692 lprintf(9, "Creating SuppMsgInfo record\n");
1693 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1694 smi.smi_msgnum = newmsgid;
1695 smi.smi_refcount = 0;
1696 safestrncpy(smi.smi_content_type, content_type, 64);
1697 PutSuppMsgInfo(&smi);
1699 /* Now figure out where to store the pointers */
1700 lprintf(9, "Storing pointers\n");
1702 /* If this is being done by the networker delivering a private
1703 * message, we want to BYPASS saving the sender's copy (because there
1704 * is no local sender; it would otherwise go to the Trashcan).
1706 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1707 CtdlSaveMsgPointerInRoom(actual_rm, 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);
1732 /* Perform "after save" hooks */
1733 lprintf(9, "Performing after-save hooks\n");
1734 PerformMessageHooks(msg, EVT_AFTERSAVE);
1737 lprintf(9, "Returning to original room\n");
1738 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1739 getroom(&CC->quickroom, hold_rm);
1741 /* For internet mail, generate delivery instructions
1742 * (Yes, this is recursive! Deal with it!)
1744 if (mailtype == MES_INTERNET) {
1745 lprintf(9, "Generating delivery instructions\n");
1746 instr = mallok(2048);
1748 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1750 SPOOLMIME, newmsgid, time(NULL), recipient );
1752 imsg = mallok(sizeof(struct CtdlMessage));
1753 memset(imsg, 0, sizeof(struct CtdlMessage));
1754 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1755 imsg->cm_anon_type = MES_NORMAL;
1756 imsg->cm_format_type = FMT_RFC822;
1757 imsg->cm_fields['A'] = strdoop("Citadel");
1758 imsg->cm_fields['M'] = instr;
1759 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1760 CtdlFreeMessage(imsg);
1769 * Convenience function for generating small administrative messages.
1771 void quickie_message(char *from, char *to, char *room, char *text)
1773 struct CtdlMessage *msg;
1775 msg = mallok(sizeof(struct CtdlMessage));
1776 memset(msg, 0, sizeof(struct CtdlMessage));
1777 msg->cm_magic = CTDLMESSAGE_MAGIC;
1778 msg->cm_anon_type = MES_NORMAL;
1779 msg->cm_format_type = 0;
1780 msg->cm_fields['A'] = strdoop(from);
1781 msg->cm_fields['O'] = strdoop(room);
1782 msg->cm_fields['N'] = strdoop(NODENAME);
1784 msg->cm_fields['R'] = strdoop(to);
1785 msg->cm_fields['M'] = strdoop(text);
1787 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1788 CtdlFreeMessage(msg);
1789 syslog(LOG_NOTICE, text);
1795 * Back end function used by make_message() and similar functions
1797 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1798 size_t maxlen, /* maximum message length */
1799 char *exist /* if non-null, append to it;
1800 exist is ALWAYS freed */
1803 size_t message_len = 0;
1804 size_t buffer_len = 0;
1808 if (exist == NULL) {
1812 m = reallok(exist, strlen(exist) + 4096);
1813 if (m == NULL) phree(exist);
1816 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1823 /* read in the lines of message text one by one */
1825 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1827 /* augment the buffer if we have to */
1828 if ((message_len + strlen(buf) + 2) > buffer_len) {
1829 lprintf(9, "realloking\n");
1830 ptr = reallok(m, (buffer_len * 2) );
1831 if (ptr == NULL) { /* flush if can't allocate */
1832 while ( (client_gets(buf)>0) &&
1833 strcmp(buf, terminator)) ;;
1836 buffer_len = (buffer_len * 2);
1839 lprintf(9, "buffer_len is %d\n", buffer_len);
1843 if (append == NULL) append = m;
1844 while (strlen(append) > 0) ++append;
1845 strcpy(append, buf);
1846 strcat(append, "\n");
1847 message_len = message_len + strlen(buf) + 1;
1849 /* if we've hit the max msg length, flush the rest */
1850 if (message_len >= maxlen) {
1851 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1862 * Build a binary message to be saved on disk.
1865 struct CtdlMessage *make_message(
1866 struct usersupp *author, /* author's usersupp structure */
1867 char *recipient, /* NULL if it's not mail */
1868 char *room, /* room where it's going */
1869 int type, /* see MES_ types in header file */
1870 int net_type, /* see MES_ types in header file */
1871 int format_type, /* local or remote (see citadel.h) */
1872 char *fake_name) /* who we're masquerading as */
1878 struct CtdlMessage *msg;
1880 msg = mallok(sizeof(struct CtdlMessage));
1881 memset(msg, 0, sizeof(struct CtdlMessage));
1882 msg->cm_magic = CTDLMESSAGE_MAGIC;
1883 msg->cm_anon_type = type;
1884 msg->cm_format_type = format_type;
1886 /* Don't confuse the poor folks if it's not routed mail. */
1887 strcpy(dest_node, "");
1889 /* If net_type is MES_BINARY, split out the destination node. */
1890 if (net_type == MES_BINARY) {
1891 strcpy(dest_node, NODENAME);
1892 for (a = 0; a < strlen(recipient); ++a) {
1893 if (recipient[a] == '@') {
1895 strcpy(dest_node, &recipient[a + 1]);
1900 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1901 if (net_type == MES_INTERNET) {
1902 strcpy(dest_node, "internet");
1905 while (isspace(recipient[strlen(recipient) - 1]))
1906 recipient[strlen(recipient) - 1] = 0;
1908 sprintf(buf, "cit%ld", author->usernum); /* Path */
1909 msg->cm_fields['P'] = strdoop(buf);
1911 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1912 msg->cm_fields['T'] = strdoop(buf);
1914 if (fake_name[0]) /* author */
1915 msg->cm_fields['A'] = strdoop(fake_name);
1917 msg->cm_fields['A'] = strdoop(author->fullname);
1919 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1920 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1922 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1924 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1925 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1927 if (recipient[0] != 0)
1928 msg->cm_fields['R'] = strdoop(recipient);
1929 if (dest_node[0] != 0)
1930 msg->cm_fields['D'] = strdoop(dest_node);
1933 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1934 config.c_maxmsglen, NULL);
1945 * message entry - mode 0 (normal)
1947 void cmd_ent0(char *entargs)
1950 char recipient[256];
1952 int format_type = 0;
1953 char newusername[256];
1954 struct CtdlMessage *msg;
1958 struct usersupp tempUS;
1961 post = extract_int(entargs, 0);
1962 extract(recipient, entargs, 1);
1963 anon_flag = extract_int(entargs, 2);
1964 format_type = extract_int(entargs, 3);
1966 /* first check to make sure the request is valid. */
1968 if (!(CC->logged_in)) {
1969 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1972 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1973 cprintf("%d Need to be validated to enter ",
1974 ERROR + HIGHER_ACCESS_REQUIRED);
1975 cprintf("(except in %s> to sysop)\n", MAILROOM);
1978 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1979 cprintf("%d Need net privileges to enter here.\n",
1980 ERROR + HIGHER_ACCESS_REQUIRED);
1983 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1984 cprintf("%d Sorry, this is a read-only room.\n",
1985 ERROR + HIGHER_ACCESS_REQUIRED);
1992 if (CC->usersupp.axlevel < 6) {
1993 cprintf("%d You don't have permission to masquerade.\n",
1994 ERROR + HIGHER_ACCESS_REQUIRED);
1997 extract(newusername, entargs, 4);
1998 memset(CC->fake_postname, 0, 32);
1999 strcpy(CC->fake_postname, newusername);
2000 cprintf("%d Ok\n", OK);
2003 CC->cs_flags |= CS_POSTING;
2006 if (CC->quickroom.QRflags & QR_MAILBOX) {
2007 if (CC->usersupp.axlevel >= 2) {
2008 strcpy(buf, recipient);
2010 strcpy(buf, "sysop");
2011 e = alias(buf); /* alias and mail type */
2012 if ((buf[0] == 0) || (e == MES_ERROR)) {
2013 cprintf("%d Unknown address - cannot send message.\n",
2014 ERROR + NO_SUCH_USER);
2017 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2018 cprintf("%d Net privileges required for network mail.\n",
2019 ERROR + HIGHER_ACCESS_REQUIRED);
2022 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2023 && ((CC->usersupp.flags & US_INTERNET) == 0)
2024 && (!CC->internal_pgm)) {
2025 cprintf("%d You don't have access to Internet mail.\n",
2026 ERROR + HIGHER_ACCESS_REQUIRED);
2029 if (!strcasecmp(buf, "sysop")) {
2034 goto SKFALL; /* don't search local file */
2035 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2036 cprintf("%d Can't send mail to yourself!\n",
2037 ERROR + NO_SUCH_USER);
2040 /* Check to make sure the user exists; also get the correct
2041 * upper/lower casing of the name.
2043 a = getuser(&tempUS, buf);
2045 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2048 strcpy(buf, tempUS.fullname);
2051 SKFALL: b = MES_NORMAL;
2052 if (CC->quickroom.QRflags & QR_ANONONLY)
2054 if (CC->quickroom.QRflags & QR_ANONOPT) {
2058 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2061 /* If we're only checking the validity of the request, return
2062 * success without creating the message.
2065 cprintf("%d %s\n", OK, buf);
2069 cprintf("%d send message\n", SEND_LISTING);
2071 /* Read in the message from the client. */
2072 if (CC->fake_postname[0])
2073 msg = make_message(&CC->usersupp, buf,
2074 CC->quickroom.QRname, b, e, format_type,
2076 else if (CC->fake_username[0])
2077 msg = make_message(&CC->usersupp, buf,
2078 CC->quickroom.QRname, b, e, format_type,
2081 msg = make_message(&CC->usersupp, buf,
2082 CC->quickroom.QRname, b, e, format_type, "");
2085 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2086 CtdlFreeMessage(msg);
2087 CC->fake_postname[0] = '\0';
2094 * message entry - mode 3 (raw)
2096 void cmd_ent3(char *entargs)
2102 unsigned char ch, which_field;
2103 struct usersupp tempUS;
2105 struct CtdlMessage *msg;
2108 if (CC->internal_pgm == 0) {
2109 cprintf("%d This command is for internal programs only.\n",
2114 /* See if there's a recipient, but make sure it's a real one */
2115 extract(recp, entargs, 1);
2116 for (a = 0; a < strlen(recp); ++a)
2117 if (!isprint(recp[a]))
2118 strcpy(&recp[a], &recp[a + 1]);
2119 while (isspace(recp[0]))
2120 strcpy(recp, &recp[1]);
2121 while (isspace(recp[strlen(recp) - 1]))
2122 recp[strlen(recp) - 1] = 0;
2124 /* If we're in Mail, check the recipient */
2125 if (strlen(recp) > 0) {
2126 e = alias(recp); /* alias and mail type */
2127 if ((recp[0] == 0) || (e == MES_ERROR)) {
2128 cprintf("%d Unknown address - cannot send message.\n",
2129 ERROR + NO_SUCH_USER);
2132 if (e == MES_LOCAL) {
2133 a = getuser(&tempUS, recp);
2135 cprintf("%d No such user.\n",
2136 ERROR + NO_SUCH_USER);
2142 /* At this point, message has been approved. */
2143 if (extract_int(entargs, 0) == 0) {
2144 cprintf("%d OK to send\n", OK);
2148 msglen = extract_long(entargs, 2);
2149 msg = mallok(sizeof(struct CtdlMessage));
2151 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2155 memset(msg, 0, sizeof(struct CtdlMessage));
2156 tempbuf = mallok(msglen);
2157 if (tempbuf == NULL) {
2158 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2163 cprintf("%d %ld\n", SEND_BINARY, msglen);
2165 client_read(&ch, 1); /* 0xFF magic number */
2166 msg->cm_magic = CTDLMESSAGE_MAGIC;
2167 client_read(&ch, 1); /* anon type */
2168 msg->cm_anon_type = ch;
2169 client_read(&ch, 1); /* format type */
2170 msg->cm_format_type = ch;
2171 msglen = msglen - 3;
2173 while (msglen > 0) {
2174 client_read(&which_field, 1);
2175 if (!isalpha(which_field)) valid_msg = 0;
2179 client_read(&ch, 1);
2181 a = strlen(tempbuf);
2184 } while ( (ch != 0) && (msglen > 0) );
2186 msg->cm_fields[which_field] = strdoop(tempbuf);
2189 msg->cm_flags = CM_SKIP_HOOKS;
2190 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2191 CtdlFreeMessage(msg);
2197 * API function to delete messages which match a set of criteria
2198 * (returns the actual number of messages deleted)
2200 int CtdlDeleteMessages(char *room_name, /* which room */
2201 long dmsgnum, /* or "0" for any */
2202 char *content_type /* or NULL for any */
2206 struct quickroom qrbuf;
2207 struct cdbdata *cdbfr;
2208 long *msglist = NULL;
2211 int num_deleted = 0;
2213 struct SuppMsgInfo smi;
2215 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2216 room_name, dmsgnum, content_type);
2218 /* get room record, obtaining a lock... */
2219 if (lgetroom(&qrbuf, room_name) != 0) {
2220 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2222 return (0); /* room not found */
2224 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2226 if (cdbfr != NULL) {
2227 msglist = mallok(cdbfr->len);
2228 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2229 num_msgs = cdbfr->len / sizeof(long);
2233 for (i = 0; i < num_msgs; ++i) {
2236 /* Set/clear a bit for each criterion */
2238 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2239 delete_this |= 0x01;
2241 if (content_type == NULL) {
2242 delete_this |= 0x02;
2244 GetSuppMsgInfo(&smi, msglist[i]);
2245 if (!strcasecmp(smi.smi_content_type,
2247 delete_this |= 0x02;
2251 /* Delete message only if all bits are set */
2252 if (delete_this == 0x03) {
2253 AdjRefCount(msglist[i], -1);
2259 num_msgs = sort_msglist(msglist, num_msgs);
2260 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2261 msglist, (num_msgs * sizeof(long)));
2263 qrbuf.QRhighest = msglist[num_msgs - 1];
2267 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2268 return (num_deleted);
2274 * Delete message from current room
2276 void cmd_dele(char *delstr)
2281 getuser(&CC->usersupp, CC->curr_user);
2282 if ((CC->usersupp.axlevel < 6)
2283 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2284 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2285 && (!(CC->internal_pgm))) {
2286 cprintf("%d Higher access required.\n",
2287 ERROR + HIGHER_ACCESS_REQUIRED);
2290 delnum = extract_long(delstr, 0);
2292 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2295 cprintf("%d %d message%s deleted.\n", OK,
2296 num_deleted, ((num_deleted != 1) ? "s" : ""));
2298 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2304 * move or copy a message to another room
2306 void cmd_move(char *args)
2310 struct quickroom qtemp;
2314 num = extract_long(args, 0);
2315 extract(targ, args, 1);
2316 targ[ROOMNAMELEN - 1] = 0;
2317 is_copy = extract_int(args, 2);
2319 getuser(&CC->usersupp, CC->curr_user);
2320 if ((CC->usersupp.axlevel < 6)
2321 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2322 cprintf("%d Higher access required.\n",
2323 ERROR + HIGHER_ACCESS_REQUIRED);
2327 if (getroom(&qtemp, targ) != 0) {
2328 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2332 err = CtdlSaveMsgPointerInRoom(targ, num,
2333 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2335 cprintf("%d Cannot store message in %s: error %d\n",
2340 /* Now delete the message from the source room,
2341 * if this is a 'move' rather than a 'copy' operation.
2343 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2345 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2351 * GetSuppMsgInfo() - Get the supplementary record for a message
2353 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2356 struct cdbdata *cdbsmi;
2359 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2360 smibuf->smi_msgnum = msgnum;
2361 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2363 /* Use the negative of the message number for its supp record index */
2364 TheIndex = (0L - msgnum);
2366 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2367 if (cdbsmi == NULL) {
2368 return; /* record not found; go with defaults */
2370 memcpy(smibuf, cdbsmi->ptr,
2371 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2372 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2379 * PutSuppMsgInfo() - (re)write supplementary record for a message
2381 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2385 /* Use the negative of the message number for its supp record index */
2386 TheIndex = (0L - smibuf->smi_msgnum);
2388 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2389 smibuf->smi_msgnum, smibuf->smi_refcount);
2391 cdb_store(CDB_MSGMAIN,
2392 &TheIndex, sizeof(long),
2393 smibuf, sizeof(struct SuppMsgInfo));
2398 * AdjRefCount - change the reference count for a message;
2399 * delete the message if it reaches zero
2401 void AdjRefCount(long msgnum, int incr)
2404 struct SuppMsgInfo smi;
2407 /* This is a *tight* critical section; please keep it that way, as
2408 * it may get called while nested in other critical sections.
2409 * Complicating this any further will surely cause deadlock!
2411 begin_critical_section(S_SUPPMSGMAIN);
2412 GetSuppMsgInfo(&smi, msgnum);
2413 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2414 msgnum, smi.smi_refcount);
2415 smi.smi_refcount += incr;
2416 PutSuppMsgInfo(&smi);
2417 end_critical_section(S_SUPPMSGMAIN);
2418 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2419 msgnum, smi.smi_refcount);
2421 /* If the reference count is now zero, delete the message
2422 * (and its supplementary record as well).
2424 if (smi.smi_refcount == 0) {
2425 lprintf(9, "Deleting message <%ld>\n", msgnum);
2427 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2428 delnum = (0L - msgnum);
2429 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2434 * Write a generic object to this room
2436 * Note: this could be much more efficient. Right now we use two temporary
2437 * files, and still pull the message into memory as with all others.
2439 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2440 char *content_type, /* MIME type of this object */
2441 char *tempfilename, /* Where to fetch it from */
2442 struct usersupp *is_mailbox, /* Mailbox room? */
2443 int is_binary, /* Is encoding necessary? */
2444 int is_unique, /* Del others of this type? */
2445 unsigned int flags /* Internal save flags */
2450 char filename[PATH_MAX];
2453 struct quickroom qrbuf;
2454 char roomname[ROOMNAMELEN];
2455 struct CtdlMessage *msg;
2458 if (is_mailbox != NULL)
2459 MailboxName(roomname, is_mailbox, req_room);
2461 safestrncpy(roomname, req_room, sizeof(roomname));
2462 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2464 strcpy(filename, tmpnam(NULL));
2465 fp = fopen(filename, "w");
2469 tempfp = fopen(tempfilename, "r");
2470 if (tempfp == NULL) {
2476 fprintf(fp, "Content-type: %s\n", content_type);
2477 lprintf(9, "Content-type: %s\n", content_type);
2479 if (is_binary == 0) {
2480 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2481 while (ch = getc(tempfp), ch > 0)
2487 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2490 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2491 tempfilename, filename);
2495 lprintf(9, "Allocating\n");
2496 msg = mallok(sizeof(struct CtdlMessage));
2497 memset(msg, 0, sizeof(struct CtdlMessage));
2498 msg->cm_magic = CTDLMESSAGE_MAGIC;
2499 msg->cm_anon_type = MES_NORMAL;
2500 msg->cm_format_type = 4;
2501 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2502 msg->cm_fields['O'] = strdoop(req_room);
2503 msg->cm_fields['N'] = strdoop(config.c_nodename);
2504 msg->cm_fields['H'] = strdoop(config.c_humannode);
2505 msg->cm_flags = flags;
2507 lprintf(9, "Loading\n");
2508 fp = fopen(filename, "rb");
2509 fseek(fp, 0L, SEEK_END);
2512 msg->cm_fields['M'] = mallok(len);
2513 fread(msg->cm_fields['M'], len, 1, fp);
2517 /* Create the requested room if we have to. */
2518 if (getroom(&qrbuf, roomname) != 0) {
2519 create_room(roomname,
2520 ( (is_mailbox != NULL) ? 4 : 3 ),
2523 /* If the caller specified this object as unique, delete all
2524 * other objects of this type that are currently in the room.
2527 lprintf(9, "Deleted %d other msgs of this type\n",
2528 CtdlDeleteMessages(roomname, 0L, content_type));
2530 /* Now write the data */
2531 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2532 CtdlFreeMessage(msg);
2540 void CtdlGetSysConfigBackend(long msgnum) {
2541 config_msgnum = msgnum;
2545 char *CtdlGetSysConfig(char *sysconfname) {
2546 char hold_rm[ROOMNAMELEN];
2549 struct CtdlMessage *msg;
2552 strcpy(hold_rm, CC->quickroom.QRname);
2553 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2554 getroom(&CC->quickroom, hold_rm);
2559 /* We want the last (and probably only) config in this room */
2560 begin_critical_section(S_CONFIG);
2561 config_msgnum = (-1L);
2562 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2563 CtdlGetSysConfigBackend);
2564 msgnum = config_msgnum;
2565 end_critical_section(S_CONFIG);
2571 msg = CtdlFetchMessage(msgnum);
2573 conf = strdoop(msg->cm_fields['M']);
2574 CtdlFreeMessage(msg);
2581 getroom(&CC->quickroom, hold_rm);
2583 lprintf(9, "eggstracting...\n");
2584 if (conf != NULL) do {
2585 extract_token(buf, conf, 0, '\n');
2586 lprintf(9, "eggstracted <%s>\n", buf);
2587 strcpy(conf, &conf[strlen(buf)+1]);
2588 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2593 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2594 char temp[PATH_MAX];
2597 strcpy(temp, tmpnam(NULL));
2599 fp = fopen(temp, "w");
2600 if (fp == NULL) return;
2601 fprintf(fp, "%s", sysconfdata);
2604 /* this handy API function does all the work for us */
2605 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);