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 omprintf("X-UIDL: %ld%s", msg_num, nl);
990 for (i = 0; i < 256; ++i) {
991 if (TheMessage->cm_fields[i]) {
992 mptr = TheMessage->cm_fields[i];
996 } else if (i == 'P') {
997 omprintf("Path: %s%s", mptr, nl);
998 for (a = 0; a < strlen(mptr); ++a) {
999 if (mptr[a] == '!') {
1000 strcpy(mptr, &mptr[a + 1]);
1004 strcpy(suser, mptr);
1005 } else if (i == 'U')
1006 omprintf("Subject: %s%s", mptr, nl);
1010 strcpy(lnode, mptr);
1012 omprintf("X-Citadel-Room: %s%s",
1015 strcpy(snode, mptr);
1017 omprintf("To: %s%s", mptr, nl);
1018 else if (i == 'T') {
1020 omprintf("Date: %s", asctime(localtime(&xtime)));
1026 if (mode == MT_RFC822) {
1027 if (!strcasecmp(snode, NODENAME)) {
1028 strcpy(snode, FQDN);
1030 omprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
1031 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1032 omprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1033 omprintf("Organization: %s%s", lnode, nl);
1036 /* end header processing loop ... at this point, we're in the text */
1038 mptr = TheMessage->cm_fields['M'];
1040 /* Tell the client about the MIME parts in this message */
1041 if (TheMessage->cm_format_type == FMT_RFC822) {
1042 if (mode == MT_CITADEL) {
1043 mime_parser(mptr, NULL, *list_this_part);
1045 else if (mode == MT_MIME) { /* list parts only */
1046 mime_parser(mptr, NULL, *list_this_part);
1047 if (do_proto) cprintf("000\n");
1048 CtdlFreeMessage(TheMessage);
1051 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1052 /* FIX ... we have to put some code in here to avoid
1053 * printing duplicate header information when both
1054 * Citadel and RFC822 headers exist. Preference should
1055 * probably be given to the RFC822 headers.
1057 while (ch=*(mptr++), ch!=0) {
1059 else if (ch==10) omprintf("%s", nl);
1060 else omprintf("%c", ch);
1062 if (do_proto) cprintf("000\n");
1063 CtdlFreeMessage(TheMessage);
1069 if (do_proto) cprintf("000\n");
1070 CtdlFreeMessage(TheMessage);
1074 /* signify start of msg text */
1075 if (mode == MT_CITADEL)
1076 if (do_proto) cprintf("text\n");
1077 if (mode == MT_RFC822) {
1081 /* If the format type on disk is 1 (fixed-format), then we want
1082 * everything to be output completely literally ... regardless of
1083 * what message transfer format is in use.
1085 if (TheMessage->cm_format_type == FMT_FIXED) {
1087 while (ch = *mptr++, ch > 0) {
1090 if ((ch == 10) || (strlen(buf) > 250)) {
1091 omprintf("%s%s", buf, nl);
1094 buf[strlen(buf) + 1] = 0;
1095 buf[strlen(buf)] = ch;
1098 if (strlen(buf) > 0)
1099 omprintf("%s%s", buf, nl);
1102 /* If the message on disk is format 0 (Citadel vari-format), we
1103 * output using the formatter at 80 columns. This is the final output
1104 * form if the transfer format is RFC822, but if the transfer format
1105 * is Citadel proprietary, it'll still work, because the indentation
1106 * for new paragraphs is correct and the client will reformat the
1107 * message to the reader's screen width.
1109 if (TheMessage->cm_format_type == FMT_CITADEL) {
1113 /* If the message on disk is format 4 (MIME), we've gotta hand it
1114 * off to the MIME parser. The client has already been told that
1115 * this message is format 1 (fixed format), so the callback function
1116 * we use will display those parts as-is.
1118 if (TheMessage->cm_format_type == FMT_RFC822) {
1119 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1120 memset(ma, 0, sizeof(struct ma_info));
1121 mime_parser(mptr, NULL, *fixed_output);
1124 /* now we're done */
1125 if (do_proto) cprintf("000\n");
1126 CtdlFreeMessage(TheMessage);
1133 * display a message (mode 0 - Citadel proprietary)
1135 void cmd_msg0(char *cmdbuf)
1138 int headers_only = 0;
1140 msgid = extract_long(cmdbuf, 0);
1141 headers_only = extract_int(cmdbuf, 1);
1143 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, NULL, -1, 0);
1149 * display a message (mode 2 - RFC822)
1151 void cmd_msg2(char *cmdbuf)
1154 int headers_only = 0;
1156 msgid = extract_long(cmdbuf, 0);
1157 headers_only = extract_int(cmdbuf, 1);
1159 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, NULL, -1, 1);
1165 * display a message (mode 3 - IGnet raw format - internal programs only)
1167 void cmd_msg3(char *cmdbuf)
1170 struct CtdlMessage *msg;
1173 if (CC->internal_pgm == 0) {
1174 cprintf("%d This command is for internal programs only.\n",
1179 msgnum = extract_long(cmdbuf, 0);
1180 msg = CtdlFetchMessage(msgnum);
1182 cprintf("%d Message %ld not found.\n",
1187 serialize_message(&smr, msg);
1188 CtdlFreeMessage(msg);
1191 cprintf("%d Unable to serialize message\n",
1192 ERROR+INTERNAL_ERROR);
1196 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1197 client_write(smr.ser, smr.len);
1204 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1206 void cmd_msg4(char *cmdbuf)
1210 msgid = extract_long(cmdbuf, 0);
1211 CtdlOutputMsg(msgid, MT_MIME, 0, 1, NULL, -1, 0);
1215 * Open a component of a MIME message as a download file
1217 void cmd_opna(char *cmdbuf)
1221 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1223 msgid = extract_long(cmdbuf, 0);
1224 extract(desired_section, cmdbuf, 1);
1226 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, NULL, -1, 1);
1231 * Save a message pointer into a specified room
1232 * (Returns 0 for success, nonzero for failure)
1233 * roomname may be NULL to use the current room
1235 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1237 char hold_rm[ROOMNAMELEN];
1238 struct cdbdata *cdbfr;
1241 long highest_msg = 0L;
1242 struct CtdlMessage *msg = NULL;
1244 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1245 roomname, msgid, flags);
1247 strcpy(hold_rm, CC->quickroom.QRname);
1249 /* We may need to check to see if this message is real */
1250 if ( (flags & SM_VERIFY_GOODNESS)
1251 || (flags & SM_DO_REPL_CHECK)
1253 msg = CtdlFetchMessage(msgid);
1254 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1257 /* Perform replication checks if necessary */
1258 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1260 if (getroom(&CC->quickroom,
1261 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1263 lprintf(9, "No such room <%s>\n", roomname);
1264 if (msg != NULL) CtdlFreeMessage(msg);
1265 return(ERROR + ROOM_NOT_FOUND);
1268 if (ReplicationChecks(msg) != 0) {
1269 getroom(&CC->quickroom, hold_rm);
1270 if (msg != NULL) CtdlFreeMessage(msg);
1271 lprintf(9, "Did replication, and newer exists\n");
1276 /* Now the regular stuff */
1277 if (lgetroom(&CC->quickroom,
1278 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1280 lprintf(9, "No such room <%s>\n", roomname);
1281 if (msg != NULL) CtdlFreeMessage(msg);
1282 return(ERROR + ROOM_NOT_FOUND);
1285 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1286 if (cdbfr == NULL) {
1290 msglist = mallok(cdbfr->len);
1291 if (msglist == NULL)
1292 lprintf(3, "ERROR malloc msglist!\n");
1293 num_msgs = cdbfr->len / sizeof(long);
1294 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1299 /* Make sure the message doesn't already exist in this room. It
1300 * is absolutely taboo to have more than one reference to the same
1301 * message in a room.
1303 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1304 if (msglist[i] == msgid) {
1305 lputroom(&CC->quickroom); /* unlock the room */
1306 getroom(&CC->quickroom, hold_rm);
1307 if (msg != NULL) CtdlFreeMessage(msg);
1308 return(ERROR + ALREADY_EXISTS);
1312 /* Now add the new message */
1314 msglist = reallok(msglist,
1315 (num_msgs * sizeof(long)));
1317 if (msglist == NULL) {
1318 lprintf(3, "ERROR: can't realloc message list!\n");
1320 msglist[num_msgs - 1] = msgid;
1322 /* Sort the message list, so all the msgid's are in order */
1323 num_msgs = sort_msglist(msglist, num_msgs);
1325 /* Determine the highest message number */
1326 highest_msg = msglist[num_msgs - 1];
1328 /* Write it back to disk. */
1329 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1330 msglist, num_msgs * sizeof(long));
1332 /* Free up the memory we used. */
1335 /* Update the highest-message pointer and unlock the room. */
1336 CC->quickroom.QRhighest = highest_msg;
1337 lputroom(&CC->quickroom);
1338 getroom(&CC->quickroom, hold_rm);
1340 /* Bump the reference count for this message. */
1341 if ((flags & SM_DONT_BUMP_REF)==0) {
1342 AdjRefCount(msgid, +1);
1345 /* Return success. */
1346 if (msg != NULL) CtdlFreeMessage(msg);
1353 * Message base operation to send a message to the master file
1354 * (returns new message number)
1356 * This is the back end for CtdlSaveMsg() and should not be directly
1357 * called by server-side modules.
1360 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1361 int generate_id, /* generate 'I' field? */
1362 FILE *save_a_copy) /* save a copy to disk? */
1369 /* Get a new message number */
1370 newmsgid = get_new_message_number();
1371 sprintf(msgidbuf, "%ld", newmsgid);
1374 msg->cm_fields['I'] = strdoop(msgidbuf);
1377 serialize_message(&smr, msg);
1380 cprintf("%d Unable to serialize message\n",
1381 ERROR+INTERNAL_ERROR);
1385 /* Write our little bundle of joy into the message base */
1386 begin_critical_section(S_MSGMAIN);
1387 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1388 smr.ser, smr.len) < 0) {
1389 lprintf(2, "Can't store message\n");
1394 end_critical_section(S_MSGMAIN);
1396 /* If the caller specified that a copy should be saved to a particular
1397 * file handle, do that now too.
1399 if (save_a_copy != NULL) {
1400 fwrite(smr.ser, smr.len, 1, save_a_copy);
1403 /* Free the memory we used for the serialized message */
1406 /* Return the *local* message ID to the caller
1407 * (even if we're storing an incoming network message)
1415 * Serialize a struct CtdlMessage into the format used on disk and network.
1417 * This function loads up a "struct ser_ret" (defined in server.h) which
1418 * contains the length of the serialized message and a pointer to the
1419 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1421 void serialize_message(struct ser_ret *ret, /* return values */
1422 struct CtdlMessage *msg) /* unserialized msg */
1426 static char *forder = FORDER;
1428 if (is_valid_message(msg) == 0) return; /* self check */
1431 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1432 ret->len = ret->len +
1433 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1435 lprintf(9, "calling malloc\n");
1436 ret->ser = mallok(ret->len);
1437 if (ret->ser == NULL) {
1443 ret->ser[1] = msg->cm_anon_type;
1444 ret->ser[2] = msg->cm_format_type;
1447 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1448 ret->ser[wlen++] = (char)forder[i];
1449 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1450 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1452 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1461 * Back end for the ReplicationChecks() function
1463 void check_repl(long msgnum) {
1464 struct CtdlMessage *msg;
1465 time_t timestamp = (-1L);
1467 lprintf(9, "check_repl() found message %ld\n", msgnum);
1468 msg = CtdlFetchMessage(msgnum);
1469 if (msg == NULL) return;
1470 if (msg->cm_fields['T'] != NULL) {
1471 timestamp = atol(msg->cm_fields['T']);
1473 CtdlFreeMessage(msg);
1475 if (timestamp > msg_repl->highest) {
1476 msg_repl->highest = timestamp; /* newer! */
1477 lprintf(9, "newer!\n");
1480 lprintf(9, "older!\n");
1482 /* Existing isn't newer? Then delete the old one(s). */
1483 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1488 * Check to see if any messages already exist which carry the same Extended ID
1492 * -> With older timestamps: delete them and return 0. Message will be saved.
1493 * -> With newer timestamps: return 1. Message save will be aborted.
1495 int ReplicationChecks(struct CtdlMessage *msg) {
1496 struct CtdlMessage *template;
1499 lprintf(9, "ReplicationChecks() started\n");
1500 /* No extended id? Don't do anything. */
1501 if (msg->cm_fields['E'] == NULL) return 0;
1502 if (strlen(msg->cm_fields['E']) == 0) return 0;
1503 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1505 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1506 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1507 msg_repl->highest = atol(msg->cm_fields['T']);
1509 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1510 memset(template, 0, sizeof(struct CtdlMessage));
1511 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1513 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1515 /* If a newer message exists with the same Extended ID, abort
1518 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1522 CtdlFreeMessage(template);
1523 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1531 * Save a message to disk
1533 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1534 char *rec, /* Recipient (mail) */
1535 char *force, /* force a particular room? */
1536 int mailtype, /* local or remote type */
1537 int generate_id) /* 1 = generate 'I' field */
1540 char hold_rm[ROOMNAMELEN];
1541 char actual_rm[ROOMNAMELEN];
1542 char force_room[ROOMNAMELEN];
1543 char content_type[256]; /* We have to learn this */
1544 char recipient[256];
1547 struct usersupp userbuf;
1549 struct SuppMsgInfo smi;
1550 FILE *network_fp = NULL;
1551 static int seqnum = 1;
1552 struct CtdlMessage *imsg;
1555 lprintf(9, "CtdlSaveMsg() called\n");
1556 if (is_valid_message(msg) == 0) return(-1); /* self check */
1558 /* If this message has no timestamp, we take the liberty of
1559 * giving it one, right now.
1561 if (msg->cm_fields['T'] == NULL) {
1562 lprintf(9, "Generating timestamp\n");
1563 sprintf(aaa, "%ld", time(NULL));
1564 msg->cm_fields['T'] = strdoop(aaa);
1567 /* If this message has no path, we generate one.
1569 if (msg->cm_fields['P'] == NULL) {
1570 lprintf(9, "Generating path\n");
1571 if (msg->cm_fields['A'] != NULL) {
1572 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1573 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1574 if (isspace(msg->cm_fields['P'][a])) {
1575 msg->cm_fields['P'][a] = ' ';
1580 msg->cm_fields['P'] = strdoop("unknown");
1584 strcpy(force_room, force);
1586 /* Strip non-printable characters out of the recipient name */
1587 strcpy(recipient, rec);
1588 for (a = 0; a < strlen(recipient); ++a)
1589 if (!isprint(recipient[a]))
1590 strcpy(&recipient[a], &recipient[a + 1]);
1592 /* Learn about what's inside, because it's what's inside that counts */
1593 lprintf(9, "Learning what's inside\n");
1594 if (msg->cm_fields['M'] == NULL) {
1595 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1598 switch (msg->cm_format_type) {
1600 strcpy(content_type, "text/x-citadel-variformat");
1603 strcpy(content_type, "text/plain");
1606 strcpy(content_type, "text/plain");
1607 /* advance past header fields */
1608 mptr = msg->cm_fields['M'];
1611 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1612 safestrncpy(content_type, mptr,
1613 sizeof(content_type));
1614 strcpy(content_type, &content_type[14]);
1615 for (a = 0; a < strlen(content_type); ++a)
1616 if ((content_type[a] == ';')
1617 || (content_type[a] == ' ')
1618 || (content_type[a] == 13)
1619 || (content_type[a] == 10))
1620 content_type[a] = 0;
1627 /* Goto the correct room */
1628 lprintf(9, "Switching rooms\n");
1629 strcpy(hold_rm, CC->quickroom.QRname);
1630 strcpy(actual_rm, CC->quickroom.QRname);
1632 /* If the user is a twit, move to the twit room for posting */
1633 lprintf(9, "Handling twit stuff\n");
1635 if (CC->usersupp.axlevel == 2) {
1636 strcpy(hold_rm, actual_rm);
1637 strcpy(actual_rm, config.c_twitroom);
1641 /* ...or if this message is destined for Aide> then go there. */
1642 if (strlen(force_room) > 0) {
1643 strcpy(actual_rm, force_room);
1646 lprintf(9, "Possibly relocating\n");
1647 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1648 getroom(&CC->quickroom, actual_rm);
1652 * If this message has no O (room) field, generate one.
1654 if (msg->cm_fields['O'] == NULL) {
1655 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1658 /* Perform "before save" hooks (aborting if any return nonzero) */
1659 lprintf(9, "Performing before-save hooks\n");
1660 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1662 /* If this message has an Extended ID, perform replication checks */
1663 lprintf(9, "Performing replication checks\n");
1664 if (ReplicationChecks(msg) > 0) return(-1);
1666 /* Network mail - send a copy to the network program. */
1667 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1668 lprintf(9, "Sending network spool\n");
1669 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1670 (long) getpid(), CC->cs_pid, ++seqnum);
1671 lprintf(9, "Saving a copy to %s\n", aaa);
1672 network_fp = fopen(aaa, "ab+");
1673 if (network_fp == NULL)
1674 lprintf(2, "ERROR: %s\n", strerror(errno));
1677 /* Save it to disk */
1678 lprintf(9, "Saving to disk\n");
1679 newmsgid = send_message(msg, generate_id, network_fp);
1680 if (network_fp != NULL) {
1682 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1685 if (newmsgid <= 0L) return(-1);
1687 /* Write a supplemental message info record. This doesn't have to
1688 * be a critical section because nobody else knows about this message
1691 lprintf(9, "Creating SuppMsgInfo record\n");
1692 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1693 smi.smi_msgnum = newmsgid;
1694 smi.smi_refcount = 0;
1695 safestrncpy(smi.smi_content_type, content_type, 64);
1696 PutSuppMsgInfo(&smi);
1698 /* Now figure out where to store the pointers */
1699 lprintf(9, "Storing pointers\n");
1701 /* If this is being done by the networker delivering a private
1702 * message, we want to BYPASS saving the sender's copy (because there
1703 * is no local sender; it would otherwise go to the Trashcan).
1705 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1706 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1709 /* For internet mail, drop a copy in the outbound queue room */
1710 if (mailtype == MES_INTERNET) {
1711 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1714 /* Bump this user's messages posted counter. */
1715 lprintf(9, "Updating user\n");
1716 lgetuser(&CC->usersupp, CC->curr_user);
1717 CC->usersupp.posted = CC->usersupp.posted + 1;
1718 lputuser(&CC->usersupp);
1720 /* If this is private, local mail, make a copy in the
1721 * recipient's mailbox and bump the reference count.
1723 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1724 if (getuser(&userbuf, recipient) == 0) {
1725 lprintf(9, "Delivering private mail\n");
1726 MailboxName(actual_rm, &userbuf, MAILROOM);
1727 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1731 /* Perform "after save" hooks */
1732 lprintf(9, "Performing after-save hooks\n");
1733 PerformMessageHooks(msg, EVT_AFTERSAVE);
1736 lprintf(9, "Returning to original room\n");
1737 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1738 getroom(&CC->quickroom, hold_rm);
1740 /* For internet mail, generate delivery instructions
1741 * (Yes, this is recursive! Deal with it!)
1743 if (mailtype == MES_INTERNET) {
1744 lprintf(9, "Generating delivery instructions\n");
1745 instr = mallok(2048);
1747 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1749 SPOOLMIME, newmsgid, time(NULL), recipient );
1751 imsg = mallok(sizeof(struct CtdlMessage));
1752 memset(imsg, 0, sizeof(struct CtdlMessage));
1753 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1754 imsg->cm_anon_type = MES_NORMAL;
1755 imsg->cm_format_type = FMT_RFC822;
1756 imsg->cm_fields['A'] = strdoop("Citadel");
1757 imsg->cm_fields['M'] = instr;
1758 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1759 CtdlFreeMessage(imsg);
1768 * Convenience function for generating small administrative messages.
1770 void quickie_message(char *from, char *to, char *room, char *text)
1772 struct CtdlMessage *msg;
1774 msg = mallok(sizeof(struct CtdlMessage));
1775 memset(msg, 0, sizeof(struct CtdlMessage));
1776 msg->cm_magic = CTDLMESSAGE_MAGIC;
1777 msg->cm_anon_type = MES_NORMAL;
1778 msg->cm_format_type = 0;
1779 msg->cm_fields['A'] = strdoop(from);
1780 msg->cm_fields['O'] = strdoop(room);
1781 msg->cm_fields['N'] = strdoop(NODENAME);
1783 msg->cm_fields['R'] = strdoop(to);
1784 msg->cm_fields['M'] = strdoop(text);
1786 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1787 CtdlFreeMessage(msg);
1788 syslog(LOG_NOTICE, text);
1794 * Back end function used by make_message() and similar functions
1796 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1797 size_t maxlen, /* maximum message length */
1798 char *exist /* if non-null, append to it;
1799 exist is ALWAYS freed */
1802 size_t message_len = 0;
1803 size_t buffer_len = 0;
1807 if (exist == NULL) {
1811 m = reallok(exist, strlen(exist) + 4096);
1812 if (m == NULL) phree(exist);
1815 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1822 /* read in the lines of message text one by one */
1824 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1826 /* augment the buffer if we have to */
1827 if ((message_len + strlen(buf) + 2) > buffer_len) {
1828 lprintf(9, "realloking\n");
1829 ptr = reallok(m, (buffer_len * 2) );
1830 if (ptr == NULL) { /* flush if can't allocate */
1831 while ( (client_gets(buf)>0) &&
1832 strcmp(buf, terminator)) ;;
1835 buffer_len = (buffer_len * 2);
1838 lprintf(9, "buffer_len is %d\n", buffer_len);
1842 if (append == NULL) append = m;
1843 while (strlen(append) > 0) ++append;
1844 strcpy(append, buf);
1845 strcat(append, "\n");
1846 message_len = message_len + strlen(buf) + 1;
1848 /* if we've hit the max msg length, flush the rest */
1849 if (message_len >= maxlen) {
1850 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1861 * Build a binary message to be saved on disk.
1864 struct CtdlMessage *make_message(
1865 struct usersupp *author, /* author's usersupp structure */
1866 char *recipient, /* NULL if it's not mail */
1867 char *room, /* room where it's going */
1868 int type, /* see MES_ types in header file */
1869 int net_type, /* see MES_ types in header file */
1870 int format_type, /* local or remote (see citadel.h) */
1871 char *fake_name) /* who we're masquerading as */
1877 struct CtdlMessage *msg;
1879 msg = mallok(sizeof(struct CtdlMessage));
1880 memset(msg, 0, sizeof(struct CtdlMessage));
1881 msg->cm_magic = CTDLMESSAGE_MAGIC;
1882 msg->cm_anon_type = type;
1883 msg->cm_format_type = format_type;
1885 /* Don't confuse the poor folks if it's not routed mail. */
1886 strcpy(dest_node, "");
1888 /* If net_type is MES_BINARY, split out the destination node. */
1889 if (net_type == MES_BINARY) {
1890 strcpy(dest_node, NODENAME);
1891 for (a = 0; a < strlen(recipient); ++a) {
1892 if (recipient[a] == '@') {
1894 strcpy(dest_node, &recipient[a + 1]);
1899 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1900 if (net_type == MES_INTERNET) {
1901 strcpy(dest_node, "internet");
1904 while (isspace(recipient[strlen(recipient) - 1]))
1905 recipient[strlen(recipient) - 1] = 0;
1907 sprintf(buf, "cit%ld", author->usernum); /* Path */
1908 msg->cm_fields['P'] = strdoop(buf);
1910 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1911 msg->cm_fields['T'] = strdoop(buf);
1913 if (fake_name[0]) /* author */
1914 msg->cm_fields['A'] = strdoop(fake_name);
1916 msg->cm_fields['A'] = strdoop(author->fullname);
1918 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1919 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1921 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1923 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1924 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1926 if (recipient[0] != 0)
1927 msg->cm_fields['R'] = strdoop(recipient);
1928 if (dest_node[0] != 0)
1929 msg->cm_fields['D'] = strdoop(dest_node);
1932 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1933 config.c_maxmsglen, NULL);
1944 * message entry - mode 0 (normal)
1946 void cmd_ent0(char *entargs)
1949 char recipient[256];
1951 int format_type = 0;
1952 char newusername[256];
1953 struct CtdlMessage *msg;
1957 struct usersupp tempUS;
1960 post = extract_int(entargs, 0);
1961 extract(recipient, entargs, 1);
1962 anon_flag = extract_int(entargs, 2);
1963 format_type = extract_int(entargs, 3);
1965 /* first check to make sure the request is valid. */
1967 if (!(CC->logged_in)) {
1968 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1971 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1972 cprintf("%d Need to be validated to enter ",
1973 ERROR + HIGHER_ACCESS_REQUIRED);
1974 cprintf("(except in %s> to sysop)\n", MAILROOM);
1977 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1978 cprintf("%d Need net privileges to enter here.\n",
1979 ERROR + HIGHER_ACCESS_REQUIRED);
1982 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1983 cprintf("%d Sorry, this is a read-only room.\n",
1984 ERROR + HIGHER_ACCESS_REQUIRED);
1991 if (CC->usersupp.axlevel < 6) {
1992 cprintf("%d You don't have permission to masquerade.\n",
1993 ERROR + HIGHER_ACCESS_REQUIRED);
1996 extract(newusername, entargs, 4);
1997 memset(CC->fake_postname, 0, 32);
1998 strcpy(CC->fake_postname, newusername);
1999 cprintf("%d Ok\n", OK);
2002 CC->cs_flags |= CS_POSTING;
2005 if (CC->quickroom.QRflags & QR_MAILBOX) {
2006 if (CC->usersupp.axlevel >= 2) {
2007 strcpy(buf, recipient);
2009 strcpy(buf, "sysop");
2010 e = alias(buf); /* alias and mail type */
2011 if ((buf[0] == 0) || (e == MES_ERROR)) {
2012 cprintf("%d Unknown address - cannot send message.\n",
2013 ERROR + NO_SUCH_USER);
2016 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2017 cprintf("%d Net privileges required for network mail.\n",
2018 ERROR + HIGHER_ACCESS_REQUIRED);
2021 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2022 && ((CC->usersupp.flags & US_INTERNET) == 0)
2023 && (!CC->internal_pgm)) {
2024 cprintf("%d You don't have access to Internet mail.\n",
2025 ERROR + HIGHER_ACCESS_REQUIRED);
2028 if (!strcasecmp(buf, "sysop")) {
2033 goto SKFALL; /* don't search local file */
2034 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2035 cprintf("%d Can't send mail to yourself!\n",
2036 ERROR + NO_SUCH_USER);
2039 /* Check to make sure the user exists; also get the correct
2040 * upper/lower casing of the name.
2042 a = getuser(&tempUS, buf);
2044 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2047 strcpy(buf, tempUS.fullname);
2050 SKFALL: b = MES_NORMAL;
2051 if (CC->quickroom.QRflags & QR_ANONONLY)
2053 if (CC->quickroom.QRflags & QR_ANONOPT) {
2057 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2060 /* If we're only checking the validity of the request, return
2061 * success without creating the message.
2064 cprintf("%d %s\n", OK, buf);
2068 cprintf("%d send message\n", SEND_LISTING);
2070 /* Read in the message from the client. */
2071 if (CC->fake_postname[0])
2072 msg = make_message(&CC->usersupp, buf,
2073 CC->quickroom.QRname, b, e, format_type,
2075 else if (CC->fake_username[0])
2076 msg = make_message(&CC->usersupp, buf,
2077 CC->quickroom.QRname, b, e, format_type,
2080 msg = make_message(&CC->usersupp, buf,
2081 CC->quickroom.QRname, b, e, format_type, "");
2084 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2085 CtdlFreeMessage(msg);
2086 CC->fake_postname[0] = '\0';
2093 * message entry - mode 3 (raw)
2095 void cmd_ent3(char *entargs)
2101 unsigned char ch, which_field;
2102 struct usersupp tempUS;
2104 struct CtdlMessage *msg;
2107 if (CC->internal_pgm == 0) {
2108 cprintf("%d This command is for internal programs only.\n",
2113 /* See if there's a recipient, but make sure it's a real one */
2114 extract(recp, entargs, 1);
2115 for (a = 0; a < strlen(recp); ++a)
2116 if (!isprint(recp[a]))
2117 strcpy(&recp[a], &recp[a + 1]);
2118 while (isspace(recp[0]))
2119 strcpy(recp, &recp[1]);
2120 while (isspace(recp[strlen(recp) - 1]))
2121 recp[strlen(recp) - 1] = 0;
2123 /* If we're in Mail, check the recipient */
2124 if (strlen(recp) > 0) {
2125 e = alias(recp); /* alias and mail type */
2126 if ((recp[0] == 0) || (e == MES_ERROR)) {
2127 cprintf("%d Unknown address - cannot send message.\n",
2128 ERROR + NO_SUCH_USER);
2131 if (e == MES_LOCAL) {
2132 a = getuser(&tempUS, recp);
2134 cprintf("%d No such user.\n",
2135 ERROR + NO_SUCH_USER);
2141 /* At this point, message has been approved. */
2142 if (extract_int(entargs, 0) == 0) {
2143 cprintf("%d OK to send\n", OK);
2147 msglen = extract_long(entargs, 2);
2148 msg = mallok(sizeof(struct CtdlMessage));
2150 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2154 memset(msg, 0, sizeof(struct CtdlMessage));
2155 tempbuf = mallok(msglen);
2156 if (tempbuf == NULL) {
2157 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2162 cprintf("%d %ld\n", SEND_BINARY, msglen);
2164 client_read(&ch, 1); /* 0xFF magic number */
2165 msg->cm_magic = CTDLMESSAGE_MAGIC;
2166 client_read(&ch, 1); /* anon type */
2167 msg->cm_anon_type = ch;
2168 client_read(&ch, 1); /* format type */
2169 msg->cm_format_type = ch;
2170 msglen = msglen - 3;
2172 while (msglen > 0) {
2173 client_read(&which_field, 1);
2174 if (!isalpha(which_field)) valid_msg = 0;
2178 client_read(&ch, 1);
2180 a = strlen(tempbuf);
2183 } while ( (ch != 0) && (msglen > 0) );
2185 msg->cm_fields[which_field] = strdoop(tempbuf);
2188 msg->cm_flags = CM_SKIP_HOOKS;
2189 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2190 CtdlFreeMessage(msg);
2196 * API function to delete messages which match a set of criteria
2197 * (returns the actual number of messages deleted)
2199 int CtdlDeleteMessages(char *room_name, /* which room */
2200 long dmsgnum, /* or "0" for any */
2201 char *content_type /* or NULL for any */
2205 struct quickroom qrbuf;
2206 struct cdbdata *cdbfr;
2207 long *msglist = NULL;
2210 int num_deleted = 0;
2212 struct SuppMsgInfo smi;
2214 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2215 room_name, dmsgnum, content_type);
2217 /* get room record, obtaining a lock... */
2218 if (lgetroom(&qrbuf, room_name) != 0) {
2219 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2221 return (0); /* room not found */
2223 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2225 if (cdbfr != NULL) {
2226 msglist = mallok(cdbfr->len);
2227 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2228 num_msgs = cdbfr->len / sizeof(long);
2232 for (i = 0; i < num_msgs; ++i) {
2235 /* Set/clear a bit for each criterion */
2237 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2238 delete_this |= 0x01;
2240 if (content_type == NULL) {
2241 delete_this |= 0x02;
2243 GetSuppMsgInfo(&smi, msglist[i]);
2244 if (!strcasecmp(smi.smi_content_type,
2246 delete_this |= 0x02;
2250 /* Delete message only if all bits are set */
2251 if (delete_this == 0x03) {
2252 AdjRefCount(msglist[i], -1);
2258 num_msgs = sort_msglist(msglist, num_msgs);
2259 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2260 msglist, (num_msgs * sizeof(long)));
2262 qrbuf.QRhighest = msglist[num_msgs - 1];
2266 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2267 return (num_deleted);
2273 * Delete message from current room
2275 void cmd_dele(char *delstr)
2280 getuser(&CC->usersupp, CC->curr_user);
2281 if ((CC->usersupp.axlevel < 6)
2282 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2283 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2284 && (!(CC->internal_pgm))) {
2285 cprintf("%d Higher access required.\n",
2286 ERROR + HIGHER_ACCESS_REQUIRED);
2289 delnum = extract_long(delstr, 0);
2291 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2294 cprintf("%d %d message%s deleted.\n", OK,
2295 num_deleted, ((num_deleted != 1) ? "s" : ""));
2297 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2303 * move or copy a message to another room
2305 void cmd_move(char *args)
2309 struct quickroom qtemp;
2313 num = extract_long(args, 0);
2314 extract(targ, args, 1);
2315 targ[ROOMNAMELEN - 1] = 0;
2316 is_copy = extract_int(args, 2);
2318 getuser(&CC->usersupp, CC->curr_user);
2319 if ((CC->usersupp.axlevel < 6)
2320 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2321 cprintf("%d Higher access required.\n",
2322 ERROR + HIGHER_ACCESS_REQUIRED);
2326 if (getroom(&qtemp, targ) != 0) {
2327 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2331 err = CtdlSaveMsgPointerInRoom(targ, num,
2332 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2334 cprintf("%d Cannot store message in %s: error %d\n",
2339 /* Now delete the message from the source room,
2340 * if this is a 'move' rather than a 'copy' operation.
2342 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2344 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2350 * GetSuppMsgInfo() - Get the supplementary record for a message
2352 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2355 struct cdbdata *cdbsmi;
2358 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2359 smibuf->smi_msgnum = msgnum;
2360 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2362 /* Use the negative of the message number for its supp record index */
2363 TheIndex = (0L - msgnum);
2365 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2366 if (cdbsmi == NULL) {
2367 return; /* record not found; go with defaults */
2369 memcpy(smibuf, cdbsmi->ptr,
2370 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2371 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2378 * PutSuppMsgInfo() - (re)write supplementary record for a message
2380 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2384 /* Use the negative of the message number for its supp record index */
2385 TheIndex = (0L - smibuf->smi_msgnum);
2387 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2388 smibuf->smi_msgnum, smibuf->smi_refcount);
2390 cdb_store(CDB_MSGMAIN,
2391 &TheIndex, sizeof(long),
2392 smibuf, sizeof(struct SuppMsgInfo));
2397 * AdjRefCount - change the reference count for a message;
2398 * delete the message if it reaches zero
2400 void AdjRefCount(long msgnum, int incr)
2403 struct SuppMsgInfo smi;
2406 /* This is a *tight* critical section; please keep it that way, as
2407 * it may get called while nested in other critical sections.
2408 * Complicating this any further will surely cause deadlock!
2410 begin_critical_section(S_SUPPMSGMAIN);
2411 GetSuppMsgInfo(&smi, msgnum);
2412 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2413 msgnum, smi.smi_refcount);
2414 smi.smi_refcount += incr;
2415 PutSuppMsgInfo(&smi);
2416 end_critical_section(S_SUPPMSGMAIN);
2417 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2418 msgnum, smi.smi_refcount);
2420 /* If the reference count is now zero, delete the message
2421 * (and its supplementary record as well).
2423 if (smi.smi_refcount == 0) {
2424 lprintf(9, "Deleting message <%ld>\n", msgnum);
2426 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2427 delnum = (0L - msgnum);
2428 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2433 * Write a generic object to this room
2435 * Note: this could be much more efficient. Right now we use two temporary
2436 * files, and still pull the message into memory as with all others.
2438 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2439 char *content_type, /* MIME type of this object */
2440 char *tempfilename, /* Where to fetch it from */
2441 struct usersupp *is_mailbox, /* Mailbox room? */
2442 int is_binary, /* Is encoding necessary? */
2443 int is_unique, /* Del others of this type? */
2444 unsigned int flags /* Internal save flags */
2449 char filename[PATH_MAX];
2452 struct quickroom qrbuf;
2453 char roomname[ROOMNAMELEN];
2454 struct CtdlMessage *msg;
2457 if (is_mailbox != NULL)
2458 MailboxName(roomname, is_mailbox, req_room);
2460 safestrncpy(roomname, req_room, sizeof(roomname));
2461 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2463 strcpy(filename, tmpnam(NULL));
2464 fp = fopen(filename, "w");
2468 tempfp = fopen(tempfilename, "r");
2469 if (tempfp == NULL) {
2475 fprintf(fp, "Content-type: %s\n", content_type);
2476 lprintf(9, "Content-type: %s\n", content_type);
2478 if (is_binary == 0) {
2479 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2480 while (ch = getc(tempfp), ch > 0)
2486 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2489 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2490 tempfilename, filename);
2494 lprintf(9, "Allocating\n");
2495 msg = mallok(sizeof(struct CtdlMessage));
2496 memset(msg, 0, sizeof(struct CtdlMessage));
2497 msg->cm_magic = CTDLMESSAGE_MAGIC;
2498 msg->cm_anon_type = MES_NORMAL;
2499 msg->cm_format_type = 4;
2500 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2501 msg->cm_fields['O'] = strdoop(req_room);
2502 msg->cm_fields['N'] = strdoop(config.c_nodename);
2503 msg->cm_fields['H'] = strdoop(config.c_humannode);
2504 msg->cm_flags = flags;
2506 lprintf(9, "Loading\n");
2507 fp = fopen(filename, "rb");
2508 fseek(fp, 0L, SEEK_END);
2511 msg->cm_fields['M'] = mallok(len);
2512 fread(msg->cm_fields['M'], len, 1, fp);
2516 /* Create the requested room if we have to. */
2517 if (getroom(&qrbuf, roomname) != 0) {
2518 create_room(roomname,
2519 ( (is_mailbox != NULL) ? 4 : 3 ),
2522 /* If the caller specified this object as unique, delete all
2523 * other objects of this type that are currently in the room.
2526 lprintf(9, "Deleted %d other msgs of this type\n",
2527 CtdlDeleteMessages(roomname, 0L, content_type));
2529 /* Now write the data */
2530 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2531 CtdlFreeMessage(msg);
2539 void CtdlGetSysConfigBackend(long msgnum) {
2540 config_msgnum = msgnum;
2544 char *CtdlGetSysConfig(char *sysconfname) {
2545 char hold_rm[ROOMNAMELEN];
2548 struct CtdlMessage *msg;
2551 strcpy(hold_rm, CC->quickroom.QRname);
2552 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2553 getroom(&CC->quickroom, hold_rm);
2558 /* We want the last (and probably only) config in this room */
2559 begin_critical_section(S_CONFIG);
2560 config_msgnum = (-1L);
2561 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2562 CtdlGetSysConfigBackend);
2563 msgnum = config_msgnum;
2564 end_critical_section(S_CONFIG);
2570 msg = CtdlFetchMessage(msgnum);
2572 conf = strdoop(msg->cm_fields['M']);
2573 CtdlFreeMessage(msg);
2580 getroom(&CC->quickroom, hold_rm);
2582 lprintf(9, "eggstracting...\n");
2583 if (conf != NULL) do {
2584 extract_token(buf, conf, 0, '\n');
2585 lprintf(9, "eggstracted <%s>\n", buf);
2586 strcpy(conf, &conf[strlen(buf)+1]);
2587 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2592 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2593 char temp[PATH_MAX];
2596 strcpy(temp, tmpnam(NULL));
2598 fp = fopen(temp, "w");
2599 if (fp == NULL) return;
2600 fprintf(fp, "%s", sysconfdata);
2603 /* this handy API function does all the work for us */
2604 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);