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? */
692 int crlf /* Use CRLF newlines instead of LF? */
699 char display_name[256];
700 struct CtdlMessage *TheMessage;
702 char *nl; /* newline string */
704 /* buffers needed for RFC822 translation */
713 /* BEGIN NESTED FUNCTION omprintf() */
714 void omprintf(const char *format, ...) {
718 va_start(arg_ptr, format);
719 if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
720 buf[sizeof buf - 2] = '\n';
722 fwrite(buf, strlen(buf), 1, outfp);
724 else if (outsock >= 0) {
725 write(outsock, buf, strlen(buf));
728 client_write(buf, strlen(buf));
732 /* END NESTED FUNCTION omprintf() */
734 /* BEGIN NESTED FUNCTION omfmout() */
735 void omfmout(char *mptr) {
746 c = 1; /* c is the current pos */
754 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
756 if (((old == 13) || (old == 10)) && (isspace(real))) {
764 if (((strlen(aaa) + c) > (75)) && (strlen(aaa) > (75))) {
765 omprintf("%s%s", nl, aaa);
774 if ((strlen(aaa) + c) > (75)) {
778 omprintf("%s ", aaa);
784 if ((ch == 13) || (ch == 10)) {
785 omprintf("%s%s", aaa, nl);
792 FMTEND: omprintf("%s%s", aaa, nl);
794 /* END NESTED FUNCTION omfmout() */
796 /* BEGIN NESTED FUNCTION fixed_output() */
798 * Callback function for mime parser that wants to display text
800 void fixed_output(char *name, char *filename, char *partnum, char *disp,
801 void *content, char *cbtype, size_t length)
807 if (!strcasecmp(cbtype, "multipart/alternative")) {
808 strcpy(ma->prefix, partnum);
809 strcat(ma->prefix, ".");
815 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
817 && (ma->did_print == 1) ) {
818 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
824 if (!strcasecmp(cbtype, "text/plain")) {
829 if (ch==10) omprintf("%s", nl);
830 else omprintf("%c", ch);
833 else if (!strcasecmp(cbtype, "text/html")) {
834 ptr = html_to_ascii(content, 80, 0);
839 if (ch==10) omprintf("%s", nl);
840 else omprintf("%c", ch);
844 else if (strncasecmp(cbtype, "multipart/", 10)) {
845 omprintf("Part %s: %s (%s) (%d bytes)%s",
846 partnum, filename, cbtype, length, nl);
850 /* END NESTED FUNCTION fixed_output() */
854 sprintf(mid, "%ld", msg_num);
855 nl = (crlf ? "\r\n" : "\n");
857 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
858 if (do_proto) cprintf("%d Not logged in.\n",
859 ERROR + NOT_LOGGED_IN);
860 return(om_not_logged_in);
863 /* FIX ... small security issue
864 * We need to check to make sure the requested message is actually
865 * in the current room, and set msg_ok to 1 only if it is. This
866 * functionality is currently missing because I'm in a hurry to replace
867 * broken production code with nonbroken pre-beta code. :( -- ajc
870 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
872 return(om_no_such_msg);
877 * Fetch the message from disk
879 TheMessage = CtdlFetchMessage(msg_num);
880 if (TheMessage == NULL) {
881 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
883 return(om_no_such_msg);
886 /* Are we downloading a MIME component? */
887 if (mode == MT_DOWNLOAD) {
888 if (TheMessage->cm_format_type != FMT_RFC822) {
890 cprintf("%d This is not a MIME message.\n",
892 } else if (CC->download_fp != NULL) {
893 if (do_proto) cprintf(
894 "%d You already have a download open.\n",
897 /* Parse the message text component */
898 mptr = TheMessage->cm_fields['M'];
899 mime_parser(mptr, NULL, *mime_download);
900 /* If there's no file open by this time, the requested
901 * section wasn't found, so print an error
903 if (CC->download_fp == NULL) {
904 if (do_proto) cprintf(
905 "%d Section %s not found.\n",
906 ERROR + FILE_NOT_FOUND,
910 CtdlFreeMessage(TheMessage);
911 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
914 /* now for the user-mode message reading loops */
915 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
917 /* Tell the client which format type we're using. If this is a
918 * MIME message, *lie* about it and tell the user it's fixed-format.
920 if (mode == MT_CITADEL) {
921 if (TheMessage->cm_format_type == FMT_RFC822) {
922 if (do_proto) cprintf("type=1\n");
925 if (do_proto) cprintf("type=%d\n",
926 TheMessage->cm_format_type);
930 /* nhdr=yes means that we're only displaying headers, no body */
931 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
932 if (do_proto) cprintf("nhdr=yes\n");
935 /* begin header processing loop for Citadel message format */
937 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
939 strcpy(display_name, "<unknown>");
940 if (TheMessage->cm_fields['A']) {
941 strcpy(buf, TheMessage->cm_fields['A']);
942 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
943 if (TheMessage->cm_anon_type == MES_ANON)
944 strcpy(display_name, "****");
945 else if (TheMessage->cm_anon_type == MES_AN2)
946 strcpy(display_name, "anonymous");
948 strcpy(display_name, buf);
950 && ((TheMessage->cm_anon_type == MES_ANON)
951 || (TheMessage->cm_anon_type == MES_AN2))) {
952 sprintf(&display_name[strlen(display_name)],
957 strcpy(allkeys, FORDER);
958 for (i=0; i<strlen(allkeys); ++i) {
959 k = (int) allkeys[i];
961 if (TheMessage->cm_fields[k] != NULL) {
963 if (do_proto) cprintf("%s=%s\n",
968 if (do_proto) cprintf("%s=%s\n",
970 TheMessage->cm_fields[k]
979 /* begin header processing loop for RFC822 transfer format */
983 strcpy(snode, NODENAME);
984 strcpy(lnode, HUMANNODE);
985 if (mode == MT_RFC822) {
986 for (i = 0; i < 256; ++i) {
987 if (TheMessage->cm_fields[i]) {
988 mptr = TheMessage->cm_fields[i];
992 } else if (i == 'P') {
993 omprintf("Path: %s%s", mptr, nl);
994 for (a = 0; a < strlen(mptr); ++a) {
995 if (mptr[a] == '!') {
996 strcpy(mptr, &mptr[a + 1]);
1000 strcpy(suser, mptr);
1001 } else if (i == 'U')
1002 omprintf("Subject: %s%s", mptr, nl);
1006 strcpy(lnode, mptr);
1008 omprintf("X-Citadel-Room: %s%s",
1011 strcpy(snode, mptr);
1013 omprintf("To: %s%s", mptr, nl);
1014 else if (i == 'T') {
1016 omprintf("Date: %s", asctime(localtime(&xtime)));
1022 if (mode == MT_RFC822) {
1023 if (!strcasecmp(snode, NODENAME)) {
1024 strcpy(snode, FQDN);
1026 omprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
1027 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1028 omprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1029 omprintf("Organization: %s%s", lnode, nl);
1032 /* end header processing loop ... at this point, we're in the text */
1034 mptr = TheMessage->cm_fields['M'];
1036 /* Tell the client about the MIME parts in this message */
1037 if (TheMessage->cm_format_type == FMT_RFC822) { /* legacy text dump */
1038 if (mode == MT_CITADEL) {
1039 mime_parser(mptr, NULL, *list_this_part);
1041 else if (mode == MT_MIME) { /* list parts only */
1042 mime_parser(mptr, NULL, *list_this_part);
1043 if (do_proto) cprintf("000\n");
1044 CtdlFreeMessage(TheMessage);
1050 if (do_proto) cprintf("000\n");
1051 CtdlFreeMessage(TheMessage);
1055 /* signify start of msg text */
1056 if (mode == MT_CITADEL)
1057 if (do_proto) cprintf("text\n");
1058 /* if ((mode == MT_RFC822) && (TheMessage->cm_format_type != FMT_RFC822)) { */
1059 if (mode == MT_RFC822) {
1063 /* If the format type on disk is 1 (fixed-format), then we want
1064 * everything to be output completely literally ... regardless of
1065 * what message transfer format is in use.
1067 if (TheMessage->cm_format_type == FMT_FIXED) {
1069 while (ch = *mptr++, ch > 0) {
1072 if ((ch == 10) || (strlen(buf) > 250)) {
1073 omprintf("%s%s", buf, nl);
1076 buf[strlen(buf) + 1] = 0;
1077 buf[strlen(buf)] = ch;
1080 if (strlen(buf) > 0)
1081 omprintf("%s%s", buf, nl);
1084 /* If the message on disk is format 0 (Citadel vari-format), we
1085 * output using the formatter at 80 columns. This is the final output
1086 * form if the transfer format is RFC822, but if the transfer format
1087 * is Citadel proprietary, it'll still work, because the indentation
1088 * for new paragraphs is correct and the client will reformat the
1089 * message to the reader's screen width.
1091 if (TheMessage->cm_format_type == FMT_CITADEL) {
1095 /* If the message on disk is format 4 (MIME), we've gotta hand it
1096 * off to the MIME parser. The client has already been told that
1097 * this message is format 1 (fixed format), so the callback function
1098 * we use will display those parts as-is.
1100 if (TheMessage->cm_format_type == FMT_RFC822) {
1101 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1102 memset(ma, 0, sizeof(struct ma_info));
1103 mime_parser(mptr, NULL, *fixed_output);
1106 /* now we're done */
1107 if (do_proto) cprintf("000\n");
1108 CtdlFreeMessage(TheMessage);
1115 * display a message (mode 0 - Citadel proprietary)
1117 void cmd_msg0(char *cmdbuf)
1120 int headers_only = 0;
1122 msgid = extract_long(cmdbuf, 0);
1123 headers_only = extract_int(cmdbuf, 1);
1125 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, NULL, -1, 0);
1131 * display a message (mode 2 - RFC822)
1133 void cmd_msg2(char *cmdbuf)
1136 int headers_only = 0;
1138 msgid = extract_long(cmdbuf, 0);
1139 headers_only = extract_int(cmdbuf, 1);
1141 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, NULL, -1, 1);
1147 * display a message (mode 3 - IGnet raw format - internal programs only)
1149 void cmd_msg3(char *cmdbuf)
1152 struct CtdlMessage *msg;
1155 if (CC->internal_pgm == 0) {
1156 cprintf("%d This command is for internal programs only.\n",
1161 msgnum = extract_long(cmdbuf, 0);
1162 msg = CtdlFetchMessage(msgnum);
1164 cprintf("%d Message %ld not found.\n",
1169 serialize_message(&smr, msg);
1170 CtdlFreeMessage(msg);
1173 cprintf("%d Unable to serialize message\n",
1174 ERROR+INTERNAL_ERROR);
1178 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1179 client_write(smr.ser, smr.len);
1186 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1188 void cmd_msg4(char *cmdbuf)
1192 msgid = extract_long(cmdbuf, 0);
1193 CtdlOutputMsg(msgid, MT_MIME, 0, 1, NULL, -1, 0);
1197 * Open a component of a MIME message as a download file
1199 void cmd_opna(char *cmdbuf)
1203 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1205 msgid = extract_long(cmdbuf, 0);
1206 extract(desired_section, cmdbuf, 1);
1208 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, NULL, -1, 1);
1213 * Save a message pointer into a specified room
1214 * (Returns 0 for success, nonzero for failure)
1215 * roomname may be NULL to use the current room
1217 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1219 char hold_rm[ROOMNAMELEN];
1220 struct cdbdata *cdbfr;
1223 long highest_msg = 0L;
1224 struct CtdlMessage *msg = NULL;
1226 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1227 roomname, msgid, flags);
1229 strcpy(hold_rm, CC->quickroom.QRname);
1231 /* We may need to check to see if this message is real */
1232 if ( (flags & SM_VERIFY_GOODNESS)
1233 || (flags & SM_DO_REPL_CHECK)
1235 msg = CtdlFetchMessage(msgid);
1236 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1239 /* Perform replication checks if necessary */
1240 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1242 if (getroom(&CC->quickroom,
1243 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1245 lprintf(9, "No such room <%s>\n", roomname);
1246 if (msg != NULL) CtdlFreeMessage(msg);
1247 return(ERROR + ROOM_NOT_FOUND);
1250 if (ReplicationChecks(msg) != 0) {
1251 getroom(&CC->quickroom, hold_rm);
1252 if (msg != NULL) CtdlFreeMessage(msg);
1253 lprintf(9, "Did replication, and newer exists\n");
1258 /* Now the regular stuff */
1259 if (lgetroom(&CC->quickroom,
1260 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1262 lprintf(9, "No such room <%s>\n", roomname);
1263 if (msg != NULL) CtdlFreeMessage(msg);
1264 return(ERROR + ROOM_NOT_FOUND);
1267 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1268 if (cdbfr == NULL) {
1272 msglist = mallok(cdbfr->len);
1273 if (msglist == NULL)
1274 lprintf(3, "ERROR malloc msglist!\n");
1275 num_msgs = cdbfr->len / sizeof(long);
1276 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1281 /* Make sure the message doesn't already exist in this room. It
1282 * is absolutely taboo to have more than one reference to the same
1283 * message in a room.
1285 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1286 if (msglist[i] == msgid) {
1287 lputroom(&CC->quickroom); /* unlock the room */
1288 getroom(&CC->quickroom, hold_rm);
1289 if (msg != NULL) CtdlFreeMessage(msg);
1290 return(ERROR + ALREADY_EXISTS);
1294 /* Now add the new message */
1296 msglist = reallok(msglist,
1297 (num_msgs * sizeof(long)));
1299 if (msglist == NULL) {
1300 lprintf(3, "ERROR: can't realloc message list!\n");
1302 msglist[num_msgs - 1] = msgid;
1304 /* Sort the message list, so all the msgid's are in order */
1305 num_msgs = sort_msglist(msglist, num_msgs);
1307 /* Determine the highest message number */
1308 highest_msg = msglist[num_msgs - 1];
1310 /* Write it back to disk. */
1311 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1312 msglist, num_msgs * sizeof(long));
1314 /* Free up the memory we used. */
1317 /* Update the highest-message pointer and unlock the room. */
1318 CC->quickroom.QRhighest = highest_msg;
1319 lputroom(&CC->quickroom);
1320 getroom(&CC->quickroom, hold_rm);
1322 /* Bump the reference count for this message. */
1323 if ((flags & SM_DONT_BUMP_REF)==0) {
1324 AdjRefCount(msgid, +1);
1327 /* Return success. */
1328 if (msg != NULL) CtdlFreeMessage(msg);
1335 * Message base operation to send a message to the master file
1336 * (returns new message number)
1338 * This is the back end for CtdlSaveMsg() and should not be directly
1339 * called by server-side modules.
1342 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1343 int generate_id, /* generate 'I' field? */
1344 FILE *save_a_copy) /* save a copy to disk? */
1351 /* Get a new message number */
1352 newmsgid = get_new_message_number();
1353 sprintf(msgidbuf, "%ld", newmsgid);
1356 msg->cm_fields['I'] = strdoop(msgidbuf);
1359 serialize_message(&smr, msg);
1362 cprintf("%d Unable to serialize message\n",
1363 ERROR+INTERNAL_ERROR);
1367 /* Write our little bundle of joy into the message base */
1368 begin_critical_section(S_MSGMAIN);
1369 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1370 smr.ser, smr.len) < 0) {
1371 lprintf(2, "Can't store message\n");
1376 end_critical_section(S_MSGMAIN);
1378 /* If the caller specified that a copy should be saved to a particular
1379 * file handle, do that now too.
1381 if (save_a_copy != NULL) {
1382 fwrite(smr.ser, smr.len, 1, save_a_copy);
1385 /* Free the memory we used for the serialized message */
1388 /* Return the *local* message ID to the caller
1389 * (even if we're storing an incoming network message)
1397 * Serialize a struct CtdlMessage into the format used on disk and network.
1399 * This function loads up a "struct ser_ret" (defined in server.h) which
1400 * contains the length of the serialized message and a pointer to the
1401 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1403 void serialize_message(struct ser_ret *ret, /* return values */
1404 struct CtdlMessage *msg) /* unserialized msg */
1408 static char *forder = FORDER;
1410 if (is_valid_message(msg) == 0) return; /* self check */
1413 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1414 ret->len = ret->len +
1415 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1417 lprintf(9, "calling malloc\n");
1418 ret->ser = mallok(ret->len);
1419 if (ret->ser == NULL) {
1425 ret->ser[1] = msg->cm_anon_type;
1426 ret->ser[2] = msg->cm_format_type;
1429 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1430 ret->ser[wlen++] = (char)forder[i];
1431 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1432 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1434 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1443 * Back end for the ReplicationChecks() function
1445 void check_repl(long msgnum) {
1446 struct CtdlMessage *msg;
1447 time_t timestamp = (-1L);
1449 lprintf(9, "check_repl() found message %ld\n", msgnum);
1450 msg = CtdlFetchMessage(msgnum);
1451 if (msg == NULL) return;
1452 if (msg->cm_fields['T'] != NULL) {
1453 timestamp = atol(msg->cm_fields['T']);
1455 CtdlFreeMessage(msg);
1457 if (timestamp > msg_repl->highest) {
1458 msg_repl->highest = timestamp; /* newer! */
1459 lprintf(9, "newer!\n");
1462 lprintf(9, "older!\n");
1464 /* Existing isn't newer? Then delete the old one(s). */
1465 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1470 * Check to see if any messages already exist which carry the same Extended ID
1474 * -> With older timestamps: delete them and return 0. Message will be saved.
1475 * -> With newer timestamps: return 1. Message save will be aborted.
1477 int ReplicationChecks(struct CtdlMessage *msg) {
1478 struct CtdlMessage *template;
1481 lprintf(9, "ReplicationChecks() started\n");
1482 /* No extended id? Don't do anything. */
1483 if (msg->cm_fields['E'] == NULL) return 0;
1484 if (strlen(msg->cm_fields['E']) == 0) return 0;
1485 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1487 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1488 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1489 msg_repl->highest = atol(msg->cm_fields['T']);
1491 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1492 memset(template, 0, sizeof(struct CtdlMessage));
1493 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1495 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1497 /* If a newer message exists with the same Extended ID, abort
1500 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1504 CtdlFreeMessage(template);
1505 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1513 * Save a message to disk
1515 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1516 char *rec, /* Recipient (mail) */
1517 char *force, /* force a particular room? */
1518 int mailtype, /* local or remote type */
1519 int generate_id) /* 1 = generate 'I' field */
1522 char hold_rm[ROOMNAMELEN];
1523 char actual_rm[ROOMNAMELEN];
1524 char force_room[ROOMNAMELEN];
1525 char content_type[256]; /* We have to learn this */
1526 char recipient[256];
1529 struct usersupp userbuf;
1531 struct SuppMsgInfo smi;
1532 FILE *network_fp = NULL;
1533 static int seqnum = 1;
1534 struct CtdlMessage *imsg;
1537 lprintf(9, "CtdlSaveMsg() called\n");
1538 if (is_valid_message(msg) == 0) return(-1); /* self check */
1540 /* If this message has no timestamp, we take the liberty of
1541 * giving it one, right now.
1543 if (msg->cm_fields['T'] == NULL) {
1544 lprintf(9, "Generating timestamp\n");
1545 sprintf(aaa, "%ld", time(NULL));
1546 msg->cm_fields['T'] = strdoop(aaa);
1549 /* If this message has no path, we generate one.
1551 if (msg->cm_fields['P'] == NULL) {
1552 lprintf(9, "Generating path\n");
1553 if (msg->cm_fields['A'] != NULL) {
1554 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1555 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1556 if (isspace(msg->cm_fields['P'][a])) {
1557 msg->cm_fields['P'][a] = ' ';
1562 msg->cm_fields['P'] = strdoop("unknown");
1566 strcpy(force_room, force);
1568 /* Strip non-printable characters out of the recipient name */
1569 strcpy(recipient, rec);
1570 for (a = 0; a < strlen(recipient); ++a)
1571 if (!isprint(recipient[a]))
1572 strcpy(&recipient[a], &recipient[a + 1]);
1574 /* Learn about what's inside, because it's what's inside that counts */
1575 lprintf(9, "Learning what's inside\n");
1576 if (msg->cm_fields['M'] == NULL) {
1577 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1580 switch (msg->cm_format_type) {
1582 strcpy(content_type, "text/x-citadel-variformat");
1585 strcpy(content_type, "text/plain");
1588 strcpy(content_type, "text/plain");
1589 /* advance past header fields */
1590 mptr = msg->cm_fields['M'];
1593 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1594 safestrncpy(content_type, mptr,
1595 sizeof(content_type));
1596 strcpy(content_type, &content_type[14]);
1597 for (a = 0; a < strlen(content_type); ++a)
1598 if ((content_type[a] == ';')
1599 || (content_type[a] == ' ')
1600 || (content_type[a] == 13)
1601 || (content_type[a] == 10))
1602 content_type[a] = 0;
1609 /* Goto the correct room */
1610 lprintf(9, "Switching rooms\n");
1611 strcpy(hold_rm, CC->quickroom.QRname);
1612 strcpy(actual_rm, CC->quickroom.QRname);
1614 /* If the user is a twit, move to the twit room for posting */
1615 lprintf(9, "Handling twit stuff\n");
1617 if (CC->usersupp.axlevel == 2) {
1618 strcpy(hold_rm, actual_rm);
1619 strcpy(actual_rm, config.c_twitroom);
1623 /* ...or if this message is destined for Aide> then go there. */
1624 if (strlen(force_room) > 0) {
1625 strcpy(actual_rm, force_room);
1628 lprintf(9, "Possibly relocating\n");
1629 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1630 getroom(&CC->quickroom, actual_rm);
1634 * If this message has no O (room) field, generate one.
1636 if (msg->cm_fields['O'] == NULL) {
1637 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1640 /* Perform "before save" hooks (aborting if any return nonzero) */
1641 lprintf(9, "Performing before-save hooks\n");
1642 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1644 /* If this message has an Extended ID, perform replication checks */
1645 lprintf(9, "Performing replication checks\n");
1646 if (ReplicationChecks(msg) > 0) return(-1);
1648 /* Network mail - send a copy to the network program. */
1649 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1650 lprintf(9, "Sending network spool\n");
1651 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1652 (long) getpid(), CC->cs_pid, ++seqnum);
1653 lprintf(9, "Saving a copy to %s\n", aaa);
1654 network_fp = fopen(aaa, "ab+");
1655 if (network_fp == NULL)
1656 lprintf(2, "ERROR: %s\n", strerror(errno));
1659 /* Save it to disk */
1660 lprintf(9, "Saving to disk\n");
1661 newmsgid = send_message(msg, generate_id, network_fp);
1662 if (network_fp != NULL) {
1664 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1667 if (newmsgid <= 0L) return(-1);
1669 /* Write a supplemental message info record. This doesn't have to
1670 * be a critical section because nobody else knows about this message
1673 lprintf(9, "Creating SuppMsgInfo record\n");
1674 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1675 smi.smi_msgnum = newmsgid;
1676 smi.smi_refcount = 0;
1677 safestrncpy(smi.smi_content_type, content_type, 64);
1678 PutSuppMsgInfo(&smi);
1680 /* Now figure out where to store the pointers */
1681 lprintf(9, "Storing pointers\n");
1683 /* If this is being done by the networker delivering a private
1684 * message, we want to BYPASS saving the sender's copy (because there
1685 * is no local sender; it would otherwise go to the Trashcan).
1687 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1688 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1691 /* For internet mail, drop a copy in the outbound queue room */
1692 if (mailtype == MES_INTERNET) {
1693 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1696 /* Bump this user's messages posted counter. */
1697 lprintf(9, "Updating user\n");
1698 lgetuser(&CC->usersupp, CC->curr_user);
1699 CC->usersupp.posted = CC->usersupp.posted + 1;
1700 lputuser(&CC->usersupp);
1702 /* If this is private, local mail, make a copy in the
1703 * recipient's mailbox and bump the reference count.
1705 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1706 if (getuser(&userbuf, recipient) == 0) {
1707 lprintf(9, "Delivering private mail\n");
1708 MailboxName(actual_rm, &userbuf, MAILROOM);
1709 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1713 /* Perform "after save" hooks */
1714 lprintf(9, "Performing after-save hooks\n");
1715 PerformMessageHooks(msg, EVT_AFTERSAVE);
1718 lprintf(9, "Returning to original room\n");
1719 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1720 getroom(&CC->quickroom, hold_rm);
1722 /* For internet mail, generate delivery instructions
1723 * (Yes, this is recursive! Deal with it!)
1725 if (mailtype == MES_INTERNET) {
1726 lprintf(9, "Generating delivery instructions\n");
1727 instr = mallok(2048);
1729 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1731 SPOOLMIME, newmsgid, time(NULL), recipient );
1733 imsg = mallok(sizeof(struct CtdlMessage));
1734 memset(imsg, 0, sizeof(struct CtdlMessage));
1735 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1736 imsg->cm_anon_type = MES_NORMAL;
1737 imsg->cm_format_type = FMT_RFC822;
1738 imsg->cm_fields['A'] = strdoop("Citadel");
1739 imsg->cm_fields['M'] = instr;
1740 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1741 CtdlFreeMessage(imsg);
1750 * Convenience function for generating small administrative messages.
1752 void quickie_message(char *from, char *to, char *room, char *text)
1754 struct CtdlMessage *msg;
1756 msg = mallok(sizeof(struct CtdlMessage));
1757 memset(msg, 0, sizeof(struct CtdlMessage));
1758 msg->cm_magic = CTDLMESSAGE_MAGIC;
1759 msg->cm_anon_type = MES_NORMAL;
1760 msg->cm_format_type = 0;
1761 msg->cm_fields['A'] = strdoop(from);
1762 msg->cm_fields['O'] = strdoop(room);
1763 msg->cm_fields['N'] = strdoop(NODENAME);
1765 msg->cm_fields['R'] = strdoop(to);
1766 msg->cm_fields['M'] = strdoop(text);
1768 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1769 CtdlFreeMessage(msg);
1770 syslog(LOG_NOTICE, text);
1776 * Back end function used by make_message() and similar functions
1778 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1779 size_t maxlen, /* maximum message length */
1780 char *exist /* if non-null, append to it;
1781 exist is ALWAYS freed */
1784 size_t message_len = 0;
1785 size_t buffer_len = 0;
1789 if (exist == NULL) {
1793 m = reallok(exist, strlen(exist) + 4096);
1794 if (m == NULL) phree(exist);
1797 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1804 /* read in the lines of message text one by one */
1806 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1808 /* augment the buffer if we have to */
1809 if ((message_len + strlen(buf) + 2) > buffer_len) {
1810 lprintf(9, "realloking\n");
1811 ptr = reallok(m, (buffer_len * 2) );
1812 if (ptr == NULL) { /* flush if can't allocate */
1813 while ( (client_gets(buf)>0) &&
1814 strcmp(buf, terminator)) ;;
1817 buffer_len = (buffer_len * 2);
1820 lprintf(9, "buffer_len is %d\n", buffer_len);
1824 if (append == NULL) append = m;
1825 while (strlen(append) > 0) ++append;
1826 strcpy(append, buf);
1827 strcat(append, "\n");
1828 message_len = message_len + strlen(buf) + 1;
1830 /* if we've hit the max msg length, flush the rest */
1831 if (message_len >= maxlen) {
1832 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1843 * Build a binary message to be saved on disk.
1846 struct CtdlMessage *make_message(
1847 struct usersupp *author, /* author's usersupp structure */
1848 char *recipient, /* NULL if it's not mail */
1849 char *room, /* room where it's going */
1850 int type, /* see MES_ types in header file */
1851 int net_type, /* see MES_ types in header file */
1852 int format_type, /* local or remote (see citadel.h) */
1853 char *fake_name) /* who we're masquerading as */
1859 struct CtdlMessage *msg;
1861 msg = mallok(sizeof(struct CtdlMessage));
1862 memset(msg, 0, sizeof(struct CtdlMessage));
1863 msg->cm_magic = CTDLMESSAGE_MAGIC;
1864 msg->cm_anon_type = type;
1865 msg->cm_format_type = format_type;
1867 /* Don't confuse the poor folks if it's not routed mail. */
1868 strcpy(dest_node, "");
1870 /* If net_type is MES_BINARY, split out the destination node. */
1871 if (net_type == MES_BINARY) {
1872 strcpy(dest_node, NODENAME);
1873 for (a = 0; a < strlen(recipient); ++a) {
1874 if (recipient[a] == '@') {
1876 strcpy(dest_node, &recipient[a + 1]);
1881 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1882 if (net_type == MES_INTERNET) {
1883 strcpy(dest_node, "internet");
1886 while (isspace(recipient[strlen(recipient) - 1]))
1887 recipient[strlen(recipient) - 1] = 0;
1889 sprintf(buf, "cit%ld", author->usernum); /* Path */
1890 msg->cm_fields['P'] = strdoop(buf);
1892 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1893 msg->cm_fields['T'] = strdoop(buf);
1895 if (fake_name[0]) /* author */
1896 msg->cm_fields['A'] = strdoop(fake_name);
1898 msg->cm_fields['A'] = strdoop(author->fullname);
1900 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1901 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1903 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1905 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1906 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1908 if (recipient[0] != 0)
1909 msg->cm_fields['R'] = strdoop(recipient);
1910 if (dest_node[0] != 0)
1911 msg->cm_fields['D'] = strdoop(dest_node);
1914 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1915 config.c_maxmsglen, NULL);
1926 * message entry - mode 0 (normal)
1928 void cmd_ent0(char *entargs)
1931 char recipient[256];
1933 int format_type = 0;
1934 char newusername[256];
1935 struct CtdlMessage *msg;
1939 struct usersupp tempUS;
1942 post = extract_int(entargs, 0);
1943 extract(recipient, entargs, 1);
1944 anon_flag = extract_int(entargs, 2);
1945 format_type = extract_int(entargs, 3);
1947 /* first check to make sure the request is valid. */
1949 if (!(CC->logged_in)) {
1950 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1953 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1954 cprintf("%d Need to be validated to enter ",
1955 ERROR + HIGHER_ACCESS_REQUIRED);
1956 cprintf("(except in %s> to sysop)\n", MAILROOM);
1959 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1960 cprintf("%d Need net privileges to enter here.\n",
1961 ERROR + HIGHER_ACCESS_REQUIRED);
1964 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1965 cprintf("%d Sorry, this is a read-only room.\n",
1966 ERROR + HIGHER_ACCESS_REQUIRED);
1973 if (CC->usersupp.axlevel < 6) {
1974 cprintf("%d You don't have permission to masquerade.\n",
1975 ERROR + HIGHER_ACCESS_REQUIRED);
1978 extract(newusername, entargs, 4);
1979 memset(CC->fake_postname, 0, 32);
1980 strcpy(CC->fake_postname, newusername);
1981 cprintf("%d Ok\n", OK);
1984 CC->cs_flags |= CS_POSTING;
1987 if (CC->quickroom.QRflags & QR_MAILBOX) {
1988 if (CC->usersupp.axlevel >= 2) {
1989 strcpy(buf, recipient);
1991 strcpy(buf, "sysop");
1992 e = alias(buf); /* alias and mail type */
1993 if ((buf[0] == 0) || (e == MES_ERROR)) {
1994 cprintf("%d Unknown address - cannot send message.\n",
1995 ERROR + NO_SUCH_USER);
1998 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1999 cprintf("%d Net privileges required for network mail.\n",
2000 ERROR + HIGHER_ACCESS_REQUIRED);
2003 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2004 && ((CC->usersupp.flags & US_INTERNET) == 0)
2005 && (!CC->internal_pgm)) {
2006 cprintf("%d You don't have access to Internet mail.\n",
2007 ERROR + HIGHER_ACCESS_REQUIRED);
2010 if (!strcasecmp(buf, "sysop")) {
2015 goto SKFALL; /* don't search local file */
2016 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2017 cprintf("%d Can't send mail to yourself!\n",
2018 ERROR + NO_SUCH_USER);
2021 /* Check to make sure the user exists; also get the correct
2022 * upper/lower casing of the name.
2024 a = getuser(&tempUS, buf);
2026 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2029 strcpy(buf, tempUS.fullname);
2032 SKFALL: b = MES_NORMAL;
2033 if (CC->quickroom.QRflags & QR_ANONONLY)
2035 if (CC->quickroom.QRflags & QR_ANONOPT) {
2039 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2042 /* If we're only checking the validity of the request, return
2043 * success without creating the message.
2046 cprintf("%d %s\n", OK, buf);
2050 cprintf("%d send message\n", SEND_LISTING);
2052 /* Read in the message from the client. */
2053 if (CC->fake_postname[0])
2054 msg = make_message(&CC->usersupp, buf,
2055 CC->quickroom.QRname, b, e, format_type,
2057 else if (CC->fake_username[0])
2058 msg = make_message(&CC->usersupp, buf,
2059 CC->quickroom.QRname, b, e, format_type,
2062 msg = make_message(&CC->usersupp, buf,
2063 CC->quickroom.QRname, b, e, format_type, "");
2066 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2067 CtdlFreeMessage(msg);
2068 CC->fake_postname[0] = '\0';
2075 * message entry - mode 3 (raw)
2077 void cmd_ent3(char *entargs)
2083 unsigned char ch, which_field;
2084 struct usersupp tempUS;
2086 struct CtdlMessage *msg;
2089 if (CC->internal_pgm == 0) {
2090 cprintf("%d This command is for internal programs only.\n",
2095 /* See if there's a recipient, but make sure it's a real one */
2096 extract(recp, entargs, 1);
2097 for (a = 0; a < strlen(recp); ++a)
2098 if (!isprint(recp[a]))
2099 strcpy(&recp[a], &recp[a + 1]);
2100 while (isspace(recp[0]))
2101 strcpy(recp, &recp[1]);
2102 while (isspace(recp[strlen(recp) - 1]))
2103 recp[strlen(recp) - 1] = 0;
2105 /* If we're in Mail, check the recipient */
2106 if (strlen(recp) > 0) {
2107 e = alias(recp); /* alias and mail type */
2108 if ((recp[0] == 0) || (e == MES_ERROR)) {
2109 cprintf("%d Unknown address - cannot send message.\n",
2110 ERROR + NO_SUCH_USER);
2113 if (e == MES_LOCAL) {
2114 a = getuser(&tempUS, recp);
2116 cprintf("%d No such user.\n",
2117 ERROR + NO_SUCH_USER);
2123 /* At this point, message has been approved. */
2124 if (extract_int(entargs, 0) == 0) {
2125 cprintf("%d OK to send\n", OK);
2129 msglen = extract_long(entargs, 2);
2130 msg = mallok(sizeof(struct CtdlMessage));
2132 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2136 memset(msg, 0, sizeof(struct CtdlMessage));
2137 tempbuf = mallok(msglen);
2138 if (tempbuf == NULL) {
2139 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2144 cprintf("%d %ld\n", SEND_BINARY, msglen);
2146 client_read(&ch, 1); /* 0xFF magic number */
2147 msg->cm_magic = CTDLMESSAGE_MAGIC;
2148 client_read(&ch, 1); /* anon type */
2149 msg->cm_anon_type = ch;
2150 client_read(&ch, 1); /* format type */
2151 msg->cm_format_type = ch;
2152 msglen = msglen - 3;
2154 while (msglen > 0) {
2155 client_read(&which_field, 1);
2156 if (!isalpha(which_field)) valid_msg = 0;
2160 client_read(&ch, 1);
2162 a = strlen(tempbuf);
2165 } while ( (ch != 0) && (msglen > 0) );
2167 msg->cm_fields[which_field] = strdoop(tempbuf);
2170 msg->cm_flags = CM_SKIP_HOOKS;
2171 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2172 CtdlFreeMessage(msg);
2178 * API function to delete messages which match a set of criteria
2179 * (returns the actual number of messages deleted)
2181 int CtdlDeleteMessages(char *room_name, /* which room */
2182 long dmsgnum, /* or "0" for any */
2183 char *content_type /* or NULL for any */
2187 struct quickroom qrbuf;
2188 struct cdbdata *cdbfr;
2189 long *msglist = NULL;
2192 int num_deleted = 0;
2194 struct SuppMsgInfo smi;
2196 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2197 room_name, dmsgnum, content_type);
2199 /* get room record, obtaining a lock... */
2200 if (lgetroom(&qrbuf, room_name) != 0) {
2201 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2203 return (0); /* room not found */
2205 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2207 if (cdbfr != NULL) {
2208 msglist = mallok(cdbfr->len);
2209 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2210 num_msgs = cdbfr->len / sizeof(long);
2214 for (i = 0; i < num_msgs; ++i) {
2217 /* Set/clear a bit for each criterion */
2219 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2220 delete_this |= 0x01;
2222 if (content_type == NULL) {
2223 delete_this |= 0x02;
2225 GetSuppMsgInfo(&smi, msglist[i]);
2226 if (!strcasecmp(smi.smi_content_type,
2228 delete_this |= 0x02;
2232 /* Delete message only if all bits are set */
2233 if (delete_this == 0x03) {
2234 AdjRefCount(msglist[i], -1);
2240 num_msgs = sort_msglist(msglist, num_msgs);
2241 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2242 msglist, (num_msgs * sizeof(long)));
2244 qrbuf.QRhighest = msglist[num_msgs - 1];
2248 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2249 return (num_deleted);
2255 * Delete message from current room
2257 void cmd_dele(char *delstr)
2262 getuser(&CC->usersupp, CC->curr_user);
2263 if ((CC->usersupp.axlevel < 6)
2264 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2265 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2266 && (!(CC->internal_pgm))) {
2267 cprintf("%d Higher access required.\n",
2268 ERROR + HIGHER_ACCESS_REQUIRED);
2271 delnum = extract_long(delstr, 0);
2273 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2276 cprintf("%d %d message%s deleted.\n", OK,
2277 num_deleted, ((num_deleted != 1) ? "s" : ""));
2279 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2285 * move or copy a message to another room
2287 void cmd_move(char *args)
2291 struct quickroom qtemp;
2295 num = extract_long(args, 0);
2296 extract(targ, args, 1);
2297 targ[ROOMNAMELEN - 1] = 0;
2298 is_copy = extract_int(args, 2);
2300 getuser(&CC->usersupp, CC->curr_user);
2301 if ((CC->usersupp.axlevel < 6)
2302 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2303 cprintf("%d Higher access required.\n",
2304 ERROR + HIGHER_ACCESS_REQUIRED);
2308 if (getroom(&qtemp, targ) != 0) {
2309 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2313 err = CtdlSaveMsgPointerInRoom(targ, num,
2314 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2316 cprintf("%d Cannot store message in %s: error %d\n",
2321 /* Now delete the message from the source room,
2322 * if this is a 'move' rather than a 'copy' operation.
2324 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2326 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2332 * GetSuppMsgInfo() - Get the supplementary record for a message
2334 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2337 struct cdbdata *cdbsmi;
2340 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2341 smibuf->smi_msgnum = msgnum;
2342 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2344 /* Use the negative of the message number for its supp record index */
2345 TheIndex = (0L - msgnum);
2347 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2348 if (cdbsmi == NULL) {
2349 return; /* record not found; go with defaults */
2351 memcpy(smibuf, cdbsmi->ptr,
2352 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2353 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2360 * PutSuppMsgInfo() - (re)write supplementary record for a message
2362 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2366 /* Use the negative of the message number for its supp record index */
2367 TheIndex = (0L - smibuf->smi_msgnum);
2369 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2370 smibuf->smi_msgnum, smibuf->smi_refcount);
2372 cdb_store(CDB_MSGMAIN,
2373 &TheIndex, sizeof(long),
2374 smibuf, sizeof(struct SuppMsgInfo));
2379 * AdjRefCount - change the reference count for a message;
2380 * delete the message if it reaches zero
2382 void AdjRefCount(long msgnum, int incr)
2385 struct SuppMsgInfo smi;
2388 /* This is a *tight* critical section; please keep it that way, as
2389 * it may get called while nested in other critical sections.
2390 * Complicating this any further will surely cause deadlock!
2392 begin_critical_section(S_SUPPMSGMAIN);
2393 GetSuppMsgInfo(&smi, msgnum);
2394 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2395 msgnum, smi.smi_refcount);
2396 smi.smi_refcount += incr;
2397 PutSuppMsgInfo(&smi);
2398 end_critical_section(S_SUPPMSGMAIN);
2399 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2400 msgnum, smi.smi_refcount);
2402 /* If the reference count is now zero, delete the message
2403 * (and its supplementary record as well).
2405 if (smi.smi_refcount == 0) {
2406 lprintf(9, "Deleting message <%ld>\n", msgnum);
2408 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2409 delnum = (0L - msgnum);
2410 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2415 * Write a generic object to this room
2417 * Note: this could be much more efficient. Right now we use two temporary
2418 * files, and still pull the message into memory as with all others.
2420 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2421 char *content_type, /* MIME type of this object */
2422 char *tempfilename, /* Where to fetch it from */
2423 struct usersupp *is_mailbox, /* Mailbox room? */
2424 int is_binary, /* Is encoding necessary? */
2425 int is_unique, /* Del others of this type? */
2426 unsigned int flags /* Internal save flags */
2431 char filename[PATH_MAX];
2434 struct quickroom qrbuf;
2435 char roomname[ROOMNAMELEN];
2436 struct CtdlMessage *msg;
2439 if (is_mailbox != NULL)
2440 MailboxName(roomname, is_mailbox, req_room);
2442 safestrncpy(roomname, req_room, sizeof(roomname));
2443 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2445 strcpy(filename, tmpnam(NULL));
2446 fp = fopen(filename, "w");
2450 tempfp = fopen(tempfilename, "r");
2451 if (tempfp == NULL) {
2457 fprintf(fp, "Content-type: %s\n", content_type);
2458 lprintf(9, "Content-type: %s\n", content_type);
2460 if (is_binary == 0) {
2461 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2462 while (ch = getc(tempfp), ch > 0)
2468 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2471 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2472 tempfilename, filename);
2476 lprintf(9, "Allocating\n");
2477 msg = mallok(sizeof(struct CtdlMessage));
2478 memset(msg, 0, sizeof(struct CtdlMessage));
2479 msg->cm_magic = CTDLMESSAGE_MAGIC;
2480 msg->cm_anon_type = MES_NORMAL;
2481 msg->cm_format_type = 4;
2482 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2483 msg->cm_fields['O'] = strdoop(req_room);
2484 msg->cm_fields['N'] = strdoop(config.c_nodename);
2485 msg->cm_fields['H'] = strdoop(config.c_humannode);
2486 msg->cm_flags = flags;
2488 lprintf(9, "Loading\n");
2489 fp = fopen(filename, "rb");
2490 fseek(fp, 0L, SEEK_END);
2493 msg->cm_fields['M'] = mallok(len);
2494 fread(msg->cm_fields['M'], len, 1, fp);
2498 /* Create the requested room if we have to. */
2499 if (getroom(&qrbuf, roomname) != 0) {
2500 create_room(roomname,
2501 ( (is_mailbox != NULL) ? 4 : 3 ),
2504 /* If the caller specified this object as unique, delete all
2505 * other objects of this type that are currently in the room.
2508 lprintf(9, "Deleted %d other msgs of this type\n",
2509 CtdlDeleteMessages(roomname, 0L, content_type));
2511 /* Now write the data */
2512 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2513 CtdlFreeMessage(msg);
2521 void CtdlGetSysConfigBackend(long msgnum) {
2522 config_msgnum = msgnum;
2526 char *CtdlGetSysConfig(char *sysconfname) {
2527 char hold_rm[ROOMNAMELEN];
2530 struct CtdlMessage *msg;
2533 strcpy(hold_rm, CC->quickroom.QRname);
2534 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2535 getroom(&CC->quickroom, hold_rm);
2540 /* We want the last (and probably only) config in this room */
2541 begin_critical_section(S_CONFIG);
2542 config_msgnum = (-1L);
2543 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2544 CtdlGetSysConfigBackend);
2545 msgnum = config_msgnum;
2546 end_critical_section(S_CONFIG);
2552 msg = CtdlFetchMessage(msgnum);
2554 conf = strdoop(msg->cm_fields['M']);
2555 CtdlFreeMessage(msg);
2562 getroom(&CC->quickroom, hold_rm);
2564 lprintf(9, "eggstracting...\n");
2565 if (conf != NULL) do {
2566 extract_token(buf, conf, 0, '\n');
2567 lprintf(9, "eggstracted <%s>\n", buf);
2568 strcpy(conf, &conf[strlen(buf)+1]);
2569 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2574 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2575 char temp[PATH_MAX];
2578 strcpy(temp, tmpnam(NULL));
2580 fp = fopen(temp, "w");
2581 if (fp == NULL) return;
2582 fprintf(fp, "%s", sysconfdata);
2585 /* this handy API function does all the work for us */
2586 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);