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 lprintf(9, "Trying %ld\n", msglist[a]);
293 GetSuppMsgInfo(&smi, msglist[a]);
294 lprintf(9, "ct is %s\n", smi.smi_content_type);
295 if (strcasecmp(smi.smi_content_type, content_type)) {
300 num_msgs = sort_msglist(msglist, num_msgs);
302 /* If a template was supplied, filter out the messages which
303 * don't match. (This could induce some delays!)
306 if (compare != NULL) {
307 for (a = 0; a < num_msgs; ++a) {
308 msg = CtdlFetchMessage(msglist[a]);
310 if (CtdlMsgCmp(msg, compare)) {
313 CtdlFreeMessage(msg);
321 * Now iterate through the message list, according to the
322 * criteria supplied by the caller.
325 for (a = 0; a < num_msgs; ++a) {
326 thismsg = msglist[a];
331 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
332 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
333 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
334 && (CC->usersupp.flags & US_LASTOLD))
335 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
336 || ((mode == MSGS_FIRST) && (a < ref))
337 || ((mode == MSGS_GT) && (thismsg > ref))
343 phree(msglist); /* Clean up */
349 * cmd_msgs() - get list of message #'s in this room
350 * implements the MSGS server command using CtdlForEachMessage()
352 void cmd_msgs(char *cmdbuf)
361 int with_template = 0;
362 struct CtdlMessage *template = NULL;
364 extract(which, cmdbuf, 0);
365 cm_ref = extract_int(cmdbuf, 1);
366 with_template = extract_int(cmdbuf, 2);
370 if (!strncasecmp(which, "OLD", 3))
372 else if (!strncasecmp(which, "NEW", 3))
374 else if (!strncasecmp(which, "FIRST", 5))
376 else if (!strncasecmp(which, "LAST", 4))
378 else if (!strncasecmp(which, "GT", 2))
381 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
382 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
387 cprintf("%d Send template then receive message list\n",
389 template = (struct CtdlMessage *)
390 mallok(sizeof(struct CtdlMessage));
391 memset(template, 0, sizeof(struct CtdlMessage));
392 while(client_gets(buf), strcmp(buf,"000")) {
393 extract(tfield, buf, 0);
394 extract(tvalue, buf, 1);
395 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
396 if (!strcasecmp(tfield, msgkeys[i])) {
397 template->cm_fields[i] =
404 cprintf("%d Message list...\n", LISTING_FOLLOWS);
407 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
408 if (template != NULL) CtdlFreeMessage(template);
416 * help_subst() - support routine for help file viewer
418 void help_subst(char *strbuf, char *source, char *dest)
423 while (p = pattern2(strbuf, source), (p >= 0)) {
424 strcpy(workbuf, &strbuf[p + strlen(source)]);
425 strcpy(&strbuf[p], dest);
426 strcat(strbuf, workbuf);
431 void do_help_subst(char *buffer)
435 help_subst(buffer, "^nodename", config.c_nodename);
436 help_subst(buffer, "^humannode", config.c_humannode);
437 help_subst(buffer, "^fqdn", config.c_fqdn);
438 help_subst(buffer, "^username", CC->usersupp.fullname);
439 sprintf(buf2, "%ld", CC->usersupp.usernum);
440 help_subst(buffer, "^usernum", buf2);
441 help_subst(buffer, "^sysadm", config.c_sysadm);
442 help_subst(buffer, "^variantname", CITADEL);
443 sprintf(buf2, "%d", config.c_maxsessions);
444 help_subst(buffer, "^maxsessions", buf2);
450 * memfmout() - Citadel text formatter and paginator.
451 * Although the original purpose of this routine was to format
452 * text to the reader's screen width, all we're really using it
453 * for here is to format text out to 80 columns before sending it
454 * to the client. The client software may reformat it again.
457 int width, /* screen width to use */
458 char *mptr, /* where are we going to get our text from? */
459 char subst) /* nonzero if we should do substitutions */
471 c = 1; /* c is the current pos */
474 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
476 buffer[strlen(buffer) + 1] = 0;
477 buffer[strlen(buffer)] = ch;
480 if (buffer[0] == '^')
481 do_help_subst(buffer);
483 buffer[strlen(buffer) + 1] = 0;
485 strcpy(buffer, &buffer[1]);
495 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
497 if (((old == 13) || (old == 10)) && (isspace(real))) {
505 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
506 cprintf("\n%s", aaa);
515 if ((strlen(aaa) + c) > (width - 5)) {
525 if ((ch == 13) || (ch == 10)) {
526 cprintf("%s\n", aaa);
533 FMTEND: cprintf("%s\n", aaa);
539 * Callback function for mime parser that simply lists the part
541 void list_this_part(char *name, char *filename, char *partnum, char *disp,
542 void *content, char *cbtype, size_t length)
545 cprintf("part=%s|%s|%s|%s|%s|%d\n",
546 name, filename, partnum, disp, cbtype, length);
551 * Callback function for mime parser that opens a section for downloading
553 void mime_download(char *name, char *filename, char *partnum, char *disp,
554 void *content, char *cbtype, size_t length)
557 /* Silently go away if there's already a download open... */
558 if (CC->download_fp != NULL)
561 /* ...or if this is not the desired section */
562 if (strcasecmp(desired_section, partnum))
565 CC->download_fp = tmpfile();
566 if (CC->download_fp == NULL)
569 fwrite(content, length, 1, CC->download_fp);
570 fflush(CC->download_fp);
571 rewind(CC->download_fp);
573 OpenCmdResult(filename, cbtype);
579 * Load a message from disk into memory.
580 * This is used by CtdlOutputMsg() and other fetch functions.
582 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
583 * using the CtdlMessageFree() function.
585 struct CtdlMessage *CtdlFetchMessage(long msgnum)
587 struct cdbdata *dmsgtext;
588 struct CtdlMessage *ret = NULL;
591 CIT_UBYTE field_header;
594 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
595 if (dmsgtext == NULL) {
598 mptr = dmsgtext->ptr;
600 /* Parse the three bytes that begin EVERY message on disk.
601 * The first is always 0xFF, the on-disk magic number.
602 * The second is the anonymous/public type byte.
603 * The third is the format type byte (vari, fixed, or MIME).
607 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
611 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
612 memset(ret, 0, sizeof(struct CtdlMessage));
614 ret->cm_magic = CTDLMESSAGE_MAGIC;
615 ret->cm_anon_type = *mptr++; /* Anon type byte */
616 ret->cm_format_type = *mptr++; /* Format type byte */
619 * The rest is zero or more arbitrary fields. Load them in.
620 * We're done when we encounter either a zero-length field or
621 * have just processed the 'M' (message text) field.
624 field_length = strlen(mptr);
625 if (field_length == 0)
627 field_header = *mptr++;
628 ret->cm_fields[field_header] = mallok(field_length);
629 strcpy(ret->cm_fields[field_header], mptr);
631 while (*mptr++ != 0); /* advance to next field */
633 } while ((field_length > 0) && (field_header != 'M'));
637 /* Always make sure there's something in the msg text field */
638 if (ret->cm_fields['M'] == NULL)
639 ret->cm_fields['M'] = strdoop("<no text>\n");
641 /* Perform "before read" hooks (aborting if any return nonzero) */
642 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
643 CtdlFreeMessage(ret);
652 * Returns 1 if the supplied pointer points to a valid Citadel message.
653 * If the pointer is NULL or the magic number check fails, returns 0.
655 int is_valid_message(struct CtdlMessage *msg) {
658 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
659 lprintf(3, "is_valid_message() -- self-check failed\n");
667 * 'Destructor' for struct CtdlMessage
669 void CtdlFreeMessage(struct CtdlMessage *msg)
673 if (is_valid_message(msg) == 0) return;
675 for (i = 0; i < 256; ++i)
676 if (msg->cm_fields[i] != NULL)
677 phree(msg->cm_fields[i]);
679 msg->cm_magic = 0; /* just in case */
685 * Get a message off disk. (return value is the message's timestamp)
688 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
689 int mode, /* how would you like that message? */
690 int headers_only, /* eschew the message body? */
691 int do_proto, /* do Citadel protocol responses? */
694 int crlf /* Use CRLF newlines instead of LF? */
701 char display_name[256];
702 struct CtdlMessage *TheMessage;
704 char *nl; /* newline string */
706 /* buffers needed for RFC822 translation */
715 /* BEGIN NESTED FUNCTION omprintf() */
716 void omprintf(const char *format, ...) {
720 va_start(arg_ptr, format);
721 if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
722 buf[sizeof buf - 2] = '\n';
724 fwrite(buf, strlen(buf), 1, outfp);
726 else if (outsock >= 0) {
727 write(outsock, buf, strlen(buf));
730 client_write(buf, strlen(buf));
734 /* END NESTED FUNCTION omprintf() */
736 /* BEGIN NESTED FUNCTION omfmout() */
737 void omfmout(char *mptr) {
748 c = 1; /* c is the current pos */
756 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
758 if (((old == 13) || (old == 10)) && (isspace(real))) {
766 if (((strlen(aaa) + c) > (75)) && (strlen(aaa) > (75))) {
767 omprintf("%s%s", nl, aaa);
776 if ((strlen(aaa) + c) > (75)) {
780 omprintf("%s ", aaa);
786 if ((ch == 13) || (ch == 10)) {
787 omprintf("%s%s", aaa, nl);
794 FMTEND: omprintf("%s%s", aaa, nl);
796 /* END NESTED FUNCTION omfmout() */
798 /* BEGIN NESTED FUNCTION fixed_output() */
800 * Callback function for mime parser that wants to display text
802 void fixed_output(char *name, char *filename, char *partnum, char *disp,
803 void *content, char *cbtype, size_t length)
809 if (!strcasecmp(cbtype, "multipart/alternative")) {
810 strcpy(ma->prefix, partnum);
811 strcat(ma->prefix, ".");
817 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
819 && (ma->did_print == 1) ) {
820 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
826 if (!strcasecmp(cbtype, "text/plain")) {
831 if (ch==10) omprintf("%s", nl);
832 else omprintf("%c", ch);
835 else if (!strcasecmp(cbtype, "text/html")) {
836 ptr = html_to_ascii(content, 80, 0);
841 if (ch==10) omprintf("%s", nl);
842 else omprintf("%c", ch);
846 else if (strncasecmp(cbtype, "multipart/", 10)) {
847 omprintf("Part %s: %s (%s) (%d bytes)%s",
848 partnum, filename, cbtype, length, nl);
852 /* END NESTED FUNCTION fixed_output() */
856 sprintf(mid, "%ld", msg_num);
857 nl = (crlf ? "\r\n" : "\n");
859 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
860 if (do_proto) cprintf("%d Not logged in.\n",
861 ERROR + NOT_LOGGED_IN);
862 return(om_not_logged_in);
865 /* FIX ... small security issue
866 * We need to check to make sure the requested message is actually
867 * in the current room, and set msg_ok to 1 only if it is. This
868 * functionality is currently missing because I'm in a hurry to replace
869 * broken production code with nonbroken pre-beta code. :( -- ajc
872 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
874 return(om_no_such_msg);
879 * Fetch the message from disk
881 TheMessage = CtdlFetchMessage(msg_num);
882 if (TheMessage == NULL) {
883 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
885 return(om_no_such_msg);
888 /* Are we downloading a MIME component? */
889 if (mode == MT_DOWNLOAD) {
890 if (TheMessage->cm_format_type != FMT_RFC822) {
892 cprintf("%d This is not a MIME message.\n",
894 } else if (CC->download_fp != NULL) {
895 if (do_proto) cprintf(
896 "%d You already have a download open.\n",
899 /* Parse the message text component */
900 mptr = TheMessage->cm_fields['M'];
901 mime_parser(mptr, NULL, *mime_download);
902 /* If there's no file open by this time, the requested
903 * section wasn't found, so print an error
905 if (CC->download_fp == NULL) {
906 if (do_proto) cprintf(
907 "%d Section %s not found.\n",
908 ERROR + FILE_NOT_FOUND,
912 CtdlFreeMessage(TheMessage);
913 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
916 /* now for the user-mode message reading loops */
917 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
919 /* Tell the client which format type we're using. If this is a
920 * MIME message, *lie* about it and tell the user it's fixed-format.
922 if (mode == MT_CITADEL) {
923 if (TheMessage->cm_format_type == FMT_RFC822) {
924 if (do_proto) cprintf("type=1\n");
927 if (do_proto) cprintf("type=%d\n",
928 TheMessage->cm_format_type);
932 /* nhdr=yes means that we're only displaying headers, no body */
933 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
934 if (do_proto) cprintf("nhdr=yes\n");
937 /* begin header processing loop for Citadel message format */
939 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
941 strcpy(display_name, "<unknown>");
942 if (TheMessage->cm_fields['A']) {
943 strcpy(buf, TheMessage->cm_fields['A']);
944 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
945 if (TheMessage->cm_anon_type == MES_ANON)
946 strcpy(display_name, "****");
947 else if (TheMessage->cm_anon_type == MES_AN2)
948 strcpy(display_name, "anonymous");
950 strcpy(display_name, buf);
952 && ((TheMessage->cm_anon_type == MES_ANON)
953 || (TheMessage->cm_anon_type == MES_AN2))) {
954 sprintf(&display_name[strlen(display_name)],
959 strcpy(allkeys, FORDER);
960 for (i=0; i<strlen(allkeys); ++i) {
961 k = (int) allkeys[i];
963 if (TheMessage->cm_fields[k] != NULL) {
965 if (do_proto) cprintf("%s=%s\n",
970 if (do_proto) cprintf("%s=%s\n",
972 TheMessage->cm_fields[k]
981 /* begin header processing loop for RFC822 transfer format */
985 strcpy(snode, NODENAME);
986 strcpy(lnode, HUMANNODE);
987 if (mode == MT_RFC822) {
988 for (i = 0; i < 256; ++i) {
989 if (TheMessage->cm_fields[i]) {
990 mptr = TheMessage->cm_fields[i];
994 } else if (i == 'P') {
995 omprintf("Path: %s%s", mptr, nl);
996 for (a = 0; a < strlen(mptr); ++a) {
997 if (mptr[a] == '!') {
998 strcpy(mptr, &mptr[a + 1]);
1002 strcpy(suser, mptr);
1003 } else if (i == 'U')
1004 omprintf("Subject: %s%s", mptr, nl);
1008 strcpy(lnode, mptr);
1010 omprintf("X-Citadel-Room: %s%s",
1013 strcpy(snode, mptr);
1015 omprintf("To: %s%s", mptr, nl);
1016 else if (i == 'T') {
1018 omprintf("Date: %s", asctime(localtime(&xtime)));
1024 if (mode == MT_RFC822) {
1025 if (!strcasecmp(snode, NODENAME)) {
1026 strcpy(snode, FQDN);
1028 omprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
1029 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1030 omprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1031 omprintf("Organization: %s%s", lnode, nl);
1034 /* end header processing loop ... at this point, we're in the text */
1036 mptr = TheMessage->cm_fields['M'];
1038 /* Tell the client about the MIME parts in this message */
1039 if (TheMessage->cm_format_type == FMT_RFC822) { /* legacy text dump */
1040 if (mode == MT_CITADEL) {
1041 mime_parser(mptr, NULL, *list_this_part);
1043 else if (mode == MT_MIME) { /* list parts only */
1044 mime_parser(mptr, NULL, *list_this_part);
1045 if (do_proto) cprintf("000\n");
1046 CtdlFreeMessage(TheMessage);
1052 if (do_proto) cprintf("000\n");
1053 CtdlFreeMessage(TheMessage);
1057 /* signify start of msg text */
1058 if (mode == MT_CITADEL)
1059 if (do_proto) cprintf("text\n");
1060 /* if ((mode == MT_RFC822) && (TheMessage->cm_format_type != FMT_RFC822)) { */
1061 if (mode == MT_RFC822) {
1065 /* If the format type on disk is 1 (fixed-format), then we want
1066 * everything to be output completely literally ... regardless of
1067 * what message transfer format is in use.
1069 if (TheMessage->cm_format_type == FMT_FIXED) {
1071 while (ch = *mptr++, ch > 0) {
1074 if ((ch == 10) || (strlen(buf) > 250)) {
1075 omprintf("%s%s", buf, nl);
1078 buf[strlen(buf) + 1] = 0;
1079 buf[strlen(buf)] = ch;
1082 if (strlen(buf) > 0)
1083 omprintf("%s%s", buf, nl);
1086 /* If the message on disk is format 0 (Citadel vari-format), we
1087 * output using the formatter at 80 columns. This is the final output
1088 * form if the transfer format is RFC822, but if the transfer format
1089 * is Citadel proprietary, it'll still work, because the indentation
1090 * for new paragraphs is correct and the client will reformat the
1091 * message to the reader's screen width.
1093 if (TheMessage->cm_format_type == FMT_CITADEL) {
1097 /* If the message on disk is format 4 (MIME), we've gotta hand it
1098 * off to the MIME parser. The client has already been told that
1099 * this message is format 1 (fixed format), so the callback function
1100 * we use will display those parts as-is.
1102 if (TheMessage->cm_format_type == FMT_RFC822) {
1103 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1104 memset(ma, 0, sizeof(struct ma_info));
1105 mime_parser(mptr, NULL, *fixed_output);
1108 /* now we're done */
1109 if (do_proto) cprintf("000\n");
1110 CtdlFreeMessage(TheMessage);
1117 * display a message (mode 0 - Citadel proprietary)
1119 void cmd_msg0(char *cmdbuf)
1122 int headers_only = 0;
1124 msgid = extract_long(cmdbuf, 0);
1125 headers_only = extract_int(cmdbuf, 1);
1127 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, NULL, -1, 0);
1133 * display a message (mode 2 - RFC822)
1135 void cmd_msg2(char *cmdbuf)
1138 int headers_only = 0;
1140 msgid = extract_long(cmdbuf, 0);
1141 headers_only = extract_int(cmdbuf, 1);
1143 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, NULL, -1, 1);
1149 * display a message (mode 3 - IGnet raw format - internal programs only)
1151 void cmd_msg3(char *cmdbuf)
1154 struct CtdlMessage *msg;
1157 if (CC->internal_pgm == 0) {
1158 cprintf("%d This command is for internal programs only.\n",
1163 msgnum = extract_long(cmdbuf, 0);
1164 msg = CtdlFetchMessage(msgnum);
1166 cprintf("%d Message %ld not found.\n",
1171 serialize_message(&smr, msg);
1172 CtdlFreeMessage(msg);
1175 cprintf("%d Unable to serialize message\n",
1176 ERROR+INTERNAL_ERROR);
1180 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1181 client_write(smr.ser, smr.len);
1188 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1190 void cmd_msg4(char *cmdbuf)
1194 msgid = extract_long(cmdbuf, 0);
1195 CtdlOutputMsg(msgid, MT_MIME, 0, 1, NULL, -1, 0);
1199 * Open a component of a MIME message as a download file
1201 void cmd_opna(char *cmdbuf)
1205 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1207 msgid = extract_long(cmdbuf, 0);
1208 extract(desired_section, cmdbuf, 1);
1210 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, NULL, -1, 1);
1215 * Save a message pointer into a specified room
1216 * (Returns 0 for success, nonzero for failure)
1217 * roomname may be NULL to use the current room
1219 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1221 char hold_rm[ROOMNAMELEN];
1222 struct cdbdata *cdbfr;
1225 long highest_msg = 0L;
1226 struct CtdlMessage *msg = NULL;
1228 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1229 roomname, msgid, flags);
1231 strcpy(hold_rm, CC->quickroom.QRname);
1233 /* We may need to check to see if this message is real */
1234 if ( (flags & SM_VERIFY_GOODNESS)
1235 || (flags & SM_DO_REPL_CHECK)
1237 msg = CtdlFetchMessage(msgid);
1238 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1241 /* Perform replication checks if necessary */
1242 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1244 if (getroom(&CC->quickroom,
1245 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1247 lprintf(9, "No such room <%s>\n", roomname);
1248 if (msg != NULL) CtdlFreeMessage(msg);
1249 return(ERROR + ROOM_NOT_FOUND);
1252 if (ReplicationChecks(msg) != 0) {
1253 getroom(&CC->quickroom, hold_rm);
1254 if (msg != NULL) CtdlFreeMessage(msg);
1255 lprintf(9, "Did replication, and newer exists\n");
1260 /* Now the regular stuff */
1261 if (lgetroom(&CC->quickroom,
1262 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1264 lprintf(9, "No such room <%s>\n", roomname);
1265 if (msg != NULL) CtdlFreeMessage(msg);
1266 return(ERROR + ROOM_NOT_FOUND);
1269 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1270 if (cdbfr == NULL) {
1274 msglist = mallok(cdbfr->len);
1275 if (msglist == NULL)
1276 lprintf(3, "ERROR malloc msglist!\n");
1277 num_msgs = cdbfr->len / sizeof(long);
1278 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1283 /* Make sure the message doesn't already exist in this room. It
1284 * is absolutely taboo to have more than one reference to the same
1285 * message in a room.
1287 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1288 if (msglist[i] == msgid) {
1289 lputroom(&CC->quickroom); /* unlock the room */
1290 getroom(&CC->quickroom, hold_rm);
1291 if (msg != NULL) CtdlFreeMessage(msg);
1292 return(ERROR + ALREADY_EXISTS);
1296 /* Now add the new message */
1298 msglist = reallok(msglist,
1299 (num_msgs * sizeof(long)));
1301 if (msglist == NULL) {
1302 lprintf(3, "ERROR: can't realloc message list!\n");
1304 msglist[num_msgs - 1] = msgid;
1306 /* Sort the message list, so all the msgid's are in order */
1307 num_msgs = sort_msglist(msglist, num_msgs);
1309 /* Determine the highest message number */
1310 highest_msg = msglist[num_msgs - 1];
1312 /* Write it back to disk. */
1313 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1314 msglist, num_msgs * sizeof(long));
1316 /* Free up the memory we used. */
1319 /* Update the highest-message pointer and unlock the room. */
1320 CC->quickroom.QRhighest = highest_msg;
1321 lputroom(&CC->quickroom);
1322 getroom(&CC->quickroom, hold_rm);
1324 /* Bump the reference count for this message. */
1325 if ((flags & SM_DONT_BUMP_REF)==0) {
1326 AdjRefCount(msgid, +1);
1329 /* Return success. */
1330 if (msg != NULL) CtdlFreeMessage(msg);
1337 * Message base operation to send a message to the master file
1338 * (returns new message number)
1340 * This is the back end for CtdlSaveMsg() and should not be directly
1341 * called by server-side modules.
1344 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1345 int generate_id, /* generate 'I' field? */
1346 FILE *save_a_copy) /* save a copy to disk? */
1353 /* Get a new message number */
1354 newmsgid = get_new_message_number();
1355 sprintf(msgidbuf, "%ld", newmsgid);
1358 msg->cm_fields['I'] = strdoop(msgidbuf);
1361 serialize_message(&smr, msg);
1364 cprintf("%d Unable to serialize message\n",
1365 ERROR+INTERNAL_ERROR);
1369 /* Write our little bundle of joy into the message base */
1370 begin_critical_section(S_MSGMAIN);
1371 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1372 smr.ser, smr.len) < 0) {
1373 lprintf(2, "Can't store message\n");
1378 end_critical_section(S_MSGMAIN);
1380 /* If the caller specified that a copy should be saved to a particular
1381 * file handle, do that now too.
1383 if (save_a_copy != NULL) {
1384 fwrite(smr.ser, smr.len, 1, save_a_copy);
1387 /* Free the memory we used for the serialized message */
1390 /* Return the *local* message ID to the caller
1391 * (even if we're storing an incoming network message)
1399 * Serialize a struct CtdlMessage into the format used on disk and network.
1401 * This function loads up a "struct ser_ret" (defined in server.h) which
1402 * contains the length of the serialized message and a pointer to the
1403 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1405 void serialize_message(struct ser_ret *ret, /* return values */
1406 struct CtdlMessage *msg) /* unserialized msg */
1410 static char *forder = FORDER;
1412 if (is_valid_message(msg) == 0) return; /* self check */
1415 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1416 ret->len = ret->len +
1417 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1419 lprintf(9, "calling malloc\n");
1420 ret->ser = mallok(ret->len);
1421 if (ret->ser == NULL) {
1427 ret->ser[1] = msg->cm_anon_type;
1428 ret->ser[2] = msg->cm_format_type;
1431 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1432 ret->ser[wlen++] = (char)forder[i];
1433 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1434 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1436 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1445 * Back end for the ReplicationChecks() function
1447 void check_repl(long msgnum) {
1448 struct CtdlMessage *msg;
1449 time_t timestamp = (-1L);
1451 lprintf(9, "check_repl() found message %ld\n", msgnum);
1452 msg = CtdlFetchMessage(msgnum);
1453 if (msg == NULL) return;
1454 if (msg->cm_fields['T'] != NULL) {
1455 timestamp = atol(msg->cm_fields['T']);
1457 CtdlFreeMessage(msg);
1459 if (timestamp > msg_repl->highest) {
1460 msg_repl->highest = timestamp; /* newer! */
1461 lprintf(9, "newer!\n");
1464 lprintf(9, "older!\n");
1466 /* Existing isn't newer? Then delete the old one(s). */
1467 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1472 * Check to see if any messages already exist which carry the same Extended ID
1476 * -> With older timestamps: delete them and return 0. Message will be saved.
1477 * -> With newer timestamps: return 1. Message save will be aborted.
1479 int ReplicationChecks(struct CtdlMessage *msg) {
1480 struct CtdlMessage *template;
1483 lprintf(9, "ReplicationChecks() started\n");
1484 /* No extended id? Don't do anything. */
1485 if (msg->cm_fields['E'] == NULL) return 0;
1486 if (strlen(msg->cm_fields['E']) == 0) return 0;
1487 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1489 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1490 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1491 msg_repl->highest = atol(msg->cm_fields['T']);
1493 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1494 memset(template, 0, sizeof(struct CtdlMessage));
1495 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1497 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1499 /* If a newer message exists with the same Extended ID, abort
1502 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1506 CtdlFreeMessage(template);
1507 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1515 * Save a message to disk
1517 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1518 char *rec, /* Recipient (mail) */
1519 char *force, /* force a particular room? */
1520 int mailtype, /* local or remote type */
1521 int generate_id) /* 1 = generate 'I' field */
1524 char hold_rm[ROOMNAMELEN];
1525 char actual_rm[ROOMNAMELEN];
1526 char force_room[ROOMNAMELEN];
1527 char content_type[256]; /* We have to learn this */
1528 char recipient[256];
1531 struct usersupp userbuf;
1533 struct SuppMsgInfo smi;
1534 FILE *network_fp = NULL;
1535 static int seqnum = 1;
1536 struct CtdlMessage *imsg;
1539 lprintf(9, "CtdlSaveMsg() called\n");
1540 if (is_valid_message(msg) == 0) return(-1); /* self check */
1542 /* If this message has no timestamp, we take the liberty of
1543 * giving it one, right now.
1545 if (msg->cm_fields['T'] == NULL) {
1546 lprintf(9, "Generating timestamp\n");
1547 sprintf(aaa, "%ld", time(NULL));
1548 msg->cm_fields['T'] = strdoop(aaa);
1551 /* If this message has no path, we generate one.
1553 if (msg->cm_fields['P'] == NULL) {
1554 lprintf(9, "Generating path\n");
1555 if (msg->cm_fields['A'] != NULL) {
1556 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1557 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1558 if (isspace(msg->cm_fields['P'][a])) {
1559 msg->cm_fields['P'][a] = ' ';
1564 msg->cm_fields['P'] = strdoop("unknown");
1568 strcpy(force_room, force);
1570 /* Strip non-printable characters out of the recipient name */
1571 strcpy(recipient, rec);
1572 for (a = 0; a < strlen(recipient); ++a)
1573 if (!isprint(recipient[a]))
1574 strcpy(&recipient[a], &recipient[a + 1]);
1576 /* Learn about what's inside, because it's what's inside that counts */
1577 lprintf(9, "Learning what's inside\n");
1578 if (msg->cm_fields['M'] == NULL) {
1579 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1582 switch (msg->cm_format_type) {
1584 strcpy(content_type, "text/x-citadel-variformat");
1587 strcpy(content_type, "text/plain");
1590 strcpy(content_type, "text/plain");
1591 /* advance past header fields */
1592 mptr = msg->cm_fields['M'];
1595 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1596 safestrncpy(content_type, mptr,
1597 sizeof(content_type));
1598 strcpy(content_type, &content_type[14]);
1599 for (a = 0; a < strlen(content_type); ++a)
1600 if ((content_type[a] == ';')
1601 || (content_type[a] == ' ')
1602 || (content_type[a] == 13)
1603 || (content_type[a] == 10))
1604 content_type[a] = 0;
1611 /* Goto the correct room */
1612 lprintf(9, "Switching rooms\n");
1613 strcpy(hold_rm, CC->quickroom.QRname);
1614 strcpy(actual_rm, CC->quickroom.QRname);
1616 /* If the user is a twit, move to the twit room for posting */
1617 lprintf(9, "Handling twit stuff\n");
1619 if (CC->usersupp.axlevel == 2) {
1620 strcpy(hold_rm, actual_rm);
1621 strcpy(actual_rm, config.c_twitroom);
1625 /* ...or if this message is destined for Aide> then go there. */
1626 if (strlen(force_room) > 0) {
1627 strcpy(actual_rm, force_room);
1630 lprintf(9, "Possibly relocating\n");
1631 if (strcasecmp(actual_rm, CC->quickroom.QRname))
1632 getroom(&CC->quickroom, actual_rm);
1634 /* Perform "before save" hooks (aborting if any return nonzero) */
1635 lprintf(9, "Performing before-save hooks\n");
1636 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1638 /* If this message has an Extended ID, perform replication checks */
1639 lprintf(9, "Performing replication checks\n");
1640 if (ReplicationChecks(msg) > 0) return(-1);
1642 /* Network mail - send a copy to the network program. */
1643 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1644 lprintf(9, "Sending network spool\n");
1645 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1646 (long) getpid(), CC->cs_pid, ++seqnum);
1647 lprintf(9, "Saving a copy to %s\n", aaa);
1648 network_fp = fopen(aaa, "ab+");
1649 if (network_fp == NULL)
1650 lprintf(2, "ERROR: %s\n", strerror(errno));
1653 /* Save it to disk */
1654 lprintf(9, "Saving to disk\n");
1655 newmsgid = send_message(msg, generate_id, network_fp);
1656 if (network_fp != NULL) {
1658 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1661 if (newmsgid <= 0L) return(-1);
1663 /* Write a supplemental message info record. This doesn't have to
1664 * be a critical section because nobody else knows about this message
1667 lprintf(9, "Creating SuppMsgInfo record\n");
1668 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1669 smi.smi_msgnum = newmsgid;
1670 smi.smi_refcount = 0;
1671 safestrncpy(smi.smi_content_type, content_type, 64);
1672 PutSuppMsgInfo(&smi);
1674 /* Now figure out where to store the pointers */
1675 lprintf(9, "Storing pointers\n");
1677 /* If this is being done by the networker delivering a private
1678 * message, we want to BYPASS saving the sender's copy (because there
1679 * is no local sender; it would otherwise go to the Trashcan).
1681 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1682 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1685 /* For internet mail, drop a copy in the outbound queue room */
1686 if (mailtype == MES_INTERNET) {
1687 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1690 /* Bump this user's messages posted counter. */
1691 lprintf(9, "Updating user\n");
1692 lgetuser(&CC->usersupp, CC->curr_user);
1693 CC->usersupp.posted = CC->usersupp.posted + 1;
1694 lputuser(&CC->usersupp);
1696 /* If this is private, local mail, make a copy in the
1697 * recipient's mailbox and bump the reference count.
1699 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1700 if (getuser(&userbuf, recipient) == 0) {
1701 lprintf(9, "Delivering private mail\n");
1702 MailboxName(actual_rm, &userbuf, MAILROOM);
1703 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1707 /* Perform "after save" hooks */
1708 lprintf(9, "Performing after-save hooks\n");
1709 PerformMessageHooks(msg, EVT_AFTERSAVE);
1712 lprintf(9, "Returning to original room\n");
1713 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1714 getroom(&CC->quickroom, hold_rm);
1716 /* For internet mail, generate delivery instructions
1717 * (Yes, this is recursive! Deal with it!)
1719 if (mailtype == MES_INTERNET) {
1720 lprintf(9, "Generating delivery instructions\n");
1721 instr = mallok(2048);
1723 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1725 SPOOLMIME, newmsgid, time(NULL), recipient );
1727 imsg = mallok(sizeof(struct CtdlMessage));
1728 memset(imsg, 0, sizeof(struct CtdlMessage));
1729 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1730 imsg->cm_anon_type = MES_NORMAL;
1731 imsg->cm_format_type = FMT_RFC822;
1732 imsg->cm_fields['A'] = strdoop("Citadel");
1733 imsg->cm_fields['M'] = instr;
1734 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1735 CtdlFreeMessage(imsg);
1744 * Convenience function for generating small administrative messages.
1746 void quickie_message(char *from, char *to, char *room, char *text)
1748 struct CtdlMessage *msg;
1750 msg = mallok(sizeof(struct CtdlMessage));
1751 memset(msg, 0, sizeof(struct CtdlMessage));
1752 msg->cm_magic = CTDLMESSAGE_MAGIC;
1753 msg->cm_anon_type = MES_NORMAL;
1754 msg->cm_format_type = 0;
1755 msg->cm_fields['A'] = strdoop(from);
1756 msg->cm_fields['O'] = strdoop(room);
1757 msg->cm_fields['N'] = strdoop(NODENAME);
1759 msg->cm_fields['R'] = strdoop(to);
1760 msg->cm_fields['M'] = strdoop(text);
1762 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1763 CtdlFreeMessage(msg);
1764 syslog(LOG_NOTICE, text);
1770 * Back end function used by make_message() and similar functions
1772 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1773 size_t maxlen, /* maximum message length */
1774 char *exist /* if non-null, append to it;
1775 exist is ALWAYS freed */
1778 size_t message_len = 0;
1779 size_t buffer_len = 0;
1783 if (exist == NULL) {
1787 m = reallok(exist, strlen(exist) + 4096);
1788 if (m == NULL) phree(exist);
1791 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1798 /* read in the lines of message text one by one */
1800 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1802 /* augment the buffer if we have to */
1803 if ((message_len + strlen(buf) + 2) > buffer_len) {
1804 lprintf(9, "realloking\n");
1805 ptr = reallok(m, (buffer_len * 2) );
1806 if (ptr == NULL) { /* flush if can't allocate */
1807 while ( (client_gets(buf)>0) &&
1808 strcmp(buf, terminator)) ;;
1811 buffer_len = (buffer_len * 2);
1814 lprintf(9, "buffer_len is %d\n", buffer_len);
1818 if (append == NULL) append = m;
1819 while (strlen(append) > 0) ++append;
1820 strcpy(append, buf);
1821 strcat(append, "\n");
1822 message_len = message_len + strlen(buf) + 1;
1824 /* if we've hit the max msg length, flush the rest */
1825 if (message_len >= maxlen) {
1826 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1837 * Build a binary message to be saved on disk.
1840 struct CtdlMessage *make_message(
1841 struct usersupp *author, /* author's usersupp structure */
1842 char *recipient, /* NULL if it's not mail */
1843 char *room, /* room where it's going */
1844 int type, /* see MES_ types in header file */
1845 int net_type, /* see MES_ types in header file */
1846 int format_type, /* local or remote (see citadel.h) */
1847 char *fake_name) /* who we're masquerading as */
1853 struct CtdlMessage *msg;
1855 msg = mallok(sizeof(struct CtdlMessage));
1856 memset(msg, 0, sizeof(struct CtdlMessage));
1857 msg->cm_magic = CTDLMESSAGE_MAGIC;
1858 msg->cm_anon_type = type;
1859 msg->cm_format_type = format_type;
1861 /* Don't confuse the poor folks if it's not routed mail. */
1862 strcpy(dest_node, "");
1864 /* If net_type is MES_BINARY, split out the destination node. */
1865 if (net_type == MES_BINARY) {
1866 strcpy(dest_node, NODENAME);
1867 for (a = 0; a < strlen(recipient); ++a) {
1868 if (recipient[a] == '@') {
1870 strcpy(dest_node, &recipient[a + 1]);
1875 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1876 if (net_type == MES_INTERNET) {
1877 strcpy(dest_node, "internet");
1880 while (isspace(recipient[strlen(recipient) - 1]))
1881 recipient[strlen(recipient) - 1] = 0;
1883 sprintf(buf, "cit%ld", author->usernum); /* Path */
1884 msg->cm_fields['P'] = strdoop(buf);
1886 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1887 msg->cm_fields['T'] = strdoop(buf);
1889 if (fake_name[0]) /* author */
1890 msg->cm_fields['A'] = strdoop(fake_name);
1892 msg->cm_fields['A'] = strdoop(author->fullname);
1894 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1895 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1897 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1899 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1900 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1902 if (recipient[0] != 0)
1903 msg->cm_fields['R'] = strdoop(recipient);
1904 if (dest_node[0] != 0)
1905 msg->cm_fields['D'] = strdoop(dest_node);
1908 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1909 config.c_maxmsglen, NULL);
1920 * message entry - mode 0 (normal)
1922 void cmd_ent0(char *entargs)
1925 char recipient[256];
1927 int format_type = 0;
1928 char newusername[256];
1929 struct CtdlMessage *msg;
1933 struct usersupp tempUS;
1936 post = extract_int(entargs, 0);
1937 extract(recipient, entargs, 1);
1938 anon_flag = extract_int(entargs, 2);
1939 format_type = extract_int(entargs, 3);
1941 /* first check to make sure the request is valid. */
1943 if (!(CC->logged_in)) {
1944 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1947 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1948 cprintf("%d Need to be validated to enter ",
1949 ERROR + HIGHER_ACCESS_REQUIRED);
1950 cprintf("(except in %s> to sysop)\n", MAILROOM);
1953 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1954 cprintf("%d Need net privileges to enter here.\n",
1955 ERROR + HIGHER_ACCESS_REQUIRED);
1958 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1959 cprintf("%d Sorry, this is a read-only room.\n",
1960 ERROR + HIGHER_ACCESS_REQUIRED);
1967 if (CC->usersupp.axlevel < 6) {
1968 cprintf("%d You don't have permission to masquerade.\n",
1969 ERROR + HIGHER_ACCESS_REQUIRED);
1972 extract(newusername, entargs, 4);
1973 memset(CC->fake_postname, 0, 32);
1974 strcpy(CC->fake_postname, newusername);
1975 cprintf("%d Ok\n", OK);
1978 CC->cs_flags |= CS_POSTING;
1981 if (CC->quickroom.QRflags & QR_MAILBOX) {
1982 if (CC->usersupp.axlevel >= 2) {
1983 strcpy(buf, recipient);
1985 strcpy(buf, "sysop");
1986 e = alias(buf); /* alias and mail type */
1987 if ((buf[0] == 0) || (e == MES_ERROR)) {
1988 cprintf("%d Unknown address - cannot send message.\n",
1989 ERROR + NO_SUCH_USER);
1992 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1993 cprintf("%d Net privileges required for network mail.\n",
1994 ERROR + HIGHER_ACCESS_REQUIRED);
1997 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1998 && ((CC->usersupp.flags & US_INTERNET) == 0)
1999 && (!CC->internal_pgm)) {
2000 cprintf("%d You don't have access to Internet mail.\n",
2001 ERROR + HIGHER_ACCESS_REQUIRED);
2004 if (!strcasecmp(buf, "sysop")) {
2009 goto SKFALL; /* don't search local file */
2010 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2011 cprintf("%d Can't send mail to yourself!\n",
2012 ERROR + NO_SUCH_USER);
2015 /* Check to make sure the user exists; also get the correct
2016 * upper/lower casing of the name.
2018 a = getuser(&tempUS, buf);
2020 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2023 strcpy(buf, tempUS.fullname);
2026 SKFALL: b = MES_NORMAL;
2027 if (CC->quickroom.QRflags & QR_ANONONLY)
2029 if (CC->quickroom.QRflags & QR_ANONOPT) {
2033 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2036 /* If we're only checking the validity of the request, return
2037 * success without creating the message.
2040 cprintf("%d %s\n", OK, buf);
2044 cprintf("%d send message\n", SEND_LISTING);
2046 /* Read in the message from the client. */
2047 if (CC->fake_postname[0])
2048 msg = make_message(&CC->usersupp, buf,
2049 CC->quickroom.QRname, b, e, format_type,
2051 else if (CC->fake_username[0])
2052 msg = make_message(&CC->usersupp, buf,
2053 CC->quickroom.QRname, b, e, format_type,
2056 msg = make_message(&CC->usersupp, buf,
2057 CC->quickroom.QRname, b, e, format_type, "");
2060 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2061 CtdlFreeMessage(msg);
2062 CC->fake_postname[0] = '\0';
2069 * message entry - mode 3 (raw)
2071 void cmd_ent3(char *entargs)
2077 unsigned char ch, which_field;
2078 struct usersupp tempUS;
2080 struct CtdlMessage *msg;
2083 if (CC->internal_pgm == 0) {
2084 cprintf("%d This command is for internal programs only.\n",
2089 /* See if there's a recipient, but make sure it's a real one */
2090 extract(recp, entargs, 1);
2091 for (a = 0; a < strlen(recp); ++a)
2092 if (!isprint(recp[a]))
2093 strcpy(&recp[a], &recp[a + 1]);
2094 while (isspace(recp[0]))
2095 strcpy(recp, &recp[1]);
2096 while (isspace(recp[strlen(recp) - 1]))
2097 recp[strlen(recp) - 1] = 0;
2099 /* If we're in Mail, check the recipient */
2100 if (strlen(recp) > 0) {
2101 e = alias(recp); /* alias and mail type */
2102 if ((recp[0] == 0) || (e == MES_ERROR)) {
2103 cprintf("%d Unknown address - cannot send message.\n",
2104 ERROR + NO_SUCH_USER);
2107 if (e == MES_LOCAL) {
2108 a = getuser(&tempUS, recp);
2110 cprintf("%d No such user.\n",
2111 ERROR + NO_SUCH_USER);
2117 /* At this point, message has been approved. */
2118 if (extract_int(entargs, 0) == 0) {
2119 cprintf("%d OK to send\n", OK);
2123 msglen = extract_long(entargs, 2);
2124 msg = mallok(sizeof(struct CtdlMessage));
2126 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2130 memset(msg, 0, sizeof(struct CtdlMessage));
2131 tempbuf = mallok(msglen);
2132 if (tempbuf == NULL) {
2133 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2138 cprintf("%d %ld\n", SEND_BINARY, msglen);
2140 client_read(&ch, 1); /* 0xFF magic number */
2141 msg->cm_magic = CTDLMESSAGE_MAGIC;
2142 client_read(&ch, 1); /* anon type */
2143 msg->cm_anon_type = ch;
2144 client_read(&ch, 1); /* format type */
2145 msg->cm_format_type = ch;
2146 msglen = msglen - 3;
2148 while (msglen > 0) {
2149 client_read(&which_field, 1);
2150 if (!isalpha(which_field)) valid_msg = 0;
2154 client_read(&ch, 1);
2156 a = strlen(tempbuf);
2159 } while ( (ch != 0) && (msglen > 0) );
2161 msg->cm_fields[which_field] = strdoop(tempbuf);
2164 msg->cm_flags = CM_SKIP_HOOKS;
2165 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2166 CtdlFreeMessage(msg);
2172 * API function to delete messages which match a set of criteria
2173 * (returns the actual number of messages deleted)
2175 int CtdlDeleteMessages(char *room_name, /* which room */
2176 long dmsgnum, /* or "0" for any */
2177 char *content_type /* or NULL for any */
2181 struct quickroom qrbuf;
2182 struct cdbdata *cdbfr;
2183 long *msglist = NULL;
2186 int num_deleted = 0;
2188 struct SuppMsgInfo smi;
2190 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2191 room_name, dmsgnum, content_type);
2193 /* get room record, obtaining a lock... */
2194 if (lgetroom(&qrbuf, room_name) != 0) {
2195 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2197 return (0); /* room not found */
2199 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2201 if (cdbfr != NULL) {
2202 msglist = mallok(cdbfr->len);
2203 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2204 num_msgs = cdbfr->len / sizeof(long);
2208 for (i = 0; i < num_msgs; ++i) {
2211 /* Set/clear a bit for each criterion */
2213 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2214 delete_this |= 0x01;
2216 if (content_type == NULL) {
2217 delete_this |= 0x02;
2219 GetSuppMsgInfo(&smi, msglist[i]);
2220 if (!strcasecmp(smi.smi_content_type,
2222 delete_this |= 0x02;
2226 /* Delete message only if all bits are set */
2227 if (delete_this == 0x03) {
2228 AdjRefCount(msglist[i], -1);
2234 num_msgs = sort_msglist(msglist, num_msgs);
2235 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2236 msglist, (num_msgs * sizeof(long)));
2238 qrbuf.QRhighest = msglist[num_msgs - 1];
2242 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2243 return (num_deleted);
2249 * Delete message from current room
2251 void cmd_dele(char *delstr)
2256 getuser(&CC->usersupp, CC->curr_user);
2257 if ((CC->usersupp.axlevel < 6)
2258 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2259 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2260 && (!(CC->internal_pgm))) {
2261 cprintf("%d Higher access required.\n",
2262 ERROR + HIGHER_ACCESS_REQUIRED);
2265 delnum = extract_long(delstr, 0);
2267 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2270 cprintf("%d %d message%s deleted.\n", OK,
2271 num_deleted, ((num_deleted != 1) ? "s" : ""));
2273 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2279 * move or copy a message to another room
2281 void cmd_move(char *args)
2285 struct quickroom qtemp;
2289 num = extract_long(args, 0);
2290 extract(targ, args, 1);
2291 targ[ROOMNAMELEN - 1] = 0;
2292 is_copy = extract_int(args, 2);
2294 getuser(&CC->usersupp, CC->curr_user);
2295 if ((CC->usersupp.axlevel < 6)
2296 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2297 cprintf("%d Higher access required.\n",
2298 ERROR + HIGHER_ACCESS_REQUIRED);
2302 if (getroom(&qtemp, targ) != 0) {
2303 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2307 err = CtdlSaveMsgPointerInRoom(targ, num,
2308 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2310 cprintf("%d Cannot store message in %s: error %d\n",
2315 /* Now delete the message from the source room,
2316 * if this is a 'move' rather than a 'copy' operation.
2318 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2320 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2326 * GetSuppMsgInfo() - Get the supplementary record for a message
2328 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2331 struct cdbdata *cdbsmi;
2334 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2335 smibuf->smi_msgnum = msgnum;
2336 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2338 /* Use the negative of the message number for its supp record index */
2339 TheIndex = (0L - msgnum);
2341 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2342 if (cdbsmi == NULL) {
2343 return; /* record not found; go with defaults */
2345 memcpy(smibuf, cdbsmi->ptr,
2346 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2347 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2354 * PutSuppMsgInfo() - (re)write supplementary record for a message
2356 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2360 /* Use the negative of the message number for its supp record index */
2361 TheIndex = (0L - smibuf->smi_msgnum);
2363 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2364 smibuf->smi_msgnum, smibuf->smi_refcount);
2366 cdb_store(CDB_MSGMAIN,
2367 &TheIndex, sizeof(long),
2368 smibuf, sizeof(struct SuppMsgInfo));
2373 * AdjRefCount - change the reference count for a message;
2374 * delete the message if it reaches zero
2376 void AdjRefCount(long msgnum, int incr)
2379 struct SuppMsgInfo smi;
2382 /* This is a *tight* critical section; please keep it that way, as
2383 * it may get called while nested in other critical sections.
2384 * Complicating this any further will surely cause deadlock!
2386 begin_critical_section(S_SUPPMSGMAIN);
2387 GetSuppMsgInfo(&smi, msgnum);
2388 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2389 msgnum, smi.smi_refcount);
2390 smi.smi_refcount += incr;
2391 PutSuppMsgInfo(&smi);
2392 end_critical_section(S_SUPPMSGMAIN);
2393 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2394 msgnum, smi.smi_refcount);
2396 /* If the reference count is now zero, delete the message
2397 * (and its supplementary record as well).
2399 if (smi.smi_refcount == 0) {
2400 lprintf(9, "Deleting message <%ld>\n", msgnum);
2402 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2403 delnum = (0L - msgnum);
2404 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2409 * Write a generic object to this room
2411 * Note: this could be much more efficient. Right now we use two temporary
2412 * files, and still pull the message into memory as with all others.
2414 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2415 char *content_type, /* MIME type of this object */
2416 char *tempfilename, /* Where to fetch it from */
2417 struct usersupp *is_mailbox, /* Mailbox room? */
2418 int is_binary, /* Is encoding necessary? */
2419 int is_unique, /* Del others of this type? */
2420 unsigned int flags /* Internal save flags */
2425 char filename[PATH_MAX];
2428 struct quickroom qrbuf;
2429 char roomname[ROOMNAMELEN];
2430 struct CtdlMessage *msg;
2433 if (is_mailbox != NULL)
2434 MailboxName(roomname, is_mailbox, req_room);
2436 safestrncpy(roomname, req_room, sizeof(roomname));
2437 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2439 strcpy(filename, tmpnam(NULL));
2440 fp = fopen(filename, "w");
2444 tempfp = fopen(tempfilename, "r");
2445 if (tempfp == NULL) {
2451 fprintf(fp, "Content-type: %s\n", content_type);
2452 lprintf(9, "Content-type: %s\n", content_type);
2454 if (is_binary == 0) {
2455 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2456 while (ch = getc(tempfp), ch > 0)
2462 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2465 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2466 tempfilename, filename);
2470 lprintf(9, "Allocating\n");
2471 msg = mallok(sizeof(struct CtdlMessage));
2472 memset(msg, 0, sizeof(struct CtdlMessage));
2473 msg->cm_magic = CTDLMESSAGE_MAGIC;
2474 msg->cm_anon_type = MES_NORMAL;
2475 msg->cm_format_type = 4;
2476 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2477 msg->cm_fields['O'] = strdoop(req_room);
2478 msg->cm_fields['N'] = strdoop(config.c_nodename);
2479 msg->cm_fields['H'] = strdoop(config.c_humannode);
2480 msg->cm_flags = flags;
2482 lprintf(9, "Loading\n");
2483 fp = fopen(filename, "rb");
2484 fseek(fp, 0L, SEEK_END);
2487 msg->cm_fields['M'] = mallok(len);
2488 fread(msg->cm_fields['M'], len, 1, fp);
2492 /* Create the requested room if we have to. */
2493 if (getroom(&qrbuf, roomname) != 0) {
2494 create_room(roomname,
2495 ( (is_mailbox != NULL) ? 4 : 3 ),
2498 /* If the caller specified this object as unique, delete all
2499 * other objects of this type that are currently in the room.
2502 lprintf(9, "Deleted %d other msgs of this type\n",
2503 CtdlDeleteMessages(roomname, 0L, content_type));
2505 /* Now write the data */
2506 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2507 CtdlFreeMessage(msg);
2515 void CtdlGetSysConfigBackend(long msgnum) {
2516 config_msgnum = msgnum;
2520 char *CtdlGetSysConfig(char *sysconfname) {
2521 char hold_rm[ROOMNAMELEN];
2524 struct CtdlMessage *msg;
2527 strcpy(hold_rm, CC->quickroom.QRname);
2528 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2529 getroom(&CC->quickroom, hold_rm);
2534 /* We want the last (and probably only) config in this room */
2535 begin_critical_section(S_CONFIG);
2536 config_msgnum = (-1L);
2537 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2538 CtdlGetSysConfigBackend);
2539 msgnum = config_msgnum;
2540 end_critical_section(S_CONFIG);
2546 msg = CtdlFetchMessage(msgnum);
2548 conf = strdoop(msg->cm_fields['M']);
2549 CtdlFreeMessage(msg);
2556 getroom(&CC->quickroom, hold_rm);
2558 lprintf(9, "eggstracting...\n");
2559 if (conf != NULL) do {
2560 extract_token(buf, conf, 0, '\n');
2561 lprintf(9, "eggstracted <%s>\n", buf);
2562 strcpy(conf, &conf[strlen(buf)+1]);
2563 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2568 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2569 char temp[PATH_MAX];
2572 strcpy(temp, tmpnam(NULL));
2574 fp = fopen(temp, "w");
2575 if (fp == NULL) return;
2576 fprintf(fp, "%s", sysconfdata);
2579 /* this handy API function does all the work for us */
2580 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);