20 #include "sysdep_decls.h"
21 #include "citserver.h"
26 #include "dynloader.h"
28 #include "mime_parser.h"
31 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
32 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
33 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
35 extern struct config config;
39 "", "", "", "", "", "", "", "",
40 "", "", "", "", "", "", "", "",
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
72 * This function is self explanatory.
73 * (What can I say, I'm in a weird mood today...)
75 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
79 for (i = 0; i < strlen(name); ++i)
82 if (isspace(name[i - 1])) {
83 strcpy(&name[i - 1], &name[i]);
86 while (isspace(name[i + 1])) {
87 strcpy(&name[i + 1], &name[i + 2]);
94 * Aliasing for network mail.
95 * (Error messages have been commented out, because this is a server.)
98 { /* process alias and routing info for mail */
101 char aaa[300], bbb[300];
103 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
105 fp = fopen("network/mail.aliases", "r");
107 fp = fopen("/dev/null", "r");
112 while (fgets(aaa, sizeof aaa, fp) != NULL) {
113 while (isspace(name[0]))
114 strcpy(name, &name[1]);
115 aaa[strlen(aaa) - 1] = 0;
117 for (a = 0; a < strlen(aaa); ++a) {
119 strcpy(bbb, &aaa[a + 1]);
123 if (!strcasecmp(name, aaa))
127 lprintf(7, "Mail is being forwarded to %s\n", name);
129 /* determine local or remote type, see citadel.h */
130 for (a = 0; a < strlen(name); ++a)
132 return (MES_INTERNET);
133 for (a = 0; a < strlen(name); ++a)
135 for (b = a; b < strlen(name); ++b)
137 return (MES_INTERNET);
139 for (a = 0; a < strlen(name); ++a)
143 lprintf(7, "Too many @'s in address\n");
147 for (a = 0; a < strlen(name); ++a)
149 strcpy(bbb, &name[a + 1]);
151 strcpy(bbb, &bbb[1]);
152 fp = fopen("network/mail.sysinfo", "r");
156 a = getstring(fp, aaa);
157 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
158 a = getstring(fp, aaa);
159 if (!strncmp(aaa, "use ", 4)) {
160 strcpy(bbb, &aaa[4]);
165 if (!strncmp(aaa, "uum", 3)) {
167 for (a = 0; a < strlen(bbb); ++a) {
173 while (bbb[strlen(bbb) - 1] == '_')
174 bbb[strlen(bbb) - 1] = 0;
175 sprintf(name, &aaa[4], bbb);
176 return (MES_INTERNET);
178 if (!strncmp(aaa, "bin", 3)) {
181 while (aaa[strlen(aaa) - 1] != '@')
182 aaa[strlen(aaa) - 1] = 0;
183 aaa[strlen(aaa) - 1] = 0;
184 while (aaa[strlen(aaa) - 1] == ' ')
185 aaa[strlen(aaa) - 1] = 0;
186 while (bbb[0] != '@')
187 strcpy(bbb, &bbb[1]);
188 strcpy(bbb, &bbb[1]);
189 while (bbb[0] == ' ')
190 strcpy(bbb, &bbb[1]);
191 sprintf(name, "%s @%s", aaa, bbb);
204 fp = fopen("citadel.control", "r");
205 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
211 void simple_listing(long msgnum)
213 cprintf("%ld\n", msgnum);
218 /* Determine if a given message matches the fields in a message template.
219 * Return 0 for a successful match.
221 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
224 /* If there aren't any fields in the template, all messages will
227 if (template == NULL) return(0);
229 /* Null messages are bogus. */
230 if (msg == NULL) return(1);
232 for (i='A'; i<='Z'; ++i) {
233 if (template->cm_fields[i] != NULL) {
234 if (msg->cm_fields[i] == NULL) {
237 if (strcasecmp(msg->cm_fields[i],
238 template->cm_fields[i])) return 1;
242 /* All compares succeeded: we have a match! */
250 * API function to perform an operation for each qualifying message in the
253 void CtdlForEachMessage(int mode, long ref,
255 struct CtdlMessage *compare,
256 void (*CallBack) (long msgnum))
261 struct cdbdata *cdbfr;
262 long *msglist = NULL;
265 struct SuppMsgInfo smi;
266 struct CtdlMessage *msg;
268 /* Learn about the user and room in question */
270 getuser(&CC->usersupp, CC->curr_user);
271 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
273 /* Load the message list */
274 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
276 msglist = mallok(cdbfr->len);
277 memcpy(msglist, cdbfr->ptr, cdbfr->len);
278 num_msgs = cdbfr->len / sizeof(long);
281 return; /* No messages at all? No further action. */
285 /* If the caller is looking for a specific MIME type, then filter
286 * out all messages which are not of the type requested.
289 if (content_type != NULL)
290 if (strlen(content_type) > 0)
291 for (a = 0; a < num_msgs; ++a) {
292 GetSuppMsgInfo(&smi, msglist[a]);
293 if (strcasecmp(smi.smi_content_type, content_type)) {
298 num_msgs = sort_msglist(msglist, num_msgs);
300 /* If a template was supplied, filter out the messages which
301 * don't match. (This could induce some delays!)
304 if (compare != NULL) {
305 for (a = 0; a < num_msgs; ++a) {
306 msg = CtdlFetchMessage(msglist[a]);
308 if (CtdlMsgCmp(msg, compare)) {
311 CtdlFreeMessage(msg);
319 * Now iterate through the message list, according to the
320 * criteria supplied by the caller.
323 for (a = 0; a < num_msgs; ++a) {
324 thismsg = msglist[a];
329 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
330 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
331 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
332 && (CC->usersupp.flags & US_LASTOLD))
333 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
334 || ((mode == MSGS_FIRST) && (a < ref))
335 || ((mode == MSGS_GT) && (thismsg > ref))
341 phree(msglist); /* Clean up */
347 * cmd_msgs() - get list of message #'s in this room
348 * implements the MSGS server command using CtdlForEachMessage()
350 void cmd_msgs(char *cmdbuf)
359 int with_template = 0;
360 struct CtdlMessage *template = NULL;
362 extract(which, cmdbuf, 0);
363 cm_ref = extract_int(cmdbuf, 1);
364 with_template = extract_int(cmdbuf, 2);
368 if (!strncasecmp(which, "OLD", 3))
370 else if (!strncasecmp(which, "NEW", 3))
372 else if (!strncasecmp(which, "FIRST", 5))
374 else if (!strncasecmp(which, "LAST", 4))
376 else if (!strncasecmp(which, "GT", 2))
379 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
380 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
385 cprintf("%d Send template then receive message list\n",
387 template = (struct CtdlMessage *)
388 mallok(sizeof(struct CtdlMessage));
389 memset(template, 0, sizeof(struct CtdlMessage));
390 while(client_gets(buf), strcmp(buf,"000")) {
391 extract(tfield, buf, 0);
392 extract(tvalue, buf, 1);
393 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
394 if (!strcasecmp(tfield, msgkeys[i])) {
395 template->cm_fields[i] =
402 cprintf("%d Message list...\n", LISTING_FOLLOWS);
405 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
406 if (template != NULL) CtdlFreeMessage(template);
414 * help_subst() - support routine for help file viewer
416 void help_subst(char *strbuf, char *source, char *dest)
421 while (p = pattern2(strbuf, source), (p >= 0)) {
422 strcpy(workbuf, &strbuf[p + strlen(source)]);
423 strcpy(&strbuf[p], dest);
424 strcat(strbuf, workbuf);
429 void do_help_subst(char *buffer)
433 help_subst(buffer, "^nodename", config.c_nodename);
434 help_subst(buffer, "^humannode", config.c_humannode);
435 help_subst(buffer, "^fqdn", config.c_fqdn);
436 help_subst(buffer, "^username", CC->usersupp.fullname);
437 sprintf(buf2, "%ld", CC->usersupp.usernum);
438 help_subst(buffer, "^usernum", buf2);
439 help_subst(buffer, "^sysadm", config.c_sysadm);
440 help_subst(buffer, "^variantname", CITADEL);
441 sprintf(buf2, "%d", config.c_maxsessions);
442 help_subst(buffer, "^maxsessions", buf2);
448 * memfmout() - Citadel text formatter and paginator.
449 * Although the original purpose of this routine was to format
450 * text to the reader's screen width, all we're really using it
451 * for here is to format text out to 80 columns before sending it
452 * to the client. The client software may reformat it again.
455 int width, /* screen width to use */
456 char *mptr, /* where are we going to get our text from? */
457 char subst) /* nonzero if we should do substitutions */
469 c = 1; /* c is the current pos */
472 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
474 buffer[strlen(buffer) + 1] = 0;
475 buffer[strlen(buffer)] = ch;
478 if (buffer[0] == '^')
479 do_help_subst(buffer);
481 buffer[strlen(buffer) + 1] = 0;
483 strcpy(buffer, &buffer[1]);
493 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
495 if (((old == 13) || (old == 10)) && (isspace(real))) {
503 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
504 cprintf("\n%s", aaa);
513 if ((strlen(aaa) + c) > (width - 5)) {
523 if ((ch == 13) || (ch == 10)) {
524 cprintf("%s\n", aaa);
531 FMTEND: cprintf("%s\n", aaa);
537 * Callback function for mime parser that simply lists the part
539 void list_this_part(char *name, char *filename, char *partnum, char *disp,
540 void *content, char *cbtype, size_t length)
543 cprintf("part=%s|%s|%s|%s|%s|%d\n",
544 name, filename, partnum, disp, cbtype, length);
549 * Callback function for mime parser that opens a section for downloading
551 void mime_download(char *name, char *filename, char *partnum, char *disp,
552 void *content, char *cbtype, size_t length)
555 /* Silently go away if there's already a download open... */
556 if (CC->download_fp != NULL)
559 /* ...or if this is not the desired section */
560 if (strcasecmp(desired_section, partnum))
563 CC->download_fp = tmpfile();
564 if (CC->download_fp == NULL)
567 fwrite(content, length, 1, CC->download_fp);
568 fflush(CC->download_fp);
569 rewind(CC->download_fp);
571 OpenCmdResult(filename, cbtype);
577 * Load a message from disk into memory.
578 * This is used by CtdlOutputMsg() and other fetch functions.
580 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
581 * using the CtdlMessageFree() function.
583 struct CtdlMessage *CtdlFetchMessage(long msgnum)
585 struct cdbdata *dmsgtext;
586 struct CtdlMessage *ret = NULL;
589 CIT_UBYTE field_header;
592 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
593 if (dmsgtext == NULL) {
596 mptr = dmsgtext->ptr;
598 /* Parse the three bytes that begin EVERY message on disk.
599 * The first is always 0xFF, the on-disk magic number.
600 * The second is the anonymous/public type byte.
601 * The third is the format type byte (vari, fixed, or MIME).
605 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
609 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
610 memset(ret, 0, sizeof(struct CtdlMessage));
612 ret->cm_magic = CTDLMESSAGE_MAGIC;
613 ret->cm_anon_type = *mptr++; /* Anon type byte */
614 ret->cm_format_type = *mptr++; /* Format type byte */
617 * The rest is zero or more arbitrary fields. Load them in.
618 * We're done when we encounter either a zero-length field or
619 * have just processed the 'M' (message text) field.
622 field_length = strlen(mptr);
623 if (field_length == 0)
625 field_header = *mptr++;
626 ret->cm_fields[field_header] = mallok(field_length);
627 strcpy(ret->cm_fields[field_header], mptr);
629 while (*mptr++ != 0); /* advance to next field */
631 } while ((field_length > 0) && (field_header != 'M'));
635 /* Always make sure there's something in the msg text field */
636 if (ret->cm_fields['M'] == NULL)
637 ret->cm_fields['M'] = strdoop("<no text>\n");
639 /* Perform "before read" hooks (aborting if any return nonzero) */
640 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
641 CtdlFreeMessage(ret);
650 * Returns 1 if the supplied pointer points to a valid Citadel message.
651 * If the pointer is NULL or the magic number check fails, returns 0.
653 int is_valid_message(struct CtdlMessage *msg) {
656 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
657 lprintf(3, "is_valid_message() -- self-check failed\n");
665 * 'Destructor' for struct CtdlMessage
667 void CtdlFreeMessage(struct CtdlMessage *msg)
671 if (is_valid_message(msg) == 0) return;
673 for (i = 0; i < 256; ++i)
674 if (msg->cm_fields[i] != NULL)
675 phree(msg->cm_fields[i]);
677 msg->cm_magic = 0; /* just in case */
683 * Get a message off disk. (return value is the message's timestamp)
686 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
687 int mode, /* how would you like that message? */
688 int headers_only, /* eschew the message body? */
689 int do_proto, /* do Citadel protocol responses? */
698 char display_name[256];
699 struct CtdlMessage *TheMessage;
702 /* buffers needed for RFC822 translation */
711 /* BEGIN NESTED FUNCTION omprintf() */
712 void omprintf(const char *format, ...) {
716 va_start(arg_ptr, format);
717 if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
718 buf[sizeof buf - 2] = '\n';
720 fwrite(buf, strlen(buf), 1, outfp);
722 else if (outsock >= 0) {
723 write(outsock, buf, strlen(buf));
726 client_write(buf, strlen(buf));
730 /* END NESTED FUNCTION omprintf() */
732 /* BEGIN NESTED FUNCTION omfmout() */
733 void omfmout(char *mptr) {
744 c = 1; /* c is the current pos */
752 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
754 if (((old == 13) || (old == 10)) && (isspace(real))) {
762 if (((strlen(aaa) + c) > (75)) && (strlen(aaa) > (75))) {
763 omprintf("\n%s", aaa);
772 if ((strlen(aaa) + c) > (75)) {
776 omprintf("%s ", aaa);
782 if ((ch == 13) || (ch == 10)) {
783 omprintf("%s\n", aaa);
790 FMTEND: omprintf("%s\n", aaa);
792 /* END NESTED FUNCTION omfmout() */
794 /* BEGIN NESTED FUNCTION fixed_output() */
796 * Callback function for mime parser that wants to display text
798 void fixed_output(char *name, char *filename, char *partnum, char *disp,
799 void *content, char *cbtype, size_t length)
805 if (!strcasecmp(cbtype, "multipart/alternative")) {
806 strcpy(ma->prefix, partnum);
807 strcat(ma->prefix, ".");
813 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
815 && (ma->did_print == 1) ) {
816 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
822 if (!strcasecmp(cbtype, "text/plain")) {
825 while (wlen--) omprintf("%c", *wptr++);
827 else if (!strcasecmp(cbtype, "text/html")) {
828 ptr = html_to_ascii(content, 80, 0);
831 while (wlen--) omprintf("%c", *wptr++);
834 else if (strncasecmp(cbtype, "multipart/", 10)) {
835 omprintf("Part %s: %s (%s) (%d bytes)\n",
836 partnum, filename, cbtype, length);
840 /* END NESTED FUNCTION fixed_output() */
844 sprintf(mid, "%ld", msg_num);
846 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
847 if (do_proto) cprintf("%d Not logged in.\n",
848 ERROR + NOT_LOGGED_IN);
849 return(om_not_logged_in);
852 /* FIX ... small security issue
853 * We need to check to make sure the requested message is actually
854 * in the current room, and set msg_ok to 1 only if it is. This
855 * functionality is currently missing because I'm in a hurry to replace
856 * broken production code with nonbroken pre-beta code. :( -- ajc
859 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
861 return(om_no_such_msg);
866 * Fetch the message from disk
868 TheMessage = CtdlFetchMessage(msg_num);
869 if (TheMessage == NULL) {
870 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
872 return(om_no_such_msg);
875 /* Are we downloading a MIME component? */
876 if (mode == MT_DOWNLOAD) {
877 if (TheMessage->cm_format_type != FMT_RFC822) {
879 cprintf("%d This is not a MIME message.\n",
881 } else if (CC->download_fp != NULL) {
882 if (do_proto) cprintf(
883 "%d You already have a download open.\n",
886 /* Parse the message text component */
887 mptr = TheMessage->cm_fields['M'];
888 mime_parser(mptr, NULL, *mime_download);
889 /* If there's no file open by this time, the requested
890 * section wasn't found, so print an error
892 if (CC->download_fp == NULL) {
893 if (do_proto) cprintf(
894 "%d Section %s not found.\n",
895 ERROR + FILE_NOT_FOUND,
899 CtdlFreeMessage(TheMessage);
900 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
903 /* now for the user-mode message reading loops */
904 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
906 /* Tell the client which format type we're using. If this is a
907 * MIME message, *lie* about it and tell the user it's fixed-format.
909 if (mode == MT_CITADEL) {
910 if (TheMessage->cm_format_type == FMT_RFC822) {
911 if (do_proto) cprintf("type=1\n");
914 if (do_proto) cprintf("type=%d\n",
915 TheMessage->cm_format_type);
919 /* nhdr=yes means that we're only displaying headers, no body */
920 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
921 if (do_proto) cprintf("nhdr=yes\n");
924 /* begin header processing loop for Citadel message format */
926 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
928 strcpy(display_name, "<unknown>");
929 if (TheMessage->cm_fields['A']) {
930 strcpy(buf, TheMessage->cm_fields['A']);
931 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
932 if (TheMessage->cm_anon_type == MES_ANON)
933 strcpy(display_name, "****");
934 else if (TheMessage->cm_anon_type == MES_AN2)
935 strcpy(display_name, "anonymous");
937 strcpy(display_name, buf);
939 && ((TheMessage->cm_anon_type == MES_ANON)
940 || (TheMessage->cm_anon_type == MES_AN2))) {
941 sprintf(&display_name[strlen(display_name)],
946 strcpy(allkeys, FORDER);
947 for (i=0; i<strlen(allkeys); ++i) {
948 k = (int) allkeys[i];
950 if (TheMessage->cm_fields[k] != NULL) {
952 if (do_proto) cprintf("%s=%s\n",
957 if (do_proto) cprintf("%s=%s\n",
959 TheMessage->cm_fields[k]
968 /* begin header processing loop for RFC822 transfer format */
972 strcpy(snode, NODENAME);
973 strcpy(lnode, HUMANNODE);
974 if (mode == MT_RFC822) {
975 for (i = 0; i < 256; ++i) {
976 if (TheMessage->cm_fields[i]) {
977 mptr = TheMessage->cm_fields[i];
981 } else if (i == 'P') {
982 omprintf("Path: %s\n", mptr);
983 for (a = 0; a < strlen(mptr); ++a) {
984 if (mptr[a] == '!') {
985 strcpy(mptr, &mptr[a + 1]);
991 omprintf("Subject: %s\n", mptr);
997 omprintf("X-Citadel-Room: %s\n", mptr);
1001 omprintf("To: %s\n", mptr);
1002 else if (i == 'T') {
1004 omprintf("Date: %s", asctime(localtime(&xtime)));
1010 if (mode == MT_RFC822) {
1011 if (!strcasecmp(snode, NODENAME)) {
1012 strcpy(snode, FQDN);
1014 omprintf("Message-ID: <%s@%s>\n", mid, snode);
1015 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1016 omprintf("From: %s@%s (%s)\n", suser, snode, luser);
1017 omprintf("Organization: %s\n", lnode);
1020 /* end header processing loop ... at this point, we're in the text */
1022 mptr = TheMessage->cm_fields['M'];
1024 /* Tell the client about the MIME parts in this message */
1025 if (TheMessage->cm_format_type == FMT_RFC822) { /* legacy text dump */
1026 if (mode == MT_CITADEL) {
1027 mime_parser(mptr, NULL, *list_this_part);
1029 else if (mode == MT_MIME) { /* list parts only */
1030 mime_parser(mptr, NULL, *list_this_part);
1031 if (do_proto) cprintf("000\n");
1032 CtdlFreeMessage(TheMessage);
1038 if (do_proto) cprintf("000\n");
1039 CtdlFreeMessage(TheMessage);
1043 /* signify start of msg text */
1044 if (mode == MT_CITADEL)
1045 if (do_proto) cprintf("text\n");
1046 /* if ((mode == MT_RFC822) && (TheMessage->cm_format_type != FMT_RFC822)) { */
1047 if (mode == MT_RFC822) {
1051 /* If the format type on disk is 1 (fixed-format), then we want
1052 * everything to be output completely literally ... regardless of
1053 * what message transfer format is in use.
1055 if (TheMessage->cm_format_type == FMT_FIXED) {
1057 while (ch = *mptr++, ch > 0) {
1060 if ((ch == 10) || (strlen(buf) > 250)) {
1061 omprintf("%s\n", buf);
1064 buf[strlen(buf) + 1] = 0;
1065 buf[strlen(buf)] = ch;
1068 if (strlen(buf) > 0)
1069 omprintf("%s\n", buf);
1072 /* If the message on disk is format 0 (Citadel vari-format), we
1073 * output using the formatter at 80 columns. This is the final output
1074 * form if the transfer format is RFC822, but if the transfer format
1075 * is Citadel proprietary, it'll still work, because the indentation
1076 * for new paragraphs is correct and the client will reformat the
1077 * message to the reader's screen width.
1079 if (TheMessage->cm_format_type == FMT_CITADEL) {
1083 /* If the message on disk is format 4 (MIME), we've gotta hand it
1084 * off to the MIME parser. The client has already been told that
1085 * this message is format 1 (fixed format), so the callback function
1086 * we use will display those parts as-is.
1088 if (TheMessage->cm_format_type == FMT_RFC822) {
1089 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1090 memset(ma, 0, sizeof(struct ma_info));
1091 mime_parser(mptr, NULL, *fixed_output);
1094 /* now we're done */
1095 if (do_proto) cprintf("000\n");
1096 CtdlFreeMessage(TheMessage);
1103 * display a message (mode 0 - Citadel proprietary)
1105 void cmd_msg0(char *cmdbuf)
1108 int headers_only = 0;
1110 msgid = extract_long(cmdbuf, 0);
1111 headers_only = extract_int(cmdbuf, 1);
1113 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, NULL, -1);
1119 * display a message (mode 2 - RFC822)
1121 void cmd_msg2(char *cmdbuf)
1124 int headers_only = 0;
1126 msgid = extract_long(cmdbuf, 0);
1127 headers_only = extract_int(cmdbuf, 1);
1129 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, NULL, -1);
1135 * display a message (mode 3 - IGnet raw format - internal programs only)
1137 void cmd_msg3(char *cmdbuf)
1140 struct CtdlMessage *msg;
1143 if (CC->internal_pgm == 0) {
1144 cprintf("%d This command is for internal programs only.\n",
1149 msgnum = extract_long(cmdbuf, 0);
1150 msg = CtdlFetchMessage(msgnum);
1152 cprintf("%d Message %ld not found.\n",
1157 serialize_message(&smr, msg);
1158 CtdlFreeMessage(msg);
1161 cprintf("%d Unable to serialize message\n",
1162 ERROR+INTERNAL_ERROR);
1166 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1167 client_write(smr.ser, smr.len);
1174 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1176 void cmd_msg4(char *cmdbuf)
1180 msgid = extract_long(cmdbuf, 0);
1181 CtdlOutputMsg(msgid, MT_MIME, 0, 1, NULL, -1);
1185 * Open a component of a MIME message as a download file
1187 void cmd_opna(char *cmdbuf)
1191 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1193 msgid = extract_long(cmdbuf, 0);
1194 extract(desired_section, cmdbuf, 1);
1196 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, NULL, -1);
1201 * Save a message pointer into a specified room
1202 * (Returns 0 for success, nonzero for failure)
1203 * roomname may be NULL to use the current room
1205 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1207 char hold_rm[ROOMNAMELEN];
1208 struct cdbdata *cdbfr;
1211 long highest_msg = 0L;
1212 struct CtdlMessage *msg = NULL;
1214 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1215 roomname, msgid, flags);
1217 strcpy(hold_rm, CC->quickroom.QRname);
1219 /* We may need to check to see if this message is real */
1220 if ( (flags & SM_VERIFY_GOODNESS)
1221 || (flags & SM_DO_REPL_CHECK)
1223 msg = CtdlFetchMessage(msgid);
1224 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1227 /* Perform replication checks if necessary */
1228 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1230 if (getroom(&CC->quickroom,
1231 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1233 lprintf(9, "No such room <%s>\n", roomname);
1234 if (msg != NULL) CtdlFreeMessage(msg);
1235 return(ERROR + ROOM_NOT_FOUND);
1238 if (ReplicationChecks(msg) != 0) {
1239 getroom(&CC->quickroom, hold_rm);
1240 if (msg != NULL) CtdlFreeMessage(msg);
1241 lprintf(9, "Did replication, and newer exists\n");
1246 /* Now the regular stuff */
1247 if (lgetroom(&CC->quickroom,
1248 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1250 lprintf(9, "No such room <%s>\n", roomname);
1251 if (msg != NULL) CtdlFreeMessage(msg);
1252 return(ERROR + ROOM_NOT_FOUND);
1255 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1256 if (cdbfr == NULL) {
1260 msglist = mallok(cdbfr->len);
1261 if (msglist == NULL)
1262 lprintf(3, "ERROR malloc msglist!\n");
1263 num_msgs = cdbfr->len / sizeof(long);
1264 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1269 /* Make sure the message doesn't already exist in this room. It
1270 * is absolutely taboo to have more than one reference to the same
1271 * message in a room.
1273 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1274 if (msglist[i] == msgid) {
1275 lputroom(&CC->quickroom); /* unlock the room */
1276 getroom(&CC->quickroom, hold_rm);
1277 if (msg != NULL) CtdlFreeMessage(msg);
1278 return(ERROR + ALREADY_EXISTS);
1282 /* Now add the new message */
1284 msglist = reallok(msglist,
1285 (num_msgs * sizeof(long)));
1287 if (msglist == NULL) {
1288 lprintf(3, "ERROR: can't realloc message list!\n");
1290 msglist[num_msgs - 1] = msgid;
1292 /* Sort the message list, so all the msgid's are in order */
1293 num_msgs = sort_msglist(msglist, num_msgs);
1295 /* Determine the highest message number */
1296 highest_msg = msglist[num_msgs - 1];
1298 /* Write it back to disk. */
1299 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1300 msglist, num_msgs * sizeof(long));
1302 /* Free up the memory we used. */
1305 /* Update the highest-message pointer and unlock the room. */
1306 CC->quickroom.QRhighest = highest_msg;
1307 lputroom(&CC->quickroom);
1308 getroom(&CC->quickroom, hold_rm);
1310 /* Bump the reference count for this message. */
1311 AdjRefCount(msgid, +1);
1313 /* Return success. */
1314 if (msg != NULL) CtdlFreeMessage(msg);
1321 * Message base operation to send a message to the master file
1322 * (returns new message number)
1324 * This is the back end for CtdlSaveMsg() and should not be directly
1325 * called by server-side modules.
1328 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1329 int generate_id, /* generate 'I' field? */
1330 FILE *save_a_copy) /* save a copy to disk? */
1337 /* Get a new message number */
1338 newmsgid = get_new_message_number();
1339 sprintf(msgidbuf, "%ld", newmsgid);
1342 msg->cm_fields['I'] = strdoop(msgidbuf);
1345 serialize_message(&smr, msg);
1348 cprintf("%d Unable to serialize message\n",
1349 ERROR+INTERNAL_ERROR);
1353 /* Write our little bundle of joy into the message base */
1354 begin_critical_section(S_MSGMAIN);
1355 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1356 smr.ser, smr.len) < 0) {
1357 lprintf(2, "Can't store message\n");
1362 end_critical_section(S_MSGMAIN);
1364 /* If the caller specified that a copy should be saved to a particular
1365 * file handle, do that now too.
1367 if (save_a_copy != NULL) {
1368 fwrite(smr.ser, smr.len, 1, save_a_copy);
1371 /* Free the memory we used for the serialized message */
1374 /* Return the *local* message ID to the caller
1375 * (even if we're storing an incoming network message)
1383 * Serialize a struct CtdlMessage into the format used on disk and network.
1385 * This function loads up a "struct ser_ret" (defined in server.h) which
1386 * contains the length of the serialized message and a pointer to the
1387 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1389 void serialize_message(struct ser_ret *ret, /* return values */
1390 struct CtdlMessage *msg) /* unserialized msg */
1394 static char *forder = FORDER;
1396 if (is_valid_message(msg) == 0) return; /* self check */
1399 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1400 ret->len = ret->len +
1401 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1403 lprintf(9, "calling malloc\n");
1404 ret->ser = mallok(ret->len);
1405 if (ret->ser == NULL) {
1411 ret->ser[1] = msg->cm_anon_type;
1412 ret->ser[2] = msg->cm_format_type;
1415 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1416 ret->ser[wlen++] = (char)forder[i];
1417 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1418 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1420 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1429 * Back end for the ReplicationChecks() function
1431 void check_repl(long msgnum) {
1432 struct CtdlMessage *msg;
1433 time_t timestamp = (-1L);
1435 lprintf(9, "check_repl() found message %ld\n", msgnum);
1436 msg = CtdlFetchMessage(msgnum);
1437 if (msg == NULL) return;
1438 if (msg->cm_fields['T'] != NULL) {
1439 timestamp = atol(msg->cm_fields['T']);
1441 CtdlFreeMessage(msg);
1443 if (timestamp > msg_repl->highest) {
1444 msg_repl->highest = timestamp; /* newer! */
1445 lprintf(9, "newer!\n");
1448 lprintf(9, "older!\n");
1450 /* Existing isn't newer? Then delete the old one(s). */
1451 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1456 * Check to see if any messages already exist which carry the same Extended ID
1460 * -> With older timestamps: delete them and return 0. Message will be saved.
1461 * -> With newer timestamps: return 1. Message save will be aborted.
1463 int ReplicationChecks(struct CtdlMessage *msg) {
1464 struct CtdlMessage *template;
1467 lprintf(9, "ReplicationChecks() started\n");
1468 /* No extended id? Don't do anything. */
1469 if (msg->cm_fields['E'] == NULL) return 0;
1470 if (strlen(msg->cm_fields['E']) == 0) return 0;
1471 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1473 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1474 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1475 msg_repl->highest = atol(msg->cm_fields['T']);
1477 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1478 memset(template, 0, sizeof(struct CtdlMessage));
1479 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1481 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1483 /* If a newer message exists with the same Extended ID, abort
1486 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1490 CtdlFreeMessage(template);
1491 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1499 * Save a message to disk
1501 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1502 char *rec, /* Recipient (mail) */
1503 char *force, /* force a particular room? */
1504 int mailtype, /* local or remote type */
1505 int generate_id) /* 1 = generate 'I' field */
1508 char hold_rm[ROOMNAMELEN];
1509 char actual_rm[ROOMNAMELEN];
1510 char force_room[ROOMNAMELEN];
1511 char content_type[256]; /* We have to learn this */
1512 char recipient[256];
1515 struct usersupp userbuf;
1517 struct SuppMsgInfo smi;
1518 FILE *network_fp = NULL;
1519 static int seqnum = 1;
1521 lprintf(9, "CtdlSaveMsg() called\n");
1522 if (is_valid_message(msg) == 0) return(-1); /* self check */
1524 /* If this message has no timestamp, we take the liberty of
1525 * giving it one, right now.
1527 if (msg->cm_fields['T'] == NULL) {
1528 lprintf(9, "Generating timestamp\n");
1529 sprintf(aaa, "%ld", time(NULL));
1530 msg->cm_fields['T'] = strdoop(aaa);
1533 /* If this message has no path, we generate one.
1535 if (msg->cm_fields['P'] == NULL) {
1536 lprintf(9, "Generating path\n");
1537 if (msg->cm_fields['A'] != NULL) {
1538 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1539 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1540 if (isspace(msg->cm_fields['P'][a])) {
1541 msg->cm_fields['P'][a] = ' ';
1546 msg->cm_fields['P'] = strdoop("unknown");
1550 strcpy(force_room, force);
1552 /* Strip non-printable characters out of the recipient name */
1553 strcpy(recipient, rec);
1554 for (a = 0; a < strlen(recipient); ++a)
1555 if (!isprint(recipient[a]))
1556 strcpy(&recipient[a], &recipient[a + 1]);
1558 /* Learn about what's inside, because it's what's inside that counts */
1559 lprintf(9, "Learning what's inside\n");
1560 if (msg->cm_fields['M'] == NULL) {
1561 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1564 switch (msg->cm_format_type) {
1566 strcpy(content_type, "text/x-citadel-variformat");
1569 strcpy(content_type, "text/plain");
1572 strcpy(content_type, "text/plain");
1573 /* advance past header fields */
1574 mptr = msg->cm_fields['M'];
1577 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1578 safestrncpy(content_type, mptr,
1579 sizeof(content_type));
1580 strcpy(content_type, &content_type[14]);
1581 for (a = 0; a < strlen(content_type); ++a)
1582 if ((content_type[a] == ';')
1583 || (content_type[a] == ' ')
1584 || (content_type[a] == 13)
1585 || (content_type[a] == 10))
1586 content_type[a] = 0;
1593 /* Goto the correct room */
1594 lprintf(9, "Switching rooms\n");
1595 strcpy(hold_rm, CC->quickroom.QRname);
1596 strcpy(actual_rm, CC->quickroom.QRname);
1598 /* If the user is a twit, move to the twit room for posting */
1599 lprintf(9, "Handling twit stuff\n");
1601 if (CC->usersupp.axlevel == 2) {
1602 strcpy(hold_rm, actual_rm);
1603 strcpy(actual_rm, config.c_twitroom);
1607 /* ...or if this message is destined for Aide> then go there. */
1608 if (strlen(force_room) > 0) {
1609 strcpy(actual_rm, force_room);
1612 lprintf(9, "Possibly relocating\n");
1613 if (strcasecmp(actual_rm, CC->quickroom.QRname))
1614 getroom(&CC->quickroom, actual_rm);
1616 /* Perform "before save" hooks (aborting if any return nonzero) */
1617 lprintf(9, "Performing before-save hooks\n");
1618 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1620 /* If this message has an Extended ID, perform replication checks */
1621 lprintf(9, "Performing replication checks\n");
1622 if (ReplicationChecks(msg) > 0) return(-1);
1624 /* Network mail - send a copy to the network program. */
1625 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1626 lprintf(9, "Sending network spool\n");
1627 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1628 (long) getpid(), CC->cs_pid, ++seqnum);
1629 lprintf(9, "Saving a copy to %s\n", aaa);
1630 network_fp = fopen(aaa, "ab+");
1631 if (network_fp == NULL)
1632 lprintf(2, "ERROR: %s\n", strerror(errno));
1635 /* Save it to disk */
1636 lprintf(9, "Saving to disk\n");
1637 newmsgid = send_message(msg, generate_id, network_fp);
1638 if (network_fp != NULL) {
1640 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1643 if (newmsgid <= 0L) return(-1);
1645 /* Write a supplemental message info record. This doesn't have to
1646 * be a critical section because nobody else knows about this message
1649 lprintf(9, "Creating SuppMsgInfo record\n");
1650 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1651 smi.smi_msgnum = newmsgid;
1652 smi.smi_refcount = 0;
1653 safestrncpy(smi.smi_content_type, content_type, 64);
1654 PutSuppMsgInfo(&smi);
1656 /* Now figure out where to store the pointers */
1657 lprintf(9, "Storing pointers\n");
1660 /* If this is being done by the networker delivering a private
1661 * message, we want to BYPASS saving the sender's copy (because there
1662 * is no local sender; it would otherwise go to the Trashcan).
1664 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1665 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1668 /* For internet mail, drop a copy in the outbound queue room */
1669 /* FIX ... nothing's going to get delivered until we add
1670 some delivery instructions!!! */
1671 if (mailtype == MES_INTERNET) {
1672 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1675 /* Bump this user's messages posted counter. */
1676 lprintf(9, "Updating user\n");
1677 lgetuser(&CC->usersupp, CC->curr_user);
1678 CC->usersupp.posted = CC->usersupp.posted + 1;
1679 lputuser(&CC->usersupp);
1681 /* If this is private, local mail, make a copy in the
1682 * recipient's mailbox and bump the reference count.
1684 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1685 if (getuser(&userbuf, recipient) == 0) {
1686 lprintf(9, "Delivering private mail\n");
1687 MailboxName(actual_rm, &userbuf, MAILROOM);
1688 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1692 /* Perform "after save" hooks */
1693 lprintf(9, "Performing after-save hooks\n");
1694 PerformMessageHooks(msg, EVT_AFTERSAVE);
1697 lprintf(9, "Returning to original room\n");
1698 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1699 getroom(&CC->quickroom, hold_rm);
1707 * Convenience function for generating small administrative messages.
1709 void quickie_message(char *from, char *to, char *room, char *text)
1711 struct CtdlMessage *msg;
1713 msg = mallok(sizeof(struct CtdlMessage));
1714 memset(msg, 0, sizeof(struct CtdlMessage));
1715 msg->cm_magic = CTDLMESSAGE_MAGIC;
1716 msg->cm_anon_type = MES_NORMAL;
1717 msg->cm_format_type = 0;
1718 msg->cm_fields['A'] = strdoop(from);
1719 msg->cm_fields['O'] = strdoop(room);
1720 msg->cm_fields['N'] = strdoop(NODENAME);
1722 msg->cm_fields['R'] = strdoop(to);
1723 msg->cm_fields['M'] = strdoop(text);
1725 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1726 CtdlFreeMessage(msg);
1727 syslog(LOG_NOTICE, text);
1733 * Back end function used by make_message() and similar functions
1735 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1736 size_t maxlen, /* maximum message length */
1737 char *exist /* if non-null, append to it;
1738 exist is ALWAYS freed */
1741 size_t message_len = 0;
1742 size_t buffer_len = 0;
1746 if (exist == NULL) {
1750 m = reallok(exist, strlen(exist) + 4096);
1751 if (m == NULL) phree(exist);
1754 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1761 /* read in the lines of message text one by one */
1763 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1765 /* augment the buffer if we have to */
1766 if ((message_len + strlen(buf) + 2) > buffer_len) {
1767 lprintf(9, "realloking\n");
1768 ptr = reallok(m, (buffer_len * 2) );
1769 if (ptr == NULL) { /* flush if can't allocate */
1770 while ( (client_gets(buf)>0) &&
1771 strcmp(buf, terminator)) ;;
1774 buffer_len = (buffer_len * 2);
1777 lprintf(9, "buffer_len is %d\n", buffer_len);
1781 if (append == NULL) append = m;
1782 while (strlen(append) > 0) ++append;
1783 strcpy(append, buf);
1784 strcat(append, "\n");
1785 message_len = message_len + strlen(buf) + 1;
1787 /* if we've hit the max msg length, flush the rest */
1788 if (message_len >= maxlen) {
1789 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1800 * Build a binary message to be saved on disk.
1803 struct CtdlMessage *make_message(
1804 struct usersupp *author, /* author's usersupp structure */
1805 char *recipient, /* NULL if it's not mail */
1806 char *room, /* room where it's going */
1807 int type, /* see MES_ types in header file */
1808 int net_type, /* see MES_ types in header file */
1809 int format_type, /* local or remote (see citadel.h) */
1810 char *fake_name) /* who we're masquerading as */
1816 struct CtdlMessage *msg;
1818 msg = mallok(sizeof(struct CtdlMessage));
1819 memset(msg, 0, sizeof(struct CtdlMessage));
1820 msg->cm_magic = CTDLMESSAGE_MAGIC;
1821 msg->cm_anon_type = type;
1822 msg->cm_format_type = format_type;
1824 /* Don't confuse the poor folks if it's not routed mail. */
1825 strcpy(dest_node, "");
1827 /* If net_type is MES_BINARY, split out the destination node. */
1828 if (net_type == MES_BINARY) {
1829 strcpy(dest_node, NODENAME);
1830 for (a = 0; a < strlen(recipient); ++a) {
1831 if (recipient[a] == '@') {
1833 strcpy(dest_node, &recipient[a + 1]);
1838 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1839 if (net_type == MES_INTERNET) {
1840 strcpy(dest_node, "internet");
1843 while (isspace(recipient[strlen(recipient) - 1]))
1844 recipient[strlen(recipient) - 1] = 0;
1846 sprintf(buf, "cit%ld", author->usernum); /* Path */
1847 msg->cm_fields['P'] = strdoop(buf);
1849 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1850 msg->cm_fields['T'] = strdoop(buf);
1852 if (fake_name[0]) /* author */
1853 msg->cm_fields['A'] = strdoop(fake_name);
1855 msg->cm_fields['A'] = strdoop(author->fullname);
1857 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1858 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1860 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1862 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1863 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1865 if (recipient[0] != 0)
1866 msg->cm_fields['R'] = strdoop(recipient);
1867 if (dest_node[0] != 0)
1868 msg->cm_fields['D'] = strdoop(dest_node);
1871 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1872 config.c_maxmsglen, NULL);
1883 * message entry - mode 0 (normal)
1885 void cmd_ent0(char *entargs)
1888 char recipient[256];
1890 int format_type = 0;
1891 char newusername[256];
1892 struct CtdlMessage *msg;
1896 struct usersupp tempUS;
1899 post = extract_int(entargs, 0);
1900 extract(recipient, entargs, 1);
1901 anon_flag = extract_int(entargs, 2);
1902 format_type = extract_int(entargs, 3);
1904 /* first check to make sure the request is valid. */
1906 if (!(CC->logged_in)) {
1907 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1910 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1911 cprintf("%d Need to be validated to enter ",
1912 ERROR + HIGHER_ACCESS_REQUIRED);
1913 cprintf("(except in %s> to sysop)\n", MAILROOM);
1916 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1917 cprintf("%d Need net privileges to enter here.\n",
1918 ERROR + HIGHER_ACCESS_REQUIRED);
1921 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1922 cprintf("%d Sorry, this is a read-only room.\n",
1923 ERROR + HIGHER_ACCESS_REQUIRED);
1930 if (CC->usersupp.axlevel < 6) {
1931 cprintf("%d You don't have permission to masquerade.\n",
1932 ERROR + HIGHER_ACCESS_REQUIRED);
1935 extract(newusername, entargs, 4);
1936 memset(CC->fake_postname, 0, 32);
1937 strcpy(CC->fake_postname, newusername);
1938 cprintf("%d Ok\n", OK);
1941 CC->cs_flags |= CS_POSTING;
1944 if (CC->quickroom.QRflags & QR_MAILBOX) {
1945 if (CC->usersupp.axlevel >= 2) {
1946 strcpy(buf, recipient);
1948 strcpy(buf, "sysop");
1949 e = alias(buf); /* alias and mail type */
1950 if ((buf[0] == 0) || (e == MES_ERROR)) {
1951 cprintf("%d Unknown address - cannot send message.\n",
1952 ERROR + NO_SUCH_USER);
1955 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1956 cprintf("%d Net privileges required for network mail.\n",
1957 ERROR + HIGHER_ACCESS_REQUIRED);
1960 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1961 && ((CC->usersupp.flags & US_INTERNET) == 0)
1962 && (!CC->internal_pgm)) {
1963 cprintf("%d You don't have access to Internet mail.\n",
1964 ERROR + HIGHER_ACCESS_REQUIRED);
1967 if (!strcasecmp(buf, "sysop")) {
1972 goto SKFALL; /* don't search local file */
1973 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1974 cprintf("%d Can't send mail to yourself!\n",
1975 ERROR + NO_SUCH_USER);
1978 /* Check to make sure the user exists; also get the correct
1979 * upper/lower casing of the name.
1981 a = getuser(&tempUS, buf);
1983 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1986 strcpy(buf, tempUS.fullname);
1989 SKFALL: b = MES_NORMAL;
1990 if (CC->quickroom.QRflags & QR_ANONONLY)
1992 if (CC->quickroom.QRflags & QR_ANONOPT) {
1996 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1999 /* If we're only checking the validity of the request, return
2000 * success without creating the message.
2003 cprintf("%d %s\n", OK, buf);
2007 cprintf("%d send message\n", SEND_LISTING);
2009 /* Read in the message from the client. */
2010 if (CC->fake_postname[0])
2011 msg = make_message(&CC->usersupp, buf,
2012 CC->quickroom.QRname, b, e, format_type,
2014 else if (CC->fake_username[0])
2015 msg = make_message(&CC->usersupp, buf,
2016 CC->quickroom.QRname, b, e, format_type,
2019 msg = make_message(&CC->usersupp, buf,
2020 CC->quickroom.QRname, b, e, format_type, "");
2023 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2024 CtdlFreeMessage(msg);
2025 CC->fake_postname[0] = '\0';
2032 * message entry - mode 3 (raw)
2034 void cmd_ent3(char *entargs)
2040 unsigned char ch, which_field;
2041 struct usersupp tempUS;
2043 struct CtdlMessage *msg;
2046 if (CC->internal_pgm == 0) {
2047 cprintf("%d This command is for internal programs only.\n",
2052 /* See if there's a recipient, but make sure it's a real one */
2053 extract(recp, entargs, 1);
2054 for (a = 0; a < strlen(recp); ++a)
2055 if (!isprint(recp[a]))
2056 strcpy(&recp[a], &recp[a + 1]);
2057 while (isspace(recp[0]))
2058 strcpy(recp, &recp[1]);
2059 while (isspace(recp[strlen(recp) - 1]))
2060 recp[strlen(recp) - 1] = 0;
2062 /* If we're in Mail, check the recipient */
2063 if (strlen(recp) > 0) {
2064 e = alias(recp); /* alias and mail type */
2065 if ((recp[0] == 0) || (e == MES_ERROR)) {
2066 cprintf("%d Unknown address - cannot send message.\n",
2067 ERROR + NO_SUCH_USER);
2070 if (e == MES_LOCAL) {
2071 a = getuser(&tempUS, recp);
2073 cprintf("%d No such user.\n",
2074 ERROR + NO_SUCH_USER);
2080 /* At this point, message has been approved. */
2081 if (extract_int(entargs, 0) == 0) {
2082 cprintf("%d OK to send\n", OK);
2086 msglen = extract_long(entargs, 2);
2087 msg = mallok(sizeof(struct CtdlMessage));
2089 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2093 memset(msg, 0, sizeof(struct CtdlMessage));
2094 tempbuf = mallok(msglen);
2095 if (tempbuf == NULL) {
2096 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2101 cprintf("%d %ld\n", SEND_BINARY, msglen);
2103 client_read(&ch, 1); /* 0xFF magic number */
2104 msg->cm_magic = CTDLMESSAGE_MAGIC;
2105 client_read(&ch, 1); /* anon type */
2106 msg->cm_anon_type = ch;
2107 client_read(&ch, 1); /* format type */
2108 msg->cm_format_type = ch;
2109 msglen = msglen - 3;
2111 while (msglen > 0) {
2112 client_read(&which_field, 1);
2113 if (!isalpha(which_field)) valid_msg = 0;
2117 client_read(&ch, 1);
2119 a = strlen(tempbuf);
2122 } while ( (ch != 0) && (msglen > 0) );
2124 msg->cm_fields[which_field] = strdoop(tempbuf);
2127 msg->cm_flags = CM_SKIP_HOOKS;
2128 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2129 CtdlFreeMessage(msg);
2135 * API function to delete messages which match a set of criteria
2136 * (returns the actual number of messages deleted)
2138 int CtdlDeleteMessages(char *room_name, /* which room */
2139 long dmsgnum, /* or "0" for any */
2140 char *content_type /* or NULL for any */
2144 struct quickroom qrbuf;
2145 struct cdbdata *cdbfr;
2146 long *msglist = NULL;
2149 int num_deleted = 0;
2151 struct SuppMsgInfo smi;
2153 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2154 room_name, dmsgnum, content_type);
2156 /* get room record, obtaining a lock... */
2157 if (lgetroom(&qrbuf, room_name) != 0) {
2158 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2160 return (0); /* room not found */
2162 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2164 if (cdbfr != NULL) {
2165 msglist = mallok(cdbfr->len);
2166 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2167 num_msgs = cdbfr->len / sizeof(long);
2171 for (i = 0; i < num_msgs; ++i) {
2174 /* Set/clear a bit for each criterion */
2176 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2177 delete_this |= 0x01;
2179 if (content_type == NULL) {
2180 delete_this |= 0x02;
2182 GetSuppMsgInfo(&smi, msglist[i]);
2183 if (!strcasecmp(smi.smi_content_type,
2185 delete_this |= 0x02;
2189 /* Delete message only if all bits are set */
2190 if (delete_this == 0x03) {
2191 AdjRefCount(msglist[i], -1);
2197 num_msgs = sort_msglist(msglist, num_msgs);
2198 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2199 msglist, (num_msgs * sizeof(long)));
2201 qrbuf.QRhighest = msglist[num_msgs - 1];
2205 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2206 return (num_deleted);
2212 * Delete message from current room
2214 void cmd_dele(char *delstr)
2219 getuser(&CC->usersupp, CC->curr_user);
2220 if ((CC->usersupp.axlevel < 6)
2221 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2222 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2223 && (!(CC->internal_pgm))) {
2224 cprintf("%d Higher access required.\n",
2225 ERROR + HIGHER_ACCESS_REQUIRED);
2228 delnum = extract_long(delstr, 0);
2230 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2233 cprintf("%d %d message%s deleted.\n", OK,
2234 num_deleted, ((num_deleted != 1) ? "s" : ""));
2236 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2242 * move or copy a message to another room
2244 void cmd_move(char *args)
2248 struct quickroom qtemp;
2252 num = extract_long(args, 0);
2253 extract(targ, args, 1);
2254 targ[ROOMNAMELEN - 1] = 0;
2255 is_copy = extract_int(args, 2);
2257 getuser(&CC->usersupp, CC->curr_user);
2258 if ((CC->usersupp.axlevel < 6)
2259 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2260 cprintf("%d Higher access required.\n",
2261 ERROR + HIGHER_ACCESS_REQUIRED);
2265 if (getroom(&qtemp, targ) != 0) {
2266 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2270 err = CtdlSaveMsgPointerInRoom(targ, num,
2271 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2273 cprintf("%d Cannot store message in %s: error %d\n",
2278 /* Now delete the message from the source room,
2279 * if this is a 'move' rather than a 'copy' operation.
2281 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2283 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2289 * GetSuppMsgInfo() - Get the supplementary record for a message
2291 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2294 struct cdbdata *cdbsmi;
2297 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2298 smibuf->smi_msgnum = msgnum;
2299 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2301 /* Use the negative of the message number for its supp record index */
2302 TheIndex = (0L - msgnum);
2304 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2305 if (cdbsmi == NULL) {
2306 return; /* record not found; go with defaults */
2308 memcpy(smibuf, cdbsmi->ptr,
2309 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2310 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2317 * PutSuppMsgInfo() - (re)write supplementary record for a message
2319 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2323 /* Use the negative of the message number for its supp record index */
2324 TheIndex = (0L - smibuf->smi_msgnum);
2326 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2327 smibuf->smi_msgnum, smibuf->smi_refcount);
2329 cdb_store(CDB_MSGMAIN,
2330 &TheIndex, sizeof(long),
2331 smibuf, sizeof(struct SuppMsgInfo));
2336 * AdjRefCount - change the reference count for a message;
2337 * delete the message if it reaches zero
2339 void AdjRefCount(long msgnum, int incr)
2342 struct SuppMsgInfo smi;
2345 /* This is a *tight* critical section; please keep it that way, as
2346 * it may get called while nested in other critical sections.
2347 * Complicating this any further will surely cause deadlock!
2349 begin_critical_section(S_SUPPMSGMAIN);
2350 GetSuppMsgInfo(&smi, msgnum);
2351 smi.smi_refcount += incr;
2352 PutSuppMsgInfo(&smi);
2353 end_critical_section(S_SUPPMSGMAIN);
2355 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2356 msgnum, smi.smi_refcount);
2358 /* If the reference count is now zero, delete the message
2359 * (and its supplementary record as well).
2361 if (smi.smi_refcount == 0) {
2362 lprintf(9, "Deleting message <%ld>\n", msgnum);
2364 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2365 delnum = (0L - msgnum);
2366 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2371 * Write a generic object to this room
2373 * Note: this could be much more efficient. Right now we use two temporary
2374 * files, and still pull the message into memory as with all others.
2376 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2377 char *content_type, /* MIME type of this object */
2378 char *tempfilename, /* Where to fetch it from */
2379 struct usersupp *is_mailbox, /* Mailbox room? */
2380 int is_binary, /* Is encoding necessary? */
2381 int is_unique, /* Del others of this type? */
2382 unsigned int flags /* Internal save flags */
2387 char filename[PATH_MAX];
2390 struct quickroom qrbuf;
2391 char roomname[ROOMNAMELEN];
2392 struct CtdlMessage *msg;
2395 if (is_mailbox != NULL)
2396 MailboxName(roomname, is_mailbox, req_room);
2398 safestrncpy(roomname, req_room, sizeof(roomname));
2399 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2401 strcpy(filename, tmpnam(NULL));
2402 fp = fopen(filename, "w");
2406 tempfp = fopen(tempfilename, "r");
2407 if (tempfp == NULL) {
2413 fprintf(fp, "Content-type: %s\n", content_type);
2414 lprintf(9, "Content-type: %s\n", content_type);
2416 if (is_binary == 0) {
2417 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2418 while (ch = getc(tempfp), ch > 0)
2424 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2427 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2428 tempfilename, filename);
2432 lprintf(9, "Allocating\n");
2433 msg = mallok(sizeof(struct CtdlMessage));
2434 memset(msg, 0, sizeof(struct CtdlMessage));
2435 msg->cm_magic = CTDLMESSAGE_MAGIC;
2436 msg->cm_anon_type = MES_NORMAL;
2437 msg->cm_format_type = 4;
2438 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2439 msg->cm_fields['O'] = strdoop(req_room);
2440 msg->cm_fields['N'] = strdoop(config.c_nodename);
2441 msg->cm_fields['H'] = strdoop(config.c_humannode);
2442 msg->cm_flags = flags;
2444 lprintf(9, "Loading\n");
2445 fp = fopen(filename, "rb");
2446 fseek(fp, 0L, SEEK_END);
2449 msg->cm_fields['M'] = mallok(len);
2450 fread(msg->cm_fields['M'], len, 1, fp);
2454 /* Create the requested room if we have to. */
2455 if (getroom(&qrbuf, roomname) != 0) {
2456 create_room(roomname,
2457 ( (is_mailbox != NULL) ? 4 : 3 ),
2460 /* If the caller specified this object as unique, delete all
2461 * other objects of this type that are currently in the room.
2464 lprintf(9, "Deleted %d other msgs of this type\n",
2465 CtdlDeleteMessages(roomname, 0L, content_type));
2467 /* Now write the data */
2468 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2469 CtdlFreeMessage(msg);
2477 void CtdlGetSysConfigBackend(long msgnum) {
2478 config_msgnum = msgnum;
2482 char *CtdlGetSysConfig(char *sysconfname) {
2483 char hold_rm[ROOMNAMELEN];
2486 struct CtdlMessage *msg;
2489 strcpy(hold_rm, CC->quickroom.QRname);
2490 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2491 getroom(&CC->quickroom, hold_rm);
2496 /* We want the last (and probably only) config in this room */
2497 begin_critical_section(S_CONFIG);
2498 config_msgnum = (-1L);
2499 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2500 CtdlGetSysConfigBackend);
2501 msgnum = config_msgnum;
2502 end_critical_section(S_CONFIG);
2508 msg = CtdlFetchMessage(msgnum);
2510 conf = strdoop(msg->cm_fields['M']);
2511 CtdlFreeMessage(msg);
2518 getroom(&CC->quickroom, hold_rm);
2520 lprintf(9, "eggstracting...\n");
2521 if (conf != NULL) do {
2522 extract_token(buf, conf, 0, '\n');
2523 lprintf(9, "eggstracted <%s>\n", buf);
2524 strcpy(conf, &conf[strlen(buf)+1]);
2525 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2530 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2531 char temp[PATH_MAX];
2534 strcpy(temp, tmpnam(NULL));
2536 fp = fopen(temp, "w");
2537 if (fp == NULL) return;
2538 fprintf(fp, "%s", sysconfdata);
2541 /* this handy API function does all the work for us */
2542 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);