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. (returns om_* values found in msgbase.h)
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"))
825 || (strlen(cbtype)==0) ) {
830 if (ch==10) omprintf("%s", nl);
831 else omprintf("%c", ch);
834 else if (!strcasecmp(cbtype, "text/html")) {
835 ptr = html_to_ascii(content, 80, 0);
840 if (ch==10) omprintf("%s", nl);
841 else omprintf("%c", ch);
845 else if (strncasecmp(cbtype, "multipart/", 10)) {
846 omprintf("Part %s: %s (%s) (%d bytes)%s",
847 partnum, filename, cbtype, length, nl);
851 /* END NESTED FUNCTION fixed_output() */
853 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
857 sprintf(mid, "%ld", msg_num);
858 nl = (crlf ? "\r\n" : "\n");
860 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
861 if (do_proto) cprintf("%d Not logged in.\n",
862 ERROR + NOT_LOGGED_IN);
863 return(om_not_logged_in);
866 /* FIX ... small security issue
867 * We need to check to make sure the requested message is actually
868 * in the current room, and set msg_ok to 1 only if it is. This
869 * functionality is currently missing because I'm in a hurry to replace
870 * broken production code with nonbroken pre-beta code. :( -- ajc
873 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
875 return(om_no_such_msg);
880 * Fetch the message from disk
882 TheMessage = CtdlFetchMessage(msg_num);
883 if (TheMessage == NULL) {
884 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
886 return(om_no_such_msg);
889 /* Are we downloading a MIME component? */
890 if (mode == MT_DOWNLOAD) {
891 if (TheMessage->cm_format_type != FMT_RFC822) {
893 cprintf("%d This is not a MIME message.\n",
895 } else if (CC->download_fp != NULL) {
896 if (do_proto) cprintf(
897 "%d You already have a download open.\n",
900 /* Parse the message text component */
901 mptr = TheMessage->cm_fields['M'];
902 mime_parser(mptr, NULL, *mime_download);
903 /* If there's no file open by this time, the requested
904 * section wasn't found, so print an error
906 if (CC->download_fp == NULL) {
907 if (do_proto) cprintf(
908 "%d Section %s not found.\n",
909 ERROR + FILE_NOT_FOUND,
913 CtdlFreeMessage(TheMessage);
914 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
917 /* now for the user-mode message reading loops */
918 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
920 /* Tell the client which format type we're using. If this is a
921 * MIME message, *lie* about it and tell the user it's fixed-format.
923 if (mode == MT_CITADEL) {
924 if (TheMessage->cm_format_type == FMT_RFC822) {
925 if (do_proto) cprintf("type=1\n");
928 if (do_proto) cprintf("type=%d\n",
929 TheMessage->cm_format_type);
933 /* nhdr=yes means that we're only displaying headers, no body */
934 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
935 if (do_proto) cprintf("nhdr=yes\n");
938 /* begin header processing loop for Citadel message format */
940 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
942 strcpy(display_name, "<unknown>");
943 if (TheMessage->cm_fields['A']) {
944 strcpy(buf, TheMessage->cm_fields['A']);
945 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
946 if (TheMessage->cm_anon_type == MES_ANON)
947 strcpy(display_name, "****");
948 else if (TheMessage->cm_anon_type == MES_AN2)
949 strcpy(display_name, "anonymous");
951 strcpy(display_name, buf);
953 && ((TheMessage->cm_anon_type == MES_ANON)
954 || (TheMessage->cm_anon_type == MES_AN2))) {
955 sprintf(&display_name[strlen(display_name)],
960 strcpy(allkeys, FORDER);
961 for (i=0; i<strlen(allkeys); ++i) {
962 k = (int) allkeys[i];
964 if (TheMessage->cm_fields[k] != NULL) {
966 if (do_proto) cprintf("%s=%s\n",
971 if (do_proto) cprintf("%s=%s\n",
973 TheMessage->cm_fields[k]
982 /* begin header processing loop for RFC822 transfer format */
986 strcpy(snode, NODENAME);
987 strcpy(lnode, HUMANNODE);
988 if (mode == MT_RFC822) {
989 for (i = 0; i < 256; ++i) {
990 if (TheMessage->cm_fields[i]) {
991 mptr = TheMessage->cm_fields[i];
995 } else if (i == 'P') {
996 omprintf("Path: %s%s", mptr, nl);
997 for (a = 0; a < strlen(mptr); ++a) {
998 if (mptr[a] == '!') {
999 strcpy(mptr, &mptr[a + 1]);
1003 strcpy(suser, mptr);
1004 } else if (i == 'U')
1005 omprintf("Subject: %s%s", mptr, nl);
1009 strcpy(lnode, mptr);
1011 omprintf("X-Citadel-Room: %s%s",
1014 strcpy(snode, mptr);
1016 omprintf("To: %s%s", mptr, nl);
1017 else if (i == 'T') {
1019 omprintf("Date: %s", asctime(localtime(&xtime)));
1025 if (mode == MT_RFC822) {
1026 if (!strcasecmp(snode, NODENAME)) {
1027 strcpy(snode, FQDN);
1029 omprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
1030 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1031 omprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1032 omprintf("Organization: %s%s", lnode, nl);
1035 /* end header processing loop ... at this point, we're in the text */
1037 mptr = TheMessage->cm_fields['M'];
1039 /* Tell the client about the MIME parts in this message */
1040 if (TheMessage->cm_format_type == FMT_RFC822) {
1041 if (mode == MT_CITADEL) {
1042 mime_parser(mptr, NULL, *list_this_part);
1044 else if (mode == MT_MIME) { /* list parts only */
1045 mime_parser(mptr, NULL, *list_this_part);
1046 if (do_proto) cprintf("000\n");
1047 CtdlFreeMessage(TheMessage);
1050 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1051 /* FIX ... we have to put some code in here to avoid
1052 * printing duplicate header information when both
1053 * Citadel and RFC822 headers exist. Preference should
1054 * probably be given to the RFC822 headers.
1056 while (ch=*(mptr++), ch!=0) {
1058 else if (ch==10) omprintf("%s", nl);
1059 else omprintf("%c", ch);
1061 if (do_proto) cprintf("000\n");
1062 CtdlFreeMessage(TheMessage);
1068 if (do_proto) cprintf("000\n");
1069 CtdlFreeMessage(TheMessage);
1073 /* signify start of msg text */
1074 if (mode == MT_CITADEL)
1075 if (do_proto) cprintf("text\n");
1076 if (mode == MT_RFC822) {
1080 /* If the format type on disk is 1 (fixed-format), then we want
1081 * everything to be output completely literally ... regardless of
1082 * what message transfer format is in use.
1084 if (TheMessage->cm_format_type == FMT_FIXED) {
1086 while (ch = *mptr++, ch > 0) {
1089 if ((ch == 10) || (strlen(buf) > 250)) {
1090 omprintf("%s%s", buf, nl);
1093 buf[strlen(buf) + 1] = 0;
1094 buf[strlen(buf)] = ch;
1097 if (strlen(buf) > 0)
1098 omprintf("%s%s", buf, nl);
1101 /* If the message on disk is format 0 (Citadel vari-format), we
1102 * output using the formatter at 80 columns. This is the final output
1103 * form if the transfer format is RFC822, but if the transfer format
1104 * is Citadel proprietary, it'll still work, because the indentation
1105 * for new paragraphs is correct and the client will reformat the
1106 * message to the reader's screen width.
1108 if (TheMessage->cm_format_type == FMT_CITADEL) {
1112 /* If the message on disk is format 4 (MIME), we've gotta hand it
1113 * off to the MIME parser. The client has already been told that
1114 * this message is format 1 (fixed format), so the callback function
1115 * we use will display those parts as-is.
1117 if (TheMessage->cm_format_type == FMT_RFC822) {
1118 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1119 memset(ma, 0, sizeof(struct ma_info));
1120 mime_parser(mptr, NULL, *fixed_output);
1123 /* now we're done */
1124 if (do_proto) cprintf("000\n");
1125 CtdlFreeMessage(TheMessage);
1132 * display a message (mode 0 - Citadel proprietary)
1134 void cmd_msg0(char *cmdbuf)
1137 int headers_only = 0;
1139 msgid = extract_long(cmdbuf, 0);
1140 headers_only = extract_int(cmdbuf, 1);
1142 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, NULL, -1, 0);
1148 * display a message (mode 2 - RFC822)
1150 void cmd_msg2(char *cmdbuf)
1153 int headers_only = 0;
1155 msgid = extract_long(cmdbuf, 0);
1156 headers_only = extract_int(cmdbuf, 1);
1158 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, NULL, -1, 1);
1164 * display a message (mode 3 - IGnet raw format - internal programs only)
1166 void cmd_msg3(char *cmdbuf)
1169 struct CtdlMessage *msg;
1172 if (CC->internal_pgm == 0) {
1173 cprintf("%d This command is for internal programs only.\n",
1178 msgnum = extract_long(cmdbuf, 0);
1179 msg = CtdlFetchMessage(msgnum);
1181 cprintf("%d Message %ld not found.\n",
1186 serialize_message(&smr, msg);
1187 CtdlFreeMessage(msg);
1190 cprintf("%d Unable to serialize message\n",
1191 ERROR+INTERNAL_ERROR);
1195 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1196 client_write(smr.ser, smr.len);
1203 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1205 void cmd_msg4(char *cmdbuf)
1209 msgid = extract_long(cmdbuf, 0);
1210 CtdlOutputMsg(msgid, MT_MIME, 0, 1, NULL, -1, 0);
1214 * Open a component of a MIME message as a download file
1216 void cmd_opna(char *cmdbuf)
1220 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1222 msgid = extract_long(cmdbuf, 0);
1223 extract(desired_section, cmdbuf, 1);
1225 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, NULL, -1, 1);
1230 * Save a message pointer into a specified room
1231 * (Returns 0 for success, nonzero for failure)
1232 * roomname may be NULL to use the current room
1234 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1236 char hold_rm[ROOMNAMELEN];
1237 struct cdbdata *cdbfr;
1240 long highest_msg = 0L;
1241 struct CtdlMessage *msg = NULL;
1243 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1244 roomname, msgid, flags);
1246 strcpy(hold_rm, CC->quickroom.QRname);
1248 /* We may need to check to see if this message is real */
1249 if ( (flags & SM_VERIFY_GOODNESS)
1250 || (flags & SM_DO_REPL_CHECK)
1252 msg = CtdlFetchMessage(msgid);
1253 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1256 /* Perform replication checks if necessary */
1257 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1259 if (getroom(&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 if (ReplicationChecks(msg) != 0) {
1268 getroom(&CC->quickroom, hold_rm);
1269 if (msg != NULL) CtdlFreeMessage(msg);
1270 lprintf(9, "Did replication, and newer exists\n");
1275 /* Now the regular stuff */
1276 if (lgetroom(&CC->quickroom,
1277 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1279 lprintf(9, "No such room <%s>\n", roomname);
1280 if (msg != NULL) CtdlFreeMessage(msg);
1281 return(ERROR + ROOM_NOT_FOUND);
1284 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1285 if (cdbfr == NULL) {
1289 msglist = mallok(cdbfr->len);
1290 if (msglist == NULL)
1291 lprintf(3, "ERROR malloc msglist!\n");
1292 num_msgs = cdbfr->len / sizeof(long);
1293 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1298 /* Make sure the message doesn't already exist in this room. It
1299 * is absolutely taboo to have more than one reference to the same
1300 * message in a room.
1302 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1303 if (msglist[i] == msgid) {
1304 lputroom(&CC->quickroom); /* unlock the room */
1305 getroom(&CC->quickroom, hold_rm);
1306 if (msg != NULL) CtdlFreeMessage(msg);
1307 return(ERROR + ALREADY_EXISTS);
1311 /* Now add the new message */
1313 msglist = reallok(msglist,
1314 (num_msgs * sizeof(long)));
1316 if (msglist == NULL) {
1317 lprintf(3, "ERROR: can't realloc message list!\n");
1319 msglist[num_msgs - 1] = msgid;
1321 /* Sort the message list, so all the msgid's are in order */
1322 num_msgs = sort_msglist(msglist, num_msgs);
1324 /* Determine the highest message number */
1325 highest_msg = msglist[num_msgs - 1];
1327 /* Write it back to disk. */
1328 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1329 msglist, num_msgs * sizeof(long));
1331 /* Free up the memory we used. */
1334 /* Update the highest-message pointer and unlock the room. */
1335 CC->quickroom.QRhighest = highest_msg;
1336 lputroom(&CC->quickroom);
1337 getroom(&CC->quickroom, hold_rm);
1339 /* Bump the reference count for this message. */
1340 if ((flags & SM_DONT_BUMP_REF)==0) {
1341 AdjRefCount(msgid, +1);
1344 /* Return success. */
1345 if (msg != NULL) CtdlFreeMessage(msg);
1352 * Message base operation to send a message to the master file
1353 * (returns new message number)
1355 * This is the back end for CtdlSaveMsg() and should not be directly
1356 * called by server-side modules.
1359 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1360 int generate_id, /* generate 'I' field? */
1361 FILE *save_a_copy) /* save a copy to disk? */
1368 /* Get a new message number */
1369 newmsgid = get_new_message_number();
1370 sprintf(msgidbuf, "%ld", newmsgid);
1373 msg->cm_fields['I'] = strdoop(msgidbuf);
1376 serialize_message(&smr, msg);
1379 cprintf("%d Unable to serialize message\n",
1380 ERROR+INTERNAL_ERROR);
1384 /* Write our little bundle of joy into the message base */
1385 begin_critical_section(S_MSGMAIN);
1386 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1387 smr.ser, smr.len) < 0) {
1388 lprintf(2, "Can't store message\n");
1393 end_critical_section(S_MSGMAIN);
1395 /* If the caller specified that a copy should be saved to a particular
1396 * file handle, do that now too.
1398 if (save_a_copy != NULL) {
1399 fwrite(smr.ser, smr.len, 1, save_a_copy);
1402 /* Free the memory we used for the serialized message */
1405 /* Return the *local* message ID to the caller
1406 * (even if we're storing an incoming network message)
1414 * Serialize a struct CtdlMessage into the format used on disk and network.
1416 * This function loads up a "struct ser_ret" (defined in server.h) which
1417 * contains the length of the serialized message and a pointer to the
1418 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1420 void serialize_message(struct ser_ret *ret, /* return values */
1421 struct CtdlMessage *msg) /* unserialized msg */
1425 static char *forder = FORDER;
1427 if (is_valid_message(msg) == 0) return; /* self check */
1430 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1431 ret->len = ret->len +
1432 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1434 lprintf(9, "calling malloc\n");
1435 ret->ser = mallok(ret->len);
1436 if (ret->ser == NULL) {
1442 ret->ser[1] = msg->cm_anon_type;
1443 ret->ser[2] = msg->cm_format_type;
1446 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1447 ret->ser[wlen++] = (char)forder[i];
1448 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1449 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1451 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1460 * Back end for the ReplicationChecks() function
1462 void check_repl(long msgnum) {
1463 struct CtdlMessage *msg;
1464 time_t timestamp = (-1L);
1466 lprintf(9, "check_repl() found message %ld\n", msgnum);
1467 msg = CtdlFetchMessage(msgnum);
1468 if (msg == NULL) return;
1469 if (msg->cm_fields['T'] != NULL) {
1470 timestamp = atol(msg->cm_fields['T']);
1472 CtdlFreeMessage(msg);
1474 if (timestamp > msg_repl->highest) {
1475 msg_repl->highest = timestamp; /* newer! */
1476 lprintf(9, "newer!\n");
1479 lprintf(9, "older!\n");
1481 /* Existing isn't newer? Then delete the old one(s). */
1482 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1487 * Check to see if any messages already exist which carry the same Extended ID
1491 * -> With older timestamps: delete them and return 0. Message will be saved.
1492 * -> With newer timestamps: return 1. Message save will be aborted.
1494 int ReplicationChecks(struct CtdlMessage *msg) {
1495 struct CtdlMessage *template;
1498 lprintf(9, "ReplicationChecks() started\n");
1499 /* No extended id? Don't do anything. */
1500 if (msg->cm_fields['E'] == NULL) return 0;
1501 if (strlen(msg->cm_fields['E']) == 0) return 0;
1502 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1504 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1505 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1506 msg_repl->highest = atol(msg->cm_fields['T']);
1508 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1509 memset(template, 0, sizeof(struct CtdlMessage));
1510 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1512 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1514 /* If a newer message exists with the same Extended ID, abort
1517 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1521 CtdlFreeMessage(template);
1522 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1530 * Save a message to disk
1532 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1533 char *rec, /* Recipient (mail) */
1534 char *force, /* force a particular room? */
1535 int mailtype, /* local or remote type */
1536 int generate_id) /* 1 = generate 'I' field */
1539 char hold_rm[ROOMNAMELEN];
1540 char actual_rm[ROOMNAMELEN];
1541 char force_room[ROOMNAMELEN];
1542 char content_type[256]; /* We have to learn this */
1543 char recipient[256];
1546 struct usersupp userbuf;
1548 struct SuppMsgInfo smi;
1549 FILE *network_fp = NULL;
1550 static int seqnum = 1;
1551 struct CtdlMessage *imsg;
1554 lprintf(9, "CtdlSaveMsg() called\n");
1555 if (is_valid_message(msg) == 0) return(-1); /* self check */
1557 /* If this message has no timestamp, we take the liberty of
1558 * giving it one, right now.
1560 if (msg->cm_fields['T'] == NULL) {
1561 lprintf(9, "Generating timestamp\n");
1562 sprintf(aaa, "%ld", time(NULL));
1563 msg->cm_fields['T'] = strdoop(aaa);
1566 /* If this message has no path, we generate one.
1568 if (msg->cm_fields['P'] == NULL) {
1569 lprintf(9, "Generating path\n");
1570 if (msg->cm_fields['A'] != NULL) {
1571 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1572 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1573 if (isspace(msg->cm_fields['P'][a])) {
1574 msg->cm_fields['P'][a] = ' ';
1579 msg->cm_fields['P'] = strdoop("unknown");
1583 strcpy(force_room, force);
1585 /* Strip non-printable characters out of the recipient name */
1586 strcpy(recipient, rec);
1587 for (a = 0; a < strlen(recipient); ++a)
1588 if (!isprint(recipient[a]))
1589 strcpy(&recipient[a], &recipient[a + 1]);
1591 /* Learn about what's inside, because it's what's inside that counts */
1592 lprintf(9, "Learning what's inside\n");
1593 if (msg->cm_fields['M'] == NULL) {
1594 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1597 switch (msg->cm_format_type) {
1599 strcpy(content_type, "text/x-citadel-variformat");
1602 strcpy(content_type, "text/plain");
1605 strcpy(content_type, "text/plain");
1606 /* advance past header fields */
1607 mptr = msg->cm_fields['M'];
1610 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1611 safestrncpy(content_type, mptr,
1612 sizeof(content_type));
1613 strcpy(content_type, &content_type[14]);
1614 for (a = 0; a < strlen(content_type); ++a)
1615 if ((content_type[a] == ';')
1616 || (content_type[a] == ' ')
1617 || (content_type[a] == 13)
1618 || (content_type[a] == 10))
1619 content_type[a] = 0;
1626 /* Goto the correct room */
1627 lprintf(9, "Switching rooms\n");
1628 strcpy(hold_rm, CC->quickroom.QRname);
1629 strcpy(actual_rm, CC->quickroom.QRname);
1631 /* If the user is a twit, move to the twit room for posting */
1632 lprintf(9, "Handling twit stuff\n");
1634 if (CC->usersupp.axlevel == 2) {
1635 strcpy(hold_rm, actual_rm);
1636 strcpy(actual_rm, config.c_twitroom);
1640 /* ...or if this message is destined for Aide> then go there. */
1641 if (strlen(force_room) > 0) {
1642 strcpy(actual_rm, force_room);
1645 lprintf(9, "Possibly relocating\n");
1646 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1647 getroom(&CC->quickroom, actual_rm);
1651 * If this message has no O (room) field, generate one.
1653 if (msg->cm_fields['O'] == NULL) {
1654 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1657 /* Perform "before save" hooks (aborting if any return nonzero) */
1658 lprintf(9, "Performing before-save hooks\n");
1659 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1661 /* If this message has an Extended ID, perform replication checks */
1662 lprintf(9, "Performing replication checks\n");
1663 if (ReplicationChecks(msg) > 0) return(-1);
1665 /* Network mail - send a copy to the network program. */
1666 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1667 lprintf(9, "Sending network spool\n");
1668 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1669 (long) getpid(), CC->cs_pid, ++seqnum);
1670 lprintf(9, "Saving a copy to %s\n", aaa);
1671 network_fp = fopen(aaa, "ab+");
1672 if (network_fp == NULL)
1673 lprintf(2, "ERROR: %s\n", strerror(errno));
1676 /* Save it to disk */
1677 lprintf(9, "Saving to disk\n");
1678 newmsgid = send_message(msg, generate_id, network_fp);
1679 if (network_fp != NULL) {
1681 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1684 if (newmsgid <= 0L) return(-1);
1686 /* Write a supplemental message info record. This doesn't have to
1687 * be a critical section because nobody else knows about this message
1690 lprintf(9, "Creating SuppMsgInfo record\n");
1691 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1692 smi.smi_msgnum = newmsgid;
1693 smi.smi_refcount = 0;
1694 safestrncpy(smi.smi_content_type, content_type, 64);
1695 PutSuppMsgInfo(&smi);
1697 /* Now figure out where to store the pointers */
1698 lprintf(9, "Storing pointers\n");
1700 /* If this is being done by the networker delivering a private
1701 * message, we want to BYPASS saving the sender's copy (because there
1702 * is no local sender; it would otherwise go to the Trashcan).
1704 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1705 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1708 /* For internet mail, drop a copy in the outbound queue room */
1709 if (mailtype == MES_INTERNET) {
1710 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1713 /* Bump this user's messages posted counter. */
1714 lprintf(9, "Updating user\n");
1715 lgetuser(&CC->usersupp, CC->curr_user);
1716 CC->usersupp.posted = CC->usersupp.posted + 1;
1717 lputuser(&CC->usersupp);
1719 /* If this is private, local mail, make a copy in the
1720 * recipient's mailbox and bump the reference count.
1722 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1723 if (getuser(&userbuf, recipient) == 0) {
1724 lprintf(9, "Delivering private mail\n");
1725 MailboxName(actual_rm, &userbuf, MAILROOM);
1726 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1730 /* Perform "after save" hooks */
1731 lprintf(9, "Performing after-save hooks\n");
1732 PerformMessageHooks(msg, EVT_AFTERSAVE);
1735 lprintf(9, "Returning to original room\n");
1736 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1737 getroom(&CC->quickroom, hold_rm);
1739 /* For internet mail, generate delivery instructions
1740 * (Yes, this is recursive! Deal with it!)
1742 if (mailtype == MES_INTERNET) {
1743 lprintf(9, "Generating delivery instructions\n");
1744 instr = mallok(2048);
1746 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1748 SPOOLMIME, newmsgid, time(NULL), recipient );
1750 imsg = mallok(sizeof(struct CtdlMessage));
1751 memset(imsg, 0, sizeof(struct CtdlMessage));
1752 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1753 imsg->cm_anon_type = MES_NORMAL;
1754 imsg->cm_format_type = FMT_RFC822;
1755 imsg->cm_fields['A'] = strdoop("Citadel");
1756 imsg->cm_fields['M'] = instr;
1757 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1758 CtdlFreeMessage(imsg);
1767 * Convenience function for generating small administrative messages.
1769 void quickie_message(char *from, char *to, char *room, char *text)
1771 struct CtdlMessage *msg;
1773 msg = mallok(sizeof(struct CtdlMessage));
1774 memset(msg, 0, sizeof(struct CtdlMessage));
1775 msg->cm_magic = CTDLMESSAGE_MAGIC;
1776 msg->cm_anon_type = MES_NORMAL;
1777 msg->cm_format_type = 0;
1778 msg->cm_fields['A'] = strdoop(from);
1779 msg->cm_fields['O'] = strdoop(room);
1780 msg->cm_fields['N'] = strdoop(NODENAME);
1782 msg->cm_fields['R'] = strdoop(to);
1783 msg->cm_fields['M'] = strdoop(text);
1785 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1786 CtdlFreeMessage(msg);
1787 syslog(LOG_NOTICE, text);
1793 * Back end function used by make_message() and similar functions
1795 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1796 size_t maxlen, /* maximum message length */
1797 char *exist /* if non-null, append to it;
1798 exist is ALWAYS freed */
1801 size_t message_len = 0;
1802 size_t buffer_len = 0;
1806 if (exist == NULL) {
1810 m = reallok(exist, strlen(exist) + 4096);
1811 if (m == NULL) phree(exist);
1814 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1821 /* read in the lines of message text one by one */
1823 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1825 /* augment the buffer if we have to */
1826 if ((message_len + strlen(buf) + 2) > buffer_len) {
1827 lprintf(9, "realloking\n");
1828 ptr = reallok(m, (buffer_len * 2) );
1829 if (ptr == NULL) { /* flush if can't allocate */
1830 while ( (client_gets(buf)>0) &&
1831 strcmp(buf, terminator)) ;;
1834 buffer_len = (buffer_len * 2);
1837 lprintf(9, "buffer_len is %d\n", buffer_len);
1841 if (append == NULL) append = m;
1842 while (strlen(append) > 0) ++append;
1843 strcpy(append, buf);
1844 strcat(append, "\n");
1845 message_len = message_len + strlen(buf) + 1;
1847 /* if we've hit the max msg length, flush the rest */
1848 if (message_len >= maxlen) {
1849 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1860 * Build a binary message to be saved on disk.
1863 struct CtdlMessage *make_message(
1864 struct usersupp *author, /* author's usersupp structure */
1865 char *recipient, /* NULL if it's not mail */
1866 char *room, /* room where it's going */
1867 int type, /* see MES_ types in header file */
1868 int net_type, /* see MES_ types in header file */
1869 int format_type, /* local or remote (see citadel.h) */
1870 char *fake_name) /* who we're masquerading as */
1876 struct CtdlMessage *msg;
1878 msg = mallok(sizeof(struct CtdlMessage));
1879 memset(msg, 0, sizeof(struct CtdlMessage));
1880 msg->cm_magic = CTDLMESSAGE_MAGIC;
1881 msg->cm_anon_type = type;
1882 msg->cm_format_type = format_type;
1884 /* Don't confuse the poor folks if it's not routed mail. */
1885 strcpy(dest_node, "");
1887 /* If net_type is MES_BINARY, split out the destination node. */
1888 if (net_type == MES_BINARY) {
1889 strcpy(dest_node, NODENAME);
1890 for (a = 0; a < strlen(recipient); ++a) {
1891 if (recipient[a] == '@') {
1893 strcpy(dest_node, &recipient[a + 1]);
1898 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1899 if (net_type == MES_INTERNET) {
1900 strcpy(dest_node, "internet");
1903 while (isspace(recipient[strlen(recipient) - 1]))
1904 recipient[strlen(recipient) - 1] = 0;
1906 sprintf(buf, "cit%ld", author->usernum); /* Path */
1907 msg->cm_fields['P'] = strdoop(buf);
1909 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1910 msg->cm_fields['T'] = strdoop(buf);
1912 if (fake_name[0]) /* author */
1913 msg->cm_fields['A'] = strdoop(fake_name);
1915 msg->cm_fields['A'] = strdoop(author->fullname);
1917 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1918 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1920 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1922 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1923 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1925 if (recipient[0] != 0)
1926 msg->cm_fields['R'] = strdoop(recipient);
1927 if (dest_node[0] != 0)
1928 msg->cm_fields['D'] = strdoop(dest_node);
1931 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1932 config.c_maxmsglen, NULL);
1943 * message entry - mode 0 (normal)
1945 void cmd_ent0(char *entargs)
1948 char recipient[256];
1950 int format_type = 0;
1951 char newusername[256];
1952 struct CtdlMessage *msg;
1956 struct usersupp tempUS;
1959 post = extract_int(entargs, 0);
1960 extract(recipient, entargs, 1);
1961 anon_flag = extract_int(entargs, 2);
1962 format_type = extract_int(entargs, 3);
1964 /* first check to make sure the request is valid. */
1966 if (!(CC->logged_in)) {
1967 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1970 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1971 cprintf("%d Need to be validated to enter ",
1972 ERROR + HIGHER_ACCESS_REQUIRED);
1973 cprintf("(except in %s> to sysop)\n", MAILROOM);
1976 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1977 cprintf("%d Need net privileges to enter here.\n",
1978 ERROR + HIGHER_ACCESS_REQUIRED);
1981 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1982 cprintf("%d Sorry, this is a read-only room.\n",
1983 ERROR + HIGHER_ACCESS_REQUIRED);
1990 if (CC->usersupp.axlevel < 6) {
1991 cprintf("%d You don't have permission to masquerade.\n",
1992 ERROR + HIGHER_ACCESS_REQUIRED);
1995 extract(newusername, entargs, 4);
1996 memset(CC->fake_postname, 0, 32);
1997 strcpy(CC->fake_postname, newusername);
1998 cprintf("%d Ok\n", OK);
2001 CC->cs_flags |= CS_POSTING;
2004 if (CC->quickroom.QRflags & QR_MAILBOX) {
2005 if (CC->usersupp.axlevel >= 2) {
2006 strcpy(buf, recipient);
2008 strcpy(buf, "sysop");
2009 e = alias(buf); /* alias and mail type */
2010 if ((buf[0] == 0) || (e == MES_ERROR)) {
2011 cprintf("%d Unknown address - cannot send message.\n",
2012 ERROR + NO_SUCH_USER);
2015 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2016 cprintf("%d Net privileges required for network mail.\n",
2017 ERROR + HIGHER_ACCESS_REQUIRED);
2020 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2021 && ((CC->usersupp.flags & US_INTERNET) == 0)
2022 && (!CC->internal_pgm)) {
2023 cprintf("%d You don't have access to Internet mail.\n",
2024 ERROR + HIGHER_ACCESS_REQUIRED);
2027 if (!strcasecmp(buf, "sysop")) {
2032 goto SKFALL; /* don't search local file */
2033 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2034 cprintf("%d Can't send mail to yourself!\n",
2035 ERROR + NO_SUCH_USER);
2038 /* Check to make sure the user exists; also get the correct
2039 * upper/lower casing of the name.
2041 a = getuser(&tempUS, buf);
2043 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2046 strcpy(buf, tempUS.fullname);
2049 SKFALL: b = MES_NORMAL;
2050 if (CC->quickroom.QRflags & QR_ANONONLY)
2052 if (CC->quickroom.QRflags & QR_ANONOPT) {
2056 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2059 /* If we're only checking the validity of the request, return
2060 * success without creating the message.
2063 cprintf("%d %s\n", OK, buf);
2067 cprintf("%d send message\n", SEND_LISTING);
2069 /* Read in the message from the client. */
2070 if (CC->fake_postname[0])
2071 msg = make_message(&CC->usersupp, buf,
2072 CC->quickroom.QRname, b, e, format_type,
2074 else if (CC->fake_username[0])
2075 msg = make_message(&CC->usersupp, buf,
2076 CC->quickroom.QRname, b, e, format_type,
2079 msg = make_message(&CC->usersupp, buf,
2080 CC->quickroom.QRname, b, e, format_type, "");
2083 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2084 CtdlFreeMessage(msg);
2085 CC->fake_postname[0] = '\0';
2092 * message entry - mode 3 (raw)
2094 void cmd_ent3(char *entargs)
2100 unsigned char ch, which_field;
2101 struct usersupp tempUS;
2103 struct CtdlMessage *msg;
2106 if (CC->internal_pgm == 0) {
2107 cprintf("%d This command is for internal programs only.\n",
2112 /* See if there's a recipient, but make sure it's a real one */
2113 extract(recp, entargs, 1);
2114 for (a = 0; a < strlen(recp); ++a)
2115 if (!isprint(recp[a]))
2116 strcpy(&recp[a], &recp[a + 1]);
2117 while (isspace(recp[0]))
2118 strcpy(recp, &recp[1]);
2119 while (isspace(recp[strlen(recp) - 1]))
2120 recp[strlen(recp) - 1] = 0;
2122 /* If we're in Mail, check the recipient */
2123 if (strlen(recp) > 0) {
2124 e = alias(recp); /* alias and mail type */
2125 if ((recp[0] == 0) || (e == MES_ERROR)) {
2126 cprintf("%d Unknown address - cannot send message.\n",
2127 ERROR + NO_SUCH_USER);
2130 if (e == MES_LOCAL) {
2131 a = getuser(&tempUS, recp);
2133 cprintf("%d No such user.\n",
2134 ERROR + NO_SUCH_USER);
2140 /* At this point, message has been approved. */
2141 if (extract_int(entargs, 0) == 0) {
2142 cprintf("%d OK to send\n", OK);
2146 msglen = extract_long(entargs, 2);
2147 msg = mallok(sizeof(struct CtdlMessage));
2149 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2153 memset(msg, 0, sizeof(struct CtdlMessage));
2154 tempbuf = mallok(msglen);
2155 if (tempbuf == NULL) {
2156 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2161 cprintf("%d %ld\n", SEND_BINARY, msglen);
2163 client_read(&ch, 1); /* 0xFF magic number */
2164 msg->cm_magic = CTDLMESSAGE_MAGIC;
2165 client_read(&ch, 1); /* anon type */
2166 msg->cm_anon_type = ch;
2167 client_read(&ch, 1); /* format type */
2168 msg->cm_format_type = ch;
2169 msglen = msglen - 3;
2171 while (msglen > 0) {
2172 client_read(&which_field, 1);
2173 if (!isalpha(which_field)) valid_msg = 0;
2177 client_read(&ch, 1);
2179 a = strlen(tempbuf);
2182 } while ( (ch != 0) && (msglen > 0) );
2184 msg->cm_fields[which_field] = strdoop(tempbuf);
2187 msg->cm_flags = CM_SKIP_HOOKS;
2188 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2189 CtdlFreeMessage(msg);
2195 * API function to delete messages which match a set of criteria
2196 * (returns the actual number of messages deleted)
2198 int CtdlDeleteMessages(char *room_name, /* which room */
2199 long dmsgnum, /* or "0" for any */
2200 char *content_type /* or NULL for any */
2204 struct quickroom qrbuf;
2205 struct cdbdata *cdbfr;
2206 long *msglist = NULL;
2209 int num_deleted = 0;
2211 struct SuppMsgInfo smi;
2213 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2214 room_name, dmsgnum, content_type);
2216 /* get room record, obtaining a lock... */
2217 if (lgetroom(&qrbuf, room_name) != 0) {
2218 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2220 return (0); /* room not found */
2222 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2224 if (cdbfr != NULL) {
2225 msglist = mallok(cdbfr->len);
2226 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2227 num_msgs = cdbfr->len / sizeof(long);
2231 for (i = 0; i < num_msgs; ++i) {
2234 /* Set/clear a bit for each criterion */
2236 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2237 delete_this |= 0x01;
2239 if (content_type == NULL) {
2240 delete_this |= 0x02;
2242 GetSuppMsgInfo(&smi, msglist[i]);
2243 if (!strcasecmp(smi.smi_content_type,
2245 delete_this |= 0x02;
2249 /* Delete message only if all bits are set */
2250 if (delete_this == 0x03) {
2251 AdjRefCount(msglist[i], -1);
2257 num_msgs = sort_msglist(msglist, num_msgs);
2258 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2259 msglist, (num_msgs * sizeof(long)));
2261 qrbuf.QRhighest = msglist[num_msgs - 1];
2265 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2266 return (num_deleted);
2272 * Delete message from current room
2274 void cmd_dele(char *delstr)
2279 getuser(&CC->usersupp, CC->curr_user);
2280 if ((CC->usersupp.axlevel < 6)
2281 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2282 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2283 && (!(CC->internal_pgm))) {
2284 cprintf("%d Higher access required.\n",
2285 ERROR + HIGHER_ACCESS_REQUIRED);
2288 delnum = extract_long(delstr, 0);
2290 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2293 cprintf("%d %d message%s deleted.\n", OK,
2294 num_deleted, ((num_deleted != 1) ? "s" : ""));
2296 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2302 * move or copy a message to another room
2304 void cmd_move(char *args)
2308 struct quickroom qtemp;
2312 num = extract_long(args, 0);
2313 extract(targ, args, 1);
2314 targ[ROOMNAMELEN - 1] = 0;
2315 is_copy = extract_int(args, 2);
2317 getuser(&CC->usersupp, CC->curr_user);
2318 if ((CC->usersupp.axlevel < 6)
2319 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2320 cprintf("%d Higher access required.\n",
2321 ERROR + HIGHER_ACCESS_REQUIRED);
2325 if (getroom(&qtemp, targ) != 0) {
2326 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2330 err = CtdlSaveMsgPointerInRoom(targ, num,
2331 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2333 cprintf("%d Cannot store message in %s: error %d\n",
2338 /* Now delete the message from the source room,
2339 * if this is a 'move' rather than a 'copy' operation.
2341 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2343 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2349 * GetSuppMsgInfo() - Get the supplementary record for a message
2351 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2354 struct cdbdata *cdbsmi;
2357 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2358 smibuf->smi_msgnum = msgnum;
2359 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2361 /* Use the negative of the message number for its supp record index */
2362 TheIndex = (0L - msgnum);
2364 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2365 if (cdbsmi == NULL) {
2366 return; /* record not found; go with defaults */
2368 memcpy(smibuf, cdbsmi->ptr,
2369 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2370 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2377 * PutSuppMsgInfo() - (re)write supplementary record for a message
2379 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2383 /* Use the negative of the message number for its supp record index */
2384 TheIndex = (0L - smibuf->smi_msgnum);
2386 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2387 smibuf->smi_msgnum, smibuf->smi_refcount);
2389 cdb_store(CDB_MSGMAIN,
2390 &TheIndex, sizeof(long),
2391 smibuf, sizeof(struct SuppMsgInfo));
2396 * AdjRefCount - change the reference count for a message;
2397 * delete the message if it reaches zero
2399 void AdjRefCount(long msgnum, int incr)
2402 struct SuppMsgInfo smi;
2405 /* This is a *tight* critical section; please keep it that way, as
2406 * it may get called while nested in other critical sections.
2407 * Complicating this any further will surely cause deadlock!
2409 begin_critical_section(S_SUPPMSGMAIN);
2410 GetSuppMsgInfo(&smi, msgnum);
2411 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2412 msgnum, smi.smi_refcount);
2413 smi.smi_refcount += incr;
2414 PutSuppMsgInfo(&smi);
2415 end_critical_section(S_SUPPMSGMAIN);
2416 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2417 msgnum, smi.smi_refcount);
2419 /* If the reference count is now zero, delete the message
2420 * (and its supplementary record as well).
2422 if (smi.smi_refcount == 0) {
2423 lprintf(9, "Deleting message <%ld>\n", msgnum);
2425 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2426 delnum = (0L - msgnum);
2427 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2432 * Write a generic object to this room
2434 * Note: this could be much more efficient. Right now we use two temporary
2435 * files, and still pull the message into memory as with all others.
2437 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2438 char *content_type, /* MIME type of this object */
2439 char *tempfilename, /* Where to fetch it from */
2440 struct usersupp *is_mailbox, /* Mailbox room? */
2441 int is_binary, /* Is encoding necessary? */
2442 int is_unique, /* Del others of this type? */
2443 unsigned int flags /* Internal save flags */
2448 char filename[PATH_MAX];
2451 struct quickroom qrbuf;
2452 char roomname[ROOMNAMELEN];
2453 struct CtdlMessage *msg;
2456 if (is_mailbox != NULL)
2457 MailboxName(roomname, is_mailbox, req_room);
2459 safestrncpy(roomname, req_room, sizeof(roomname));
2460 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2462 strcpy(filename, tmpnam(NULL));
2463 fp = fopen(filename, "w");
2467 tempfp = fopen(tempfilename, "r");
2468 if (tempfp == NULL) {
2474 fprintf(fp, "Content-type: %s\n", content_type);
2475 lprintf(9, "Content-type: %s\n", content_type);
2477 if (is_binary == 0) {
2478 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2479 while (ch = getc(tempfp), ch > 0)
2485 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2488 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2489 tempfilename, filename);
2493 lprintf(9, "Allocating\n");
2494 msg = mallok(sizeof(struct CtdlMessage));
2495 memset(msg, 0, sizeof(struct CtdlMessage));
2496 msg->cm_magic = CTDLMESSAGE_MAGIC;
2497 msg->cm_anon_type = MES_NORMAL;
2498 msg->cm_format_type = 4;
2499 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2500 msg->cm_fields['O'] = strdoop(req_room);
2501 msg->cm_fields['N'] = strdoop(config.c_nodename);
2502 msg->cm_fields['H'] = strdoop(config.c_humannode);
2503 msg->cm_flags = flags;
2505 lprintf(9, "Loading\n");
2506 fp = fopen(filename, "rb");
2507 fseek(fp, 0L, SEEK_END);
2510 msg->cm_fields['M'] = mallok(len);
2511 fread(msg->cm_fields['M'], len, 1, fp);
2515 /* Create the requested room if we have to. */
2516 if (getroom(&qrbuf, roomname) != 0) {
2517 create_room(roomname,
2518 ( (is_mailbox != NULL) ? 4 : 3 ),
2521 /* If the caller specified this object as unique, delete all
2522 * other objects of this type that are currently in the room.
2525 lprintf(9, "Deleted %d other msgs of this type\n",
2526 CtdlDeleteMessages(roomname, 0L, content_type));
2528 /* Now write the data */
2529 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2530 CtdlFreeMessage(msg);
2538 void CtdlGetSysConfigBackend(long msgnum) {
2539 config_msgnum = msgnum;
2543 char *CtdlGetSysConfig(char *sysconfname) {
2544 char hold_rm[ROOMNAMELEN];
2547 struct CtdlMessage *msg;
2550 strcpy(hold_rm, CC->quickroom.QRname);
2551 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2552 getroom(&CC->quickroom, hold_rm);
2557 /* We want the last (and probably only) config in this room */
2558 begin_critical_section(S_CONFIG);
2559 config_msgnum = (-1L);
2560 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2561 CtdlGetSysConfigBackend);
2562 msgnum = config_msgnum;
2563 end_critical_section(S_CONFIG);
2569 msg = CtdlFetchMessage(msgnum);
2571 conf = strdoop(msg->cm_fields['M']);
2572 CtdlFreeMessage(msg);
2579 getroom(&CC->quickroom, hold_rm);
2581 lprintf(9, "eggstracting...\n");
2582 if (conf != NULL) do {
2583 extract_token(buf, conf, 0, '\n');
2584 lprintf(9, "eggstracted <%s>\n", buf);
2585 strcpy(conf, &conf[strlen(buf)+1]);
2586 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2591 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2592 char temp[PATH_MAX];
2595 strcpy(temp, tmpnam(NULL));
2597 fp = fopen(temp, "w");
2598 if (fp == NULL) return;
2599 fprintf(fp, "%s", sysconfdata);
2602 /* this handy API function does all the work for us */
2603 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);