21 #include "sysdep_decls.h"
22 #include "citserver.h"
27 #include "dynloader.h"
29 #include "mime_parser.h"
32 #include "internet_addressing.h"
34 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
35 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
36 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
38 extern struct config config;
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
47 "", "", "", "", "", "", "", "",
48 "", "", "", "", "", "", "", "",
49 "", "", "", "", "", "", "", "",
76 * This function is self explanatory.
77 * (What can I say, I'm in a weird mood today...)
79 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
83 for (i = 0; i < strlen(name); ++i)
86 if (isspace(name[i - 1])) {
87 strcpy(&name[i - 1], &name[i]);
90 while (isspace(name[i + 1])) {
91 strcpy(&name[i + 1], &name[i + 2]);
98 * Aliasing for network mail.
99 * (Error messages have been commented out, because this is a server.)
101 int alias(char *name)
102 { /* process alias and routing info for mail */
105 char aaa[300], bbb[300];
107 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
109 fp = fopen("network/mail.aliases", "r");
111 fp = fopen("/dev/null", "r");
116 while (fgets(aaa, sizeof aaa, fp) != NULL) {
117 while (isspace(name[0]))
118 strcpy(name, &name[1]);
119 aaa[strlen(aaa) - 1] = 0;
121 for (a = 0; a < strlen(aaa); ++a) {
123 strcpy(bbb, &aaa[a + 1]);
127 if (!strcasecmp(name, aaa))
131 lprintf(7, "Mail is being forwarded to %s\n", name);
133 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
134 for (a=0; a<strlen(name); ++a) {
135 if (name[a] == '@') {
136 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
138 lprintf(7, "Changed to <%s>\n", name);
143 /* determine local or remote type, see citadel.h */
144 for (a = 0; a < strlen(name); ++a)
146 return (MES_INTERNET);
147 for (a = 0; a < strlen(name); ++a)
149 for (b = a; b < strlen(name); ++b)
151 return (MES_INTERNET);
153 for (a = 0; a < strlen(name); ++a)
157 lprintf(7, "Too many @'s in address\n");
161 for (a = 0; a < strlen(name); ++a)
163 strcpy(bbb, &name[a + 1]);
165 strcpy(bbb, &bbb[1]);
166 fp = fopen("network/mail.sysinfo", "r");
170 a = getstring(fp, aaa);
171 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
172 a = getstring(fp, aaa);
173 if (!strncmp(aaa, "use ", 4)) {
174 strcpy(bbb, &aaa[4]);
179 if (!strncmp(aaa, "uum", 3)) {
181 for (a = 0; a < strlen(bbb); ++a) {
187 while (bbb[strlen(bbb) - 1] == '_')
188 bbb[strlen(bbb) - 1] = 0;
189 sprintf(name, &aaa[4], bbb);
190 return (MES_INTERNET);
192 if (!strncmp(aaa, "bin", 3)) {
195 while (aaa[strlen(aaa) - 1] != '@')
196 aaa[strlen(aaa) - 1] = 0;
197 aaa[strlen(aaa) - 1] = 0;
198 while (aaa[strlen(aaa) - 1] == ' ')
199 aaa[strlen(aaa) - 1] = 0;
200 while (bbb[0] != '@')
201 strcpy(bbb, &bbb[1]);
202 strcpy(bbb, &bbb[1]);
203 while (bbb[0] == ' ')
204 strcpy(bbb, &bbb[1]);
205 sprintf(name, "%s @%s", aaa, bbb);
218 fp = fopen("citadel.control", "r");
219 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
225 void simple_listing(long msgnum)
227 cprintf("%ld\n", msgnum);
232 /* Determine if a given message matches the fields in a message template.
233 * Return 0 for a successful match.
235 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
238 /* If there aren't any fields in the template, all messages will
241 if (template == NULL) return(0);
243 /* Null messages are bogus. */
244 if (msg == NULL) return(1);
246 for (i='A'; i<='Z'; ++i) {
247 if (template->cm_fields[i] != NULL) {
248 if (msg->cm_fields[i] == NULL) {
251 if (strcasecmp(msg->cm_fields[i],
252 template->cm_fields[i])) return 1;
256 /* All compares succeeded: we have a match! */
264 * API function to perform an operation for each qualifying message in the
267 void CtdlForEachMessage(int mode, long ref,
269 struct CtdlMessage *compare,
270 void (*CallBack) (long msgnum))
275 struct cdbdata *cdbfr;
276 long *msglist = NULL;
279 struct SuppMsgInfo smi;
280 struct CtdlMessage *msg;
282 /* Learn about the user and room in question */
284 getuser(&CC->usersupp, CC->curr_user);
285 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
287 /* Load the message list */
288 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
290 msglist = mallok(cdbfr->len);
291 memcpy(msglist, cdbfr->ptr, cdbfr->len);
292 num_msgs = cdbfr->len / sizeof(long);
295 return; /* No messages at all? No further action. */
299 /* If the caller is looking for a specific MIME type, then filter
300 * out all messages which are not of the type requested.
303 if (content_type != NULL)
304 if (strlen(content_type) > 0)
305 for (a = 0; a < num_msgs; ++a) {
306 GetSuppMsgInfo(&smi, msglist[a]);
307 if (strcasecmp(smi.smi_content_type, content_type)) {
312 num_msgs = sort_msglist(msglist, num_msgs);
314 /* If a template was supplied, filter out the messages which
315 * don't match. (This could induce some delays!)
318 if (compare != NULL) {
319 for (a = 0; a < num_msgs; ++a) {
320 msg = CtdlFetchMessage(msglist[a]);
322 if (CtdlMsgCmp(msg, compare)) {
325 CtdlFreeMessage(msg);
333 * Now iterate through the message list, according to the
334 * criteria supplied by the caller.
337 for (a = 0; a < num_msgs; ++a) {
338 thismsg = msglist[a];
343 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
344 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
345 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
346 && (CC->usersupp.flags & US_LASTOLD))
347 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
348 || ((mode == MSGS_FIRST) && (a < ref))
349 || ((mode == MSGS_GT) && (thismsg > ref))
355 phree(msglist); /* Clean up */
361 * cmd_msgs() - get list of message #'s in this room
362 * implements the MSGS server command using CtdlForEachMessage()
364 void cmd_msgs(char *cmdbuf)
373 int with_template = 0;
374 struct CtdlMessage *template = NULL;
376 extract(which, cmdbuf, 0);
377 cm_ref = extract_int(cmdbuf, 1);
378 with_template = extract_int(cmdbuf, 2);
382 if (!strncasecmp(which, "OLD", 3))
384 else if (!strncasecmp(which, "NEW", 3))
386 else if (!strncasecmp(which, "FIRST", 5))
388 else if (!strncasecmp(which, "LAST", 4))
390 else if (!strncasecmp(which, "GT", 2))
393 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
394 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
399 cprintf("%d Send template then receive message list\n",
401 template = (struct CtdlMessage *)
402 mallok(sizeof(struct CtdlMessage));
403 memset(template, 0, sizeof(struct CtdlMessage));
404 while(client_gets(buf), strcmp(buf,"000")) {
405 extract(tfield, buf, 0);
406 extract(tvalue, buf, 1);
407 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
408 if (!strcasecmp(tfield, msgkeys[i])) {
409 template->cm_fields[i] =
416 cprintf("%d Message list...\n", LISTING_FOLLOWS);
419 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
420 if (template != NULL) CtdlFreeMessage(template);
428 * help_subst() - support routine for help file viewer
430 void help_subst(char *strbuf, char *source, char *dest)
435 while (p = pattern2(strbuf, source), (p >= 0)) {
436 strcpy(workbuf, &strbuf[p + strlen(source)]);
437 strcpy(&strbuf[p], dest);
438 strcat(strbuf, workbuf);
443 void do_help_subst(char *buffer)
447 help_subst(buffer, "^nodename", config.c_nodename);
448 help_subst(buffer, "^humannode", config.c_humannode);
449 help_subst(buffer, "^fqdn", config.c_fqdn);
450 help_subst(buffer, "^username", CC->usersupp.fullname);
451 sprintf(buf2, "%ld", CC->usersupp.usernum);
452 help_subst(buffer, "^usernum", buf2);
453 help_subst(buffer, "^sysadm", config.c_sysadm);
454 help_subst(buffer, "^variantname", CITADEL);
455 sprintf(buf2, "%d", config.c_maxsessions);
456 help_subst(buffer, "^maxsessions", buf2);
462 * memfmout() - Citadel text formatter and paginator.
463 * Although the original purpose of this routine was to format
464 * text to the reader's screen width, all we're really using it
465 * for here is to format text out to 80 columns before sending it
466 * to the client. The client software may reformat it again.
469 int width, /* screen width to use */
470 char *mptr, /* where are we going to get our text from? */
471 char subst) /* nonzero if we should do substitutions */
483 c = 1; /* c is the current pos */
486 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
488 buffer[strlen(buffer) + 1] = 0;
489 buffer[strlen(buffer)] = ch;
492 if (buffer[0] == '^')
493 do_help_subst(buffer);
495 buffer[strlen(buffer) + 1] = 0;
497 strcpy(buffer, &buffer[1]);
507 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
509 if (((old == 13) || (old == 10)) && (isspace(real))) {
517 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
518 cprintf("\n%s", aaa);
527 if ((strlen(aaa) + c) > (width - 5)) {
537 if ((ch == 13) || (ch == 10)) {
538 cprintf("%s\n", aaa);
545 FMTEND: cprintf("%s\n", aaa);
551 * Callback function for mime parser that simply lists the part
553 void list_this_part(char *name, char *filename, char *partnum, char *disp,
554 void *content, char *cbtype, size_t length)
557 cprintf("part=%s|%s|%s|%s|%s|%d\n",
558 name, filename, partnum, disp, cbtype, length);
563 * Callback function for mime parser that opens a section for downloading
565 void mime_download(char *name, char *filename, char *partnum, char *disp,
566 void *content, char *cbtype, size_t length)
569 /* Silently go away if there's already a download open... */
570 if (CC->download_fp != NULL)
573 /* ...or if this is not the desired section */
574 if (strcasecmp(desired_section, partnum))
577 CC->download_fp = tmpfile();
578 if (CC->download_fp == NULL)
581 fwrite(content, length, 1, CC->download_fp);
582 fflush(CC->download_fp);
583 rewind(CC->download_fp);
585 OpenCmdResult(filename, cbtype);
591 * Load a message from disk into memory.
592 * This is used by CtdlOutputMsg() and other fetch functions.
594 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
595 * using the CtdlMessageFree() function.
597 struct CtdlMessage *CtdlFetchMessage(long msgnum)
599 struct cdbdata *dmsgtext;
600 struct CtdlMessage *ret = NULL;
603 CIT_UBYTE field_header;
606 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
607 if (dmsgtext == NULL) {
610 mptr = dmsgtext->ptr;
612 /* Parse the three bytes that begin EVERY message on disk.
613 * The first is always 0xFF, the on-disk magic number.
614 * The second is the anonymous/public type byte.
615 * The third is the format type byte (vari, fixed, or MIME).
619 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
623 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
624 memset(ret, 0, sizeof(struct CtdlMessage));
626 ret->cm_magic = CTDLMESSAGE_MAGIC;
627 ret->cm_anon_type = *mptr++; /* Anon type byte */
628 ret->cm_format_type = *mptr++; /* Format type byte */
631 * The rest is zero or more arbitrary fields. Load them in.
632 * We're done when we encounter either a zero-length field or
633 * have just processed the 'M' (message text) field.
636 field_length = strlen(mptr);
637 if (field_length == 0)
639 field_header = *mptr++;
640 ret->cm_fields[field_header] = mallok(field_length);
641 strcpy(ret->cm_fields[field_header], mptr);
643 while (*mptr++ != 0); /* advance to next field */
645 } while ((field_length > 0) && (field_header != 'M'));
649 /* Always make sure there's something in the msg text field */
650 if (ret->cm_fields['M'] == NULL)
651 ret->cm_fields['M'] = strdoop("<no text>\n");
653 /* Perform "before read" hooks (aborting if any return nonzero) */
654 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
655 CtdlFreeMessage(ret);
664 * Returns 1 if the supplied pointer points to a valid Citadel message.
665 * If the pointer is NULL or the magic number check fails, returns 0.
667 int is_valid_message(struct CtdlMessage *msg) {
670 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
671 lprintf(3, "is_valid_message() -- self-check failed\n");
679 * 'Destructor' for struct CtdlMessage
681 void CtdlFreeMessage(struct CtdlMessage *msg)
685 if (is_valid_message(msg) == 0) return;
687 for (i = 0; i < 256; ++i)
688 if (msg->cm_fields[i] != NULL) {
689 phree(msg->cm_fields[i]);
692 msg->cm_magic = 0; /* just in case */
698 * Callback function for mime parser that wants to display text
700 void fixed_output(char *name, char *filename, char *partnum, char *disp,
701 void *content, char *cbtype, size_t length)
708 if (!strcasecmp(cbtype, "multipart/alternative")) {
709 strcpy(ma->prefix, partnum);
710 strcat(ma->prefix, ".");
716 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
718 && (ma->did_print == 1) ) {
719 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
725 if ( (!strcasecmp(cbtype, "text/plain"))
726 || (strlen(cbtype)==0) ) {
731 if (ch==10) cprintf("\r\n");
732 else cprintf("%c", ch);
735 else if (!strcasecmp(cbtype, "text/html")) {
736 ptr = html_to_ascii(content, 80, 0);
741 if (ch==10) cprintf("\r\n");
742 else cprintf("%c", ch);
746 else if (strncasecmp(cbtype, "multipart/", 10)) {
747 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
748 partnum, filename, cbtype, length);
754 * Get a message off disk. (returns om_* values found in msgbase.h)
757 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
758 int mode, /* how would you like that message? */
759 int headers_only, /* eschew the message body? */
760 int do_proto, /* do Citadel protocol responses? */
761 int crlf /* Use CRLF newlines instead of LF? */
767 char display_name[256];
768 struct CtdlMessage *TheMessage;
770 char *nl; /* newline string */
772 /* buffers needed for RFC822 translation */
782 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
786 sprintf(mid, "%ld", msg_num);
787 nl = (crlf ? "\r\n" : "\n");
789 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
790 if (do_proto) cprintf("%d Not logged in.\n",
791 ERROR + NOT_LOGGED_IN);
792 return(om_not_logged_in);
795 /* FIXME ... small security issue
796 * We need to check to make sure the requested message is actually
797 * in the current room, and set msg_ok to 1 only if it is. This
798 * functionality is currently missing because I'm in a hurry to replace
799 * broken production code with nonbroken pre-beta code. :( -- ajc
802 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
804 return(om_no_such_msg);
809 * Fetch the message from disk
811 TheMessage = CtdlFetchMessage(msg_num);
812 if (TheMessage == NULL) {
813 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
815 return(om_no_such_msg);
818 /* Are we downloading a MIME component? */
819 if (mode == MT_DOWNLOAD) {
820 if (TheMessage->cm_format_type != FMT_RFC822) {
822 cprintf("%d This is not a MIME message.\n",
824 } else if (CC->download_fp != NULL) {
825 if (do_proto) cprintf(
826 "%d You already have a download open.\n",
829 /* Parse the message text component */
830 mptr = TheMessage->cm_fields['M'];
831 mime_parser(mptr, NULL, *mime_download);
832 /* If there's no file open by this time, the requested
833 * section wasn't found, so print an error
835 if (CC->download_fp == NULL) {
836 if (do_proto) cprintf(
837 "%d Section %s not found.\n",
838 ERROR + FILE_NOT_FOUND,
842 CtdlFreeMessage(TheMessage);
843 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
846 /* now for the user-mode message reading loops */
847 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
849 /* Tell the client which format type we're using. If this is a
850 * MIME message, *lie* about it and tell the user it's fixed-format.
852 if (mode == MT_CITADEL) {
853 if (TheMessage->cm_format_type == FMT_RFC822) {
854 if (do_proto) cprintf("type=1\n");
857 if (do_proto) cprintf("type=%d\n",
858 TheMessage->cm_format_type);
862 /* nhdr=yes means that we're only displaying headers, no body */
863 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
864 if (do_proto) cprintf("nhdr=yes\n");
867 /* begin header processing loop for Citadel message format */
869 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
871 strcpy(display_name, "<unknown>");
872 if (TheMessage->cm_fields['A']) {
873 strcpy(buf, TheMessage->cm_fields['A']);
874 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
875 if (TheMessage->cm_anon_type == MES_ANON)
876 strcpy(display_name, "****");
877 else if (TheMessage->cm_anon_type == MES_AN2)
878 strcpy(display_name, "anonymous");
880 strcpy(display_name, buf);
882 && ((TheMessage->cm_anon_type == MES_ANON)
883 || (TheMessage->cm_anon_type == MES_AN2))) {
884 sprintf(&display_name[strlen(display_name)],
889 strcpy(allkeys, FORDER);
890 for (i=0; i<strlen(allkeys); ++i) {
891 k = (int) allkeys[i];
893 if (TheMessage->cm_fields[k] != NULL) {
895 if (do_proto) cprintf("%s=%s\n",
900 if (do_proto) cprintf("%s=%s\n",
902 TheMessage->cm_fields[k]
911 /* begin header processing loop for RFC822 transfer format */
916 strcpy(snode, NODENAME);
917 strcpy(lnode, HUMANNODE);
918 if (mode == MT_RFC822) {
919 cprintf("X-UIDL: %ld%s", msg_num, nl);
920 for (i = 0; i < 256; ++i) {
921 if (TheMessage->cm_fields[i]) {
922 mptr = TheMessage->cm_fields[i];
929 cprintf("Path: %s%s", mptr, nl);
932 cprintf("Subject: %s%s", mptr, nl);
938 cprintf("X-Citadel-Room: %s%s",
943 cprintf("To: %s%s", mptr, nl);
945 generate_rfc822_datestamp(datestamp,
947 cprintf("Date: %s%s", datestamp, nl);
953 for (i=0; i<strlen(suser); ++i) {
954 suser[i] = tolower(suser[i]);
955 if (!isalnum(suser[i])) suser[i]='_';
958 if (mode == MT_RFC822) {
959 if (!strcasecmp(snode, NODENAME)) {
962 cprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
963 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
965 if (strlen(fuser) > 0) {
966 cprintf("From: %s (%s)%s", fuser, luser, nl);
969 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
972 cprintf("Organization: %s%s", lnode, nl);
975 /* end header processing loop ... at this point, we're in the text */
977 mptr = TheMessage->cm_fields['M'];
979 /* Tell the client about the MIME parts in this message */
980 if (TheMessage->cm_format_type == FMT_RFC822) {
981 if (mode == MT_CITADEL) {
982 mime_parser(mptr, NULL, *list_this_part);
984 else if (mode == MT_MIME) { /* list parts only */
985 mime_parser(mptr, NULL, *list_this_part);
986 if (do_proto) cprintf("000\n");
987 CtdlFreeMessage(TheMessage);
990 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
991 /* FIXME ... we have to put some code in here to avoid
992 * printing duplicate header information when both
993 * Citadel and RFC822 headers exist. Preference should
994 * probably be given to the RFC822 headers.
996 while (ch=*(mptr++), ch!=0) {
998 else if (ch==10) cprintf("%s", nl);
999 else cprintf("%c", ch);
1001 if (do_proto) cprintf("000\n");
1002 CtdlFreeMessage(TheMessage);
1008 if (do_proto) cprintf("000\n");
1009 CtdlFreeMessage(TheMessage);
1013 /* signify start of msg text */
1014 if (mode == MT_CITADEL)
1015 if (do_proto) cprintf("text\n");
1016 if (mode == MT_RFC822) {
1017 if (TheMessage->cm_fields['U'] == NULL) {
1018 cprintf("Subject: (no subject)%s", nl);
1023 /* If the format type on disk is 1 (fixed-format), then we want
1024 * everything to be output completely literally ... regardless of
1025 * what message transfer format is in use.
1027 if (TheMessage->cm_format_type == FMT_FIXED) {
1029 while (ch = *mptr++, ch > 0) {
1032 if ((ch == 10) || (strlen(buf) > 250)) {
1033 cprintf("%s%s", buf, nl);
1036 buf[strlen(buf) + 1] = 0;
1037 buf[strlen(buf)] = ch;
1040 if (strlen(buf) > 0)
1041 cprintf("%s%s", buf, nl);
1044 /* If the message on disk is format 0 (Citadel vari-format), we
1045 * output using the formatter at 80 columns. This is the final output
1046 * form if the transfer format is RFC822, but if the transfer format
1047 * is Citadel proprietary, it'll still work, because the indentation
1048 * for new paragraphs is correct and the client will reformat the
1049 * message to the reader's screen width.
1051 if (TheMessage->cm_format_type == FMT_CITADEL) {
1052 memfmout(80, mptr, 0);
1055 /* If the message on disk is format 4 (MIME), we've gotta hand it
1056 * off to the MIME parser. The client has already been told that
1057 * this message is format 1 (fixed format), so the callback function
1058 * we use will display those parts as-is.
1060 if (TheMessage->cm_format_type == FMT_RFC822) {
1061 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1062 memset(ma, 0, sizeof(struct ma_info));
1063 mime_parser(mptr, NULL, *fixed_output);
1066 /* now we're done */
1067 if (do_proto) cprintf("000\n");
1068 CtdlFreeMessage(TheMessage);
1075 * display a message (mode 0 - Citadel proprietary)
1077 void cmd_msg0(char *cmdbuf)
1080 int headers_only = 0;
1082 msgid = extract_long(cmdbuf, 0);
1083 headers_only = extract_int(cmdbuf, 1);
1085 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1091 * display a message (mode 2 - RFC822)
1093 void cmd_msg2(char *cmdbuf)
1096 int headers_only = 0;
1098 msgid = extract_long(cmdbuf, 0);
1099 headers_only = extract_int(cmdbuf, 1);
1101 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1107 * display a message (mode 3 - IGnet raw format - internal programs only)
1109 void cmd_msg3(char *cmdbuf)
1112 struct CtdlMessage *msg;
1115 if (CC->internal_pgm == 0) {
1116 cprintf("%d This command is for internal programs only.\n",
1121 msgnum = extract_long(cmdbuf, 0);
1122 msg = CtdlFetchMessage(msgnum);
1124 cprintf("%d Message %ld not found.\n",
1129 serialize_message(&smr, msg);
1130 CtdlFreeMessage(msg);
1133 cprintf("%d Unable to serialize message\n",
1134 ERROR+INTERNAL_ERROR);
1138 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1139 client_write(smr.ser, smr.len);
1146 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1148 void cmd_msg4(char *cmdbuf)
1152 msgid = extract_long(cmdbuf, 0);
1153 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1157 * Open a component of a MIME message as a download file
1159 void cmd_opna(char *cmdbuf)
1163 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1165 msgid = extract_long(cmdbuf, 0);
1166 extract(desired_section, cmdbuf, 1);
1168 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1173 * Save a message pointer into a specified room
1174 * (Returns 0 for success, nonzero for failure)
1175 * roomname may be NULL to use the current room
1177 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1179 char hold_rm[ROOMNAMELEN];
1180 struct cdbdata *cdbfr;
1183 long highest_msg = 0L;
1184 struct CtdlMessage *msg = NULL;
1186 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1187 roomname, msgid, flags);
1189 strcpy(hold_rm, CC->quickroom.QRname);
1191 /* We may need to check to see if this message is real */
1192 if ( (flags & SM_VERIFY_GOODNESS)
1193 || (flags & SM_DO_REPL_CHECK)
1195 msg = CtdlFetchMessage(msgid);
1196 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1199 /* Perform replication checks if necessary */
1200 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1202 if (getroom(&CC->quickroom,
1203 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1205 lprintf(9, "No such room <%s>\n", roomname);
1206 if (msg != NULL) CtdlFreeMessage(msg);
1207 return(ERROR + ROOM_NOT_FOUND);
1210 if (ReplicationChecks(msg) != 0) {
1211 getroom(&CC->quickroom, hold_rm);
1212 if (msg != NULL) CtdlFreeMessage(msg);
1213 lprintf(9, "Did replication, and newer exists\n");
1218 /* Now the regular stuff */
1219 if (lgetroom(&CC->quickroom,
1220 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1222 lprintf(9, "No such room <%s>\n", roomname);
1223 if (msg != NULL) CtdlFreeMessage(msg);
1224 return(ERROR + ROOM_NOT_FOUND);
1227 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1228 if (cdbfr == NULL) {
1232 msglist = mallok(cdbfr->len);
1233 if (msglist == NULL)
1234 lprintf(3, "ERROR malloc msglist!\n");
1235 num_msgs = cdbfr->len / sizeof(long);
1236 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1241 /* Make sure the message doesn't already exist in this room. It
1242 * is absolutely taboo to have more than one reference to the same
1243 * message in a room.
1245 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1246 if (msglist[i] == msgid) {
1247 lputroom(&CC->quickroom); /* unlock the room */
1248 getroom(&CC->quickroom, hold_rm);
1249 if (msg != NULL) CtdlFreeMessage(msg);
1250 return(ERROR + ALREADY_EXISTS);
1254 /* Now add the new message */
1256 msglist = reallok(msglist,
1257 (num_msgs * sizeof(long)));
1259 if (msglist == NULL) {
1260 lprintf(3, "ERROR: can't realloc message list!\n");
1262 msglist[num_msgs - 1] = msgid;
1264 /* Sort the message list, so all the msgid's are in order */
1265 num_msgs = sort_msglist(msglist, num_msgs);
1267 /* Determine the highest message number */
1268 highest_msg = msglist[num_msgs - 1];
1270 /* Write it back to disk. */
1271 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1272 msglist, num_msgs * sizeof(long));
1274 /* Free up the memory we used. */
1277 /* Update the highest-message pointer and unlock the room. */
1278 CC->quickroom.QRhighest = highest_msg;
1279 lputroom(&CC->quickroom);
1280 getroom(&CC->quickroom, hold_rm);
1282 /* Bump the reference count for this message. */
1283 if ((flags & SM_DONT_BUMP_REF)==0) {
1284 AdjRefCount(msgid, +1);
1287 /* Return success. */
1288 if (msg != NULL) CtdlFreeMessage(msg);
1295 * Message base operation to send a message to the master file
1296 * (returns new message number)
1298 * This is the back end for CtdlSaveMsg() and should not be directly
1299 * called by server-side modules.
1302 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1303 int generate_id, /* generate 'I' field? */
1304 FILE *save_a_copy) /* save a copy to disk? */
1311 /* Get a new message number */
1312 newmsgid = get_new_message_number();
1313 sprintf(msgidbuf, "%ld", newmsgid);
1316 msg->cm_fields['I'] = strdoop(msgidbuf);
1319 serialize_message(&smr, msg);
1322 cprintf("%d Unable to serialize message\n",
1323 ERROR+INTERNAL_ERROR);
1327 /* Write our little bundle of joy into the message base */
1328 begin_critical_section(S_MSGMAIN);
1329 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1330 smr.ser, smr.len) < 0) {
1331 lprintf(2, "Can't store message\n");
1336 end_critical_section(S_MSGMAIN);
1338 /* If the caller specified that a copy should be saved to a particular
1339 * file handle, do that now too.
1341 if (save_a_copy != NULL) {
1342 fwrite(smr.ser, smr.len, 1, save_a_copy);
1345 /* Free the memory we used for the serialized message */
1348 /* Return the *local* message ID to the caller
1349 * (even if we're storing an incoming network message)
1357 * Serialize a struct CtdlMessage into the format used on disk and network.
1359 * This function loads up a "struct ser_ret" (defined in server.h) which
1360 * contains the length of the serialized message and a pointer to the
1361 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1363 void serialize_message(struct ser_ret *ret, /* return values */
1364 struct CtdlMessage *msg) /* unserialized msg */
1368 static char *forder = FORDER;
1370 if (is_valid_message(msg) == 0) return; /* self check */
1373 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1374 ret->len = ret->len +
1375 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1377 lprintf(9, "calling malloc\n");
1378 ret->ser = mallok(ret->len);
1379 if (ret->ser == NULL) {
1385 ret->ser[1] = msg->cm_anon_type;
1386 ret->ser[2] = msg->cm_format_type;
1389 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1390 ret->ser[wlen++] = (char)forder[i];
1391 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1392 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1394 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1403 * Back end for the ReplicationChecks() function
1405 void check_repl(long msgnum) {
1406 struct CtdlMessage *msg;
1407 time_t timestamp = (-1L);
1409 lprintf(9, "check_repl() found message %ld\n", msgnum);
1410 msg = CtdlFetchMessage(msgnum);
1411 if (msg == NULL) return;
1412 if (msg->cm_fields['T'] != NULL) {
1413 timestamp = atol(msg->cm_fields['T']);
1415 CtdlFreeMessage(msg);
1417 if (timestamp > msg_repl->highest) {
1418 msg_repl->highest = timestamp; /* newer! */
1419 lprintf(9, "newer!\n");
1422 lprintf(9, "older!\n");
1424 /* Existing isn't newer? Then delete the old one(s). */
1425 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1430 * Check to see if any messages already exist which carry the same Extended ID
1434 * -> With older timestamps: delete them and return 0. Message will be saved.
1435 * -> With newer timestamps: return 1. Message save will be aborted.
1437 int ReplicationChecks(struct CtdlMessage *msg) {
1438 struct CtdlMessage *template;
1441 lprintf(9, "ReplicationChecks() started\n");
1442 /* No extended id? Don't do anything. */
1443 if (msg->cm_fields['E'] == NULL) return 0;
1444 if (strlen(msg->cm_fields['E']) == 0) return 0;
1445 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1447 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1448 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1449 msg_repl->highest = atol(msg->cm_fields['T']);
1451 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1452 memset(template, 0, sizeof(struct CtdlMessage));
1453 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1455 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1457 /* If a newer message exists with the same Extended ID, abort
1460 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1464 CtdlFreeMessage(template);
1465 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1473 * Save a message to disk
1475 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1476 char *rec, /* Recipient (mail) */
1477 char *force, /* force a particular room? */
1478 int mailtype, /* local or remote type */
1479 int generate_id) /* 1 = generate 'I' field */
1482 char hold_rm[ROOMNAMELEN];
1483 char actual_rm[ROOMNAMELEN];
1484 char force_room[ROOMNAMELEN];
1485 char content_type[256]; /* We have to learn this */
1486 char recipient[256];
1489 struct usersupp userbuf;
1491 struct SuppMsgInfo smi;
1492 FILE *network_fp = NULL;
1493 static int seqnum = 1;
1494 struct CtdlMessage *imsg;
1497 lprintf(9, "CtdlSaveMsg() called\n");
1498 if (is_valid_message(msg) == 0) return(-1); /* self check */
1500 /* If this message has no timestamp, we take the liberty of
1501 * giving it one, right now.
1503 if (msg->cm_fields['T'] == NULL) {
1504 lprintf(9, "Generating timestamp\n");
1505 sprintf(aaa, "%ld", time(NULL));
1506 msg->cm_fields['T'] = strdoop(aaa);
1509 /* If this message has no path, we generate one.
1511 if (msg->cm_fields['P'] == NULL) {
1512 lprintf(9, "Generating path\n");
1513 if (msg->cm_fields['A'] != NULL) {
1514 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1515 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1516 if (isspace(msg->cm_fields['P'][a])) {
1517 msg->cm_fields['P'][a] = ' ';
1522 msg->cm_fields['P'] = strdoop("unknown");
1526 strcpy(force_room, force);
1528 /* Strip non-printable characters out of the recipient name */
1529 lprintf(9, "Checking recipient (if present)\n");
1530 strcpy(recipient, rec);
1531 for (a = 0; a < strlen(recipient); ++a)
1532 if (!isprint(recipient[a]))
1533 strcpy(&recipient[a], &recipient[a + 1]);
1535 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1536 for (a=0; a<strlen(recipient); ++a) {
1537 if (recipient[a] == '@') {
1538 if (CtdlHostAlias(&recipient[a+1])
1539 == hostalias_localhost) {
1541 lprintf(7, "Changed to <%s>\n", recipient);
1546 lprintf(9, "Recipient is <%s>\n", recipient);
1548 /* Learn about what's inside, because it's what's inside that counts */
1549 lprintf(9, "Learning what's inside\n");
1550 if (msg->cm_fields['M'] == NULL) {
1551 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1554 switch (msg->cm_format_type) {
1556 strcpy(content_type, "text/x-citadel-variformat");
1559 strcpy(content_type, "text/plain");
1562 strcpy(content_type, "text/plain");
1563 /* advance past header fields */
1564 mptr = msg->cm_fields['M'];
1567 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1568 safestrncpy(content_type, mptr,
1569 sizeof(content_type));
1570 strcpy(content_type, &content_type[14]);
1571 for (a = 0; a < strlen(content_type); ++a)
1572 if ((content_type[a] == ';')
1573 || (content_type[a] == ' ')
1574 || (content_type[a] == 13)
1575 || (content_type[a] == 10))
1576 content_type[a] = 0;
1583 /* Goto the correct room */
1584 lprintf(9, "Switching rooms\n");
1585 strcpy(hold_rm, CC->quickroom.QRname);
1586 strcpy(actual_rm, CC->quickroom.QRname);
1588 /* If the user is a twit, move to the twit room for posting */
1589 lprintf(9, "Handling twit stuff\n");
1591 if (CC->usersupp.axlevel == 2) {
1592 strcpy(hold_rm, actual_rm);
1593 strcpy(actual_rm, config.c_twitroom);
1597 /* ...or if this message is destined for Aide> then go there. */
1598 if (strlen(force_room) > 0) {
1599 strcpy(actual_rm, force_room);
1602 lprintf(9, "Possibly relocating\n");
1603 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1604 getroom(&CC->quickroom, actual_rm);
1608 * If this message has no O (room) field, generate one.
1610 if (msg->cm_fields['O'] == NULL) {
1611 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1614 /* Perform "before save" hooks (aborting if any return nonzero) */
1615 lprintf(9, "Performing before-save hooks\n");
1616 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1618 /* If this message has an Extended ID, perform replication checks */
1619 lprintf(9, "Performing replication checks\n");
1620 if (ReplicationChecks(msg) > 0) return(-1);
1622 /* Network mail - send a copy to the network program. */
1623 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1624 lprintf(9, "Sending network spool\n");
1625 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1626 (long) getpid(), CC->cs_pid, ++seqnum);
1627 lprintf(9, "Saving a copy to %s\n", aaa);
1628 network_fp = fopen(aaa, "ab+");
1629 if (network_fp == NULL)
1630 lprintf(2, "ERROR: %s\n", strerror(errno));
1633 /* Save it to disk */
1634 lprintf(9, "Saving to disk\n");
1635 newmsgid = send_message(msg, generate_id, network_fp);
1636 if (network_fp != NULL) {
1638 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1641 if (newmsgid <= 0L) return(-1);
1643 /* Write a supplemental message info record. This doesn't have to
1644 * be a critical section because nobody else knows about this message
1647 lprintf(9, "Creating SuppMsgInfo record\n");
1648 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1649 smi.smi_msgnum = newmsgid;
1650 smi.smi_refcount = 0;
1651 safestrncpy(smi.smi_content_type, content_type, 64);
1652 PutSuppMsgInfo(&smi);
1654 /* Now figure out where to store the pointers */
1655 lprintf(9, "Storing pointers\n");
1657 /* If this is being done by the networker delivering a private
1658 * message, we want to BYPASS saving the sender's copy (because there
1659 * is no local sender; it would otherwise go to the Trashcan).
1661 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1662 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1665 /* For internet mail, drop a copy in the outbound queue room */
1666 if (mailtype == MES_INTERNET) {
1667 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1670 /* Bump this user's messages posted counter. */
1671 lprintf(9, "Updating user\n");
1672 lgetuser(&CC->usersupp, CC->curr_user);
1673 CC->usersupp.posted = CC->usersupp.posted + 1;
1674 lputuser(&CC->usersupp);
1676 /* If this is private, local mail, make a copy in the
1677 * recipient's mailbox and bump the reference count.
1679 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1680 if (getuser(&userbuf, recipient) == 0) {
1681 lprintf(9, "Delivering private mail\n");
1682 MailboxName(actual_rm, &userbuf, MAILROOM);
1683 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1687 /* Perform "after save" hooks */
1688 lprintf(9, "Performing after-save hooks\n");
1689 PerformMessageHooks(msg, EVT_AFTERSAVE);
1692 lprintf(9, "Returning to original room\n");
1693 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1694 getroom(&CC->quickroom, hold_rm);
1696 /* For internet mail, generate delivery instructions
1697 * (Yes, this is recursive! Deal with it!)
1699 if (mailtype == MES_INTERNET) {
1700 lprintf(9, "Generating delivery instructions\n");
1701 instr = mallok(2048);
1703 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1706 SPOOLMIME, newmsgid, time(NULL),
1707 msg->cm_fields['A'], msg->cm_fields['N'],
1710 imsg = mallok(sizeof(struct CtdlMessage));
1711 memset(imsg, 0, sizeof(struct CtdlMessage));
1712 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1713 imsg->cm_anon_type = MES_NORMAL;
1714 imsg->cm_format_type = FMT_RFC822;
1715 imsg->cm_fields['A'] = strdoop("Citadel");
1716 imsg->cm_fields['M'] = instr;
1717 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1718 CtdlFreeMessage(imsg);
1727 * Convenience function for generating small administrative messages.
1729 void quickie_message(char *from, char *to, char *room, char *text)
1731 struct CtdlMessage *msg;
1733 msg = mallok(sizeof(struct CtdlMessage));
1734 memset(msg, 0, sizeof(struct CtdlMessage));
1735 msg->cm_magic = CTDLMESSAGE_MAGIC;
1736 msg->cm_anon_type = MES_NORMAL;
1737 msg->cm_format_type = 0;
1738 msg->cm_fields['A'] = strdoop(from);
1739 msg->cm_fields['O'] = strdoop(room);
1740 msg->cm_fields['N'] = strdoop(NODENAME);
1742 msg->cm_fields['R'] = strdoop(to);
1743 msg->cm_fields['M'] = strdoop(text);
1745 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1746 CtdlFreeMessage(msg);
1747 syslog(LOG_NOTICE, text);
1753 * Back end function used by make_message() and similar functions
1755 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1756 size_t maxlen, /* maximum message length */
1757 char *exist /* if non-null, append to it;
1758 exist is ALWAYS freed */
1761 size_t message_len = 0;
1762 size_t buffer_len = 0;
1766 if (exist == NULL) {
1770 m = reallok(exist, strlen(exist) + 4096);
1771 if (m == NULL) phree(exist);
1774 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1781 /* read in the lines of message text one by one */
1783 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1785 /* augment the buffer if we have to */
1786 if ((message_len + strlen(buf) + 2) > buffer_len) {
1787 lprintf(9, "realloking\n");
1788 ptr = reallok(m, (buffer_len * 2) );
1789 if (ptr == NULL) { /* flush if can't allocate */
1790 while ( (client_gets(buf)>0) &&
1791 strcmp(buf, terminator)) ;;
1794 buffer_len = (buffer_len * 2);
1797 lprintf(9, "buffer_len is %d\n", buffer_len);
1801 if (append == NULL) append = m;
1802 while (strlen(append) > 0) ++append;
1803 strcpy(append, buf);
1804 strcat(append, "\n");
1805 message_len = message_len + strlen(buf) + 1;
1807 /* if we've hit the max msg length, flush the rest */
1808 if (message_len >= maxlen) {
1809 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1820 * Build a binary message to be saved on disk.
1823 struct CtdlMessage *make_message(
1824 struct usersupp *author, /* author's usersupp structure */
1825 char *recipient, /* NULL if it's not mail */
1826 char *room, /* room where it's going */
1827 int type, /* see MES_ types in header file */
1828 int net_type, /* see MES_ types in header file */
1829 int format_type, /* local or remote (see citadel.h) */
1830 char *fake_name) /* who we're masquerading as */
1836 struct CtdlMessage *msg;
1838 msg = mallok(sizeof(struct CtdlMessage));
1839 memset(msg, 0, sizeof(struct CtdlMessage));
1840 msg->cm_magic = CTDLMESSAGE_MAGIC;
1841 msg->cm_anon_type = type;
1842 msg->cm_format_type = format_type;
1844 /* Don't confuse the poor folks if it's not routed mail. */
1845 strcpy(dest_node, "");
1847 /* If net_type is MES_BINARY, split out the destination node. */
1848 if (net_type == MES_BINARY) {
1849 strcpy(dest_node, NODENAME);
1850 for (a = 0; a < strlen(recipient); ++a) {
1851 if (recipient[a] == '@') {
1853 strcpy(dest_node, &recipient[a + 1]);
1858 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1859 if (net_type == MES_INTERNET) {
1860 strcpy(dest_node, "internet");
1863 while (isspace(recipient[strlen(recipient) - 1]))
1864 recipient[strlen(recipient) - 1] = 0;
1866 sprintf(buf, "cit%ld", author->usernum); /* Path */
1867 msg->cm_fields['P'] = strdoop(buf);
1869 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1870 msg->cm_fields['T'] = strdoop(buf);
1872 if (fake_name[0]) /* author */
1873 msg->cm_fields['A'] = strdoop(fake_name);
1875 msg->cm_fields['A'] = strdoop(author->fullname);
1877 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1878 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1880 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1882 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1883 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1885 if (recipient[0] != 0)
1886 msg->cm_fields['R'] = strdoop(recipient);
1887 if (dest_node[0] != 0)
1888 msg->cm_fields['D'] = strdoop(dest_node);
1891 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1892 config.c_maxmsglen, NULL);
1903 * message entry - mode 0 (normal)
1905 void cmd_ent0(char *entargs)
1908 char recipient[256];
1910 int format_type = 0;
1911 char newusername[256];
1912 struct CtdlMessage *msg;
1916 struct usersupp tempUS;
1919 post = extract_int(entargs, 0);
1920 extract(recipient, entargs, 1);
1921 anon_flag = extract_int(entargs, 2);
1922 format_type = extract_int(entargs, 3);
1924 /* first check to make sure the request is valid. */
1926 if (!(CC->logged_in)) {
1927 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1930 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1931 cprintf("%d Need to be validated to enter ",
1932 ERROR + HIGHER_ACCESS_REQUIRED);
1933 cprintf("(except in %s> to sysop)\n", MAILROOM);
1936 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1937 cprintf("%d Need net privileges to enter here.\n",
1938 ERROR + HIGHER_ACCESS_REQUIRED);
1941 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1942 cprintf("%d Sorry, this is a read-only room.\n",
1943 ERROR + HIGHER_ACCESS_REQUIRED);
1950 if (CC->usersupp.axlevel < 6) {
1951 cprintf("%d You don't have permission to masquerade.\n",
1952 ERROR + HIGHER_ACCESS_REQUIRED);
1955 extract(newusername, entargs, 4);
1956 memset(CC->fake_postname, 0, 32);
1957 strcpy(CC->fake_postname, newusername);
1958 cprintf("%d Ok\n", OK);
1961 CC->cs_flags |= CS_POSTING;
1964 if (CC->quickroom.QRflags & QR_MAILBOX) {
1965 if (CC->usersupp.axlevel >= 2) {
1966 strcpy(buf, recipient);
1968 strcpy(buf, "sysop");
1969 e = alias(buf); /* alias and mail type */
1970 if ((buf[0] == 0) || (e == MES_ERROR)) {
1971 cprintf("%d Unknown address - cannot send message.\n",
1972 ERROR + NO_SUCH_USER);
1975 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1976 cprintf("%d Net privileges required for network mail.\n",
1977 ERROR + HIGHER_ACCESS_REQUIRED);
1980 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1981 && ((CC->usersupp.flags & US_INTERNET) == 0)
1982 && (!CC->internal_pgm)) {
1983 cprintf("%d You don't have access to Internet mail.\n",
1984 ERROR + HIGHER_ACCESS_REQUIRED);
1987 if (!strcasecmp(buf, "sysop")) {
1992 goto SKFALL; /* don't search local file */
1993 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1994 cprintf("%d Can't send mail to yourself!\n",
1995 ERROR + NO_SUCH_USER);
1998 /* Check to make sure the user exists; also get the correct
1999 * upper/lower casing of the name.
2001 a = getuser(&tempUS, buf);
2003 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2006 strcpy(buf, tempUS.fullname);
2009 SKFALL: b = MES_NORMAL;
2010 if (CC->quickroom.QRflags & QR_ANONONLY)
2012 if (CC->quickroom.QRflags & QR_ANONOPT) {
2016 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2019 /* If we're only checking the validity of the request, return
2020 * success without creating the message.
2023 cprintf("%d %s\n", OK, buf);
2027 cprintf("%d send message\n", SEND_LISTING);
2029 /* Read in the message from the client. */
2030 if (CC->fake_postname[0])
2031 msg = make_message(&CC->usersupp, buf,
2032 CC->quickroom.QRname, b, e, format_type,
2034 else if (CC->fake_username[0])
2035 msg = make_message(&CC->usersupp, buf,
2036 CC->quickroom.QRname, b, e, format_type,
2039 msg = make_message(&CC->usersupp, buf,
2040 CC->quickroom.QRname, b, e, format_type, "");
2043 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2044 CtdlFreeMessage(msg);
2045 CC->fake_postname[0] = '\0';
2052 * message entry - mode 3 (raw)
2054 void cmd_ent3(char *entargs)
2060 unsigned char ch, which_field;
2061 struct usersupp tempUS;
2063 struct CtdlMessage *msg;
2066 if (CC->internal_pgm == 0) {
2067 cprintf("%d This command is for internal programs only.\n",
2072 /* See if there's a recipient, but make sure it's a real one */
2073 extract(recp, entargs, 1);
2074 for (a = 0; a < strlen(recp); ++a)
2075 if (!isprint(recp[a]))
2076 strcpy(&recp[a], &recp[a + 1]);
2077 while (isspace(recp[0]))
2078 strcpy(recp, &recp[1]);
2079 while (isspace(recp[strlen(recp) - 1]))
2080 recp[strlen(recp) - 1] = 0;
2082 /* If we're in Mail, check the recipient */
2083 if (strlen(recp) > 0) {
2084 e = alias(recp); /* alias and mail type */
2085 if ((recp[0] == 0) || (e == MES_ERROR)) {
2086 cprintf("%d Unknown address - cannot send message.\n",
2087 ERROR + NO_SUCH_USER);
2090 if (e == MES_LOCAL) {
2091 a = getuser(&tempUS, recp);
2093 cprintf("%d No such user.\n",
2094 ERROR + NO_SUCH_USER);
2100 /* At this point, message has been approved. */
2101 if (extract_int(entargs, 0) == 0) {
2102 cprintf("%d OK to send\n", OK);
2106 msglen = extract_long(entargs, 2);
2107 msg = mallok(sizeof(struct CtdlMessage));
2109 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2113 memset(msg, 0, sizeof(struct CtdlMessage));
2114 tempbuf = mallok(msglen);
2115 if (tempbuf == NULL) {
2116 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2121 cprintf("%d %ld\n", SEND_BINARY, msglen);
2123 client_read(&ch, 1); /* 0xFF magic number */
2124 msg->cm_magic = CTDLMESSAGE_MAGIC;
2125 client_read(&ch, 1); /* anon type */
2126 msg->cm_anon_type = ch;
2127 client_read(&ch, 1); /* format type */
2128 msg->cm_format_type = ch;
2129 msglen = msglen - 3;
2131 while (msglen > 0) {
2132 client_read(&which_field, 1);
2133 if (!isalpha(which_field)) valid_msg = 0;
2137 client_read(&ch, 1);
2139 a = strlen(tempbuf);
2142 } while ( (ch != 0) && (msglen > 0) );
2144 msg->cm_fields[which_field] = strdoop(tempbuf);
2147 msg->cm_flags = CM_SKIP_HOOKS;
2148 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2149 CtdlFreeMessage(msg);
2155 * API function to delete messages which match a set of criteria
2156 * (returns the actual number of messages deleted)
2158 int CtdlDeleteMessages(char *room_name, /* which room */
2159 long dmsgnum, /* or "0" for any */
2160 char *content_type /* or NULL for any */
2164 struct quickroom qrbuf;
2165 struct cdbdata *cdbfr;
2166 long *msglist = NULL;
2169 int num_deleted = 0;
2171 struct SuppMsgInfo smi;
2173 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2174 room_name, dmsgnum, content_type);
2176 /* get room record, obtaining a lock... */
2177 if (lgetroom(&qrbuf, room_name) != 0) {
2178 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2180 return (0); /* room not found */
2182 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2184 if (cdbfr != NULL) {
2185 msglist = mallok(cdbfr->len);
2186 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2187 num_msgs = cdbfr->len / sizeof(long);
2191 for (i = 0; i < num_msgs; ++i) {
2194 /* Set/clear a bit for each criterion */
2196 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2197 delete_this |= 0x01;
2199 if (content_type == NULL) {
2200 delete_this |= 0x02;
2202 GetSuppMsgInfo(&smi, msglist[i]);
2203 if (!strcasecmp(smi.smi_content_type,
2205 delete_this |= 0x02;
2209 /* Delete message only if all bits are set */
2210 if (delete_this == 0x03) {
2211 AdjRefCount(msglist[i], -1);
2217 num_msgs = sort_msglist(msglist, num_msgs);
2218 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2219 msglist, (num_msgs * sizeof(long)));
2221 qrbuf.QRhighest = msglist[num_msgs - 1];
2225 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2226 return (num_deleted);
2232 * Delete message from current room
2234 void cmd_dele(char *delstr)
2239 getuser(&CC->usersupp, CC->curr_user);
2240 if ((CC->usersupp.axlevel < 6)
2241 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2242 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2243 && (!(CC->internal_pgm))) {
2244 cprintf("%d Higher access required.\n",
2245 ERROR + HIGHER_ACCESS_REQUIRED);
2248 delnum = extract_long(delstr, 0);
2250 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2253 cprintf("%d %d message%s deleted.\n", OK,
2254 num_deleted, ((num_deleted != 1) ? "s" : ""));
2256 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2262 * move or copy a message to another room
2264 void cmd_move(char *args)
2268 struct quickroom qtemp;
2272 num = extract_long(args, 0);
2273 extract(targ, args, 1);
2274 targ[ROOMNAMELEN - 1] = 0;
2275 is_copy = extract_int(args, 2);
2277 getuser(&CC->usersupp, CC->curr_user);
2278 if ((CC->usersupp.axlevel < 6)
2279 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2280 cprintf("%d Higher access required.\n",
2281 ERROR + HIGHER_ACCESS_REQUIRED);
2285 if (getroom(&qtemp, targ) != 0) {
2286 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2290 err = CtdlSaveMsgPointerInRoom(targ, num,
2291 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2293 cprintf("%d Cannot store message in %s: error %d\n",
2298 /* Now delete the message from the source room,
2299 * if this is a 'move' rather than a 'copy' operation.
2301 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2303 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2309 * GetSuppMsgInfo() - Get the supplementary record for a message
2311 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2314 struct cdbdata *cdbsmi;
2317 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2318 smibuf->smi_msgnum = msgnum;
2319 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2321 /* Use the negative of the message number for its supp record index */
2322 TheIndex = (0L - msgnum);
2324 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2325 if (cdbsmi == NULL) {
2326 return; /* record not found; go with defaults */
2328 memcpy(smibuf, cdbsmi->ptr,
2329 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2330 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2337 * PutSuppMsgInfo() - (re)write supplementary record for a message
2339 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2343 /* Use the negative of the message number for its supp record index */
2344 TheIndex = (0L - smibuf->smi_msgnum);
2346 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2347 smibuf->smi_msgnum, smibuf->smi_refcount);
2349 cdb_store(CDB_MSGMAIN,
2350 &TheIndex, sizeof(long),
2351 smibuf, sizeof(struct SuppMsgInfo));
2356 * AdjRefCount - change the reference count for a message;
2357 * delete the message if it reaches zero
2359 void AdjRefCount(long msgnum, int incr)
2362 struct SuppMsgInfo smi;
2365 /* This is a *tight* critical section; please keep it that way, as
2366 * it may get called while nested in other critical sections.
2367 * Complicating this any further will surely cause deadlock!
2369 begin_critical_section(S_SUPPMSGMAIN);
2370 GetSuppMsgInfo(&smi, msgnum);
2371 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2372 msgnum, smi.smi_refcount);
2373 smi.smi_refcount += incr;
2374 PutSuppMsgInfo(&smi);
2375 end_critical_section(S_SUPPMSGMAIN);
2376 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2377 msgnum, smi.smi_refcount);
2379 /* If the reference count is now zero, delete the message
2380 * (and its supplementary record as well).
2382 if (smi.smi_refcount == 0) {
2383 lprintf(9, "Deleting message <%ld>\n", msgnum);
2385 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2386 delnum = (0L - msgnum);
2387 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2392 * Write a generic object to this room
2394 * Note: this could be much more efficient. Right now we use two temporary
2395 * files, and still pull the message into memory as with all others.
2397 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2398 char *content_type, /* MIME type of this object */
2399 char *tempfilename, /* Where to fetch it from */
2400 struct usersupp *is_mailbox, /* Mailbox room? */
2401 int is_binary, /* Is encoding necessary? */
2402 int is_unique, /* Del others of this type? */
2403 unsigned int flags /* Internal save flags */
2408 char filename[PATH_MAX];
2411 struct quickroom qrbuf;
2412 char roomname[ROOMNAMELEN];
2413 struct CtdlMessage *msg;
2416 if (is_mailbox != NULL)
2417 MailboxName(roomname, is_mailbox, req_room);
2419 safestrncpy(roomname, req_room, sizeof(roomname));
2420 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2422 strcpy(filename, tmpnam(NULL));
2423 fp = fopen(filename, "w");
2427 tempfp = fopen(tempfilename, "r");
2428 if (tempfp == NULL) {
2434 fprintf(fp, "Content-type: %s\n", content_type);
2435 lprintf(9, "Content-type: %s\n", content_type);
2437 if (is_binary == 0) {
2438 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2439 while (ch = getc(tempfp), ch > 0)
2445 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2448 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2449 tempfilename, filename);
2453 lprintf(9, "Allocating\n");
2454 msg = mallok(sizeof(struct CtdlMessage));
2455 memset(msg, 0, sizeof(struct CtdlMessage));
2456 msg->cm_magic = CTDLMESSAGE_MAGIC;
2457 msg->cm_anon_type = MES_NORMAL;
2458 msg->cm_format_type = 4;
2459 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2460 msg->cm_fields['O'] = strdoop(req_room);
2461 msg->cm_fields['N'] = strdoop(config.c_nodename);
2462 msg->cm_fields['H'] = strdoop(config.c_humannode);
2463 msg->cm_flags = flags;
2465 lprintf(9, "Loading\n");
2466 fp = fopen(filename, "rb");
2467 fseek(fp, 0L, SEEK_END);
2470 msg->cm_fields['M'] = mallok(len);
2471 fread(msg->cm_fields['M'], len, 1, fp);
2475 /* Create the requested room if we have to. */
2476 if (getroom(&qrbuf, roomname) != 0) {
2477 create_room(roomname,
2478 ( (is_mailbox != NULL) ? 4 : 3 ),
2481 /* If the caller specified this object as unique, delete all
2482 * other objects of this type that are currently in the room.
2485 lprintf(9, "Deleted %d other msgs of this type\n",
2486 CtdlDeleteMessages(roomname, 0L, content_type));
2488 /* Now write the data */
2489 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2490 CtdlFreeMessage(msg);
2498 void CtdlGetSysConfigBackend(long msgnum) {
2499 config_msgnum = msgnum;
2503 char *CtdlGetSysConfig(char *sysconfname) {
2504 char hold_rm[ROOMNAMELEN];
2507 struct CtdlMessage *msg;
2510 strcpy(hold_rm, CC->quickroom.QRname);
2511 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2512 getroom(&CC->quickroom, hold_rm);
2517 /* We want the last (and probably only) config in this room */
2518 begin_critical_section(S_CONFIG);
2519 config_msgnum = (-1L);
2520 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2521 CtdlGetSysConfigBackend);
2522 msgnum = config_msgnum;
2523 end_critical_section(S_CONFIG);
2529 msg = CtdlFetchMessage(msgnum);
2531 conf = strdoop(msg->cm_fields['M']);
2532 CtdlFreeMessage(msg);
2539 getroom(&CC->quickroom, hold_rm);
2541 lprintf(9, "eggstracting...\n");
2542 if (conf != NULL) do {
2543 extract_token(buf, conf, 0, '\n');
2544 lprintf(9, "eggstracted <%s>\n", buf);
2545 strcpy(conf, &conf[strlen(buf)+1]);
2546 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2551 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2552 char temp[PATH_MAX];
2555 strcpy(temp, tmpnam(NULL));
2557 fp = fopen(temp, "w");
2558 if (fp == NULL) return;
2559 fprintf(fp, "%s", sysconfdata);
2562 /* this handy API function does all the work for us */
2563 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);