19 #include "sysdep_decls.h"
20 #include "citserver.h"
25 #include "dynloader.h"
27 #include "mime_parser.h"
30 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
31 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
32 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
34 extern struct config config;
38 "", "", "", "", "", "", "", "",
39 "", "", "", "", "", "", "", "",
40 "", "", "", "", "", "", "", "",
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
71 * This function is self explanatory.
72 * (What can I say, I'm in a weird mood today...)
74 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
78 for (i = 0; i < strlen(name); ++i)
81 if (isspace(name[i - 1])) {
82 strcpy(&name[i - 1], &name[i]);
85 while (isspace(name[i + 1])) {
86 strcpy(&name[i + 1], &name[i + 2]);
93 * Aliasing for network mail.
94 * (Error messages have been commented out, because this is a server.)
97 { /* process alias and routing info for mail */
100 char aaa[300], bbb[300];
102 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
104 fp = fopen("network/mail.aliases", "r");
106 fp = fopen("/dev/null", "r");
111 while (fgets(aaa, sizeof aaa, fp) != NULL) {
112 while (isspace(name[0]))
113 strcpy(name, &name[1]);
114 aaa[strlen(aaa) - 1] = 0;
116 for (a = 0; a < strlen(aaa); ++a) {
118 strcpy(bbb, &aaa[a + 1]);
122 if (!strcasecmp(name, aaa))
126 lprintf(7, "Mail is being forwarded to %s\n", name);
128 /* determine local or remote type, see citadel.h */
129 for (a = 0; a < strlen(name); ++a)
131 return (MES_INTERNET);
132 for (a = 0; a < strlen(name); ++a)
134 for (b = a; b < strlen(name); ++b)
136 return (MES_INTERNET);
138 for (a = 0; a < strlen(name); ++a)
142 lprintf(7, "Too many @'s in address\n");
146 for (a = 0; a < strlen(name); ++a)
148 strcpy(bbb, &name[a + 1]);
150 strcpy(bbb, &bbb[1]);
151 fp = fopen("network/mail.sysinfo", "r");
155 a = getstring(fp, aaa);
156 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
157 a = getstring(fp, aaa);
158 if (!strncmp(aaa, "use ", 4)) {
159 strcpy(bbb, &aaa[4]);
164 if (!strncmp(aaa, "uum", 3)) {
166 for (a = 0; a < strlen(bbb); ++a) {
172 while (bbb[strlen(bbb) - 1] == '_')
173 bbb[strlen(bbb) - 1] = 0;
174 sprintf(name, &aaa[4], bbb);
175 return (MES_INTERNET);
177 if (!strncmp(aaa, "bin", 3)) {
180 while (aaa[strlen(aaa) - 1] != '@')
181 aaa[strlen(aaa) - 1] = 0;
182 aaa[strlen(aaa) - 1] = 0;
183 while (aaa[strlen(aaa) - 1] == ' ')
184 aaa[strlen(aaa) - 1] = 0;
185 while (bbb[0] != '@')
186 strcpy(bbb, &bbb[1]);
187 strcpy(bbb, &bbb[1]);
188 while (bbb[0] == ' ')
189 strcpy(bbb, &bbb[1]);
190 sprintf(name, "%s @%s", aaa, bbb);
203 fp = fopen("citadel.control", "r");
204 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
210 void simple_listing(long msgnum)
212 cprintf("%ld\n", msgnum);
217 /* Determine if a given message matches the fields in a message template.
218 * Return 0 for a successful match.
220 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
223 /* If there aren't any fields in the template, all messages will
226 if (template == NULL) return(0);
228 /* Null messages are bogus. */
229 if (msg == NULL) return(1);
231 for (i='A'; i<='Z'; ++i) {
232 if (template->cm_fields[i] != NULL) {
233 if (msg->cm_fields[i] == NULL) {
236 if (strcasecmp(msg->cm_fields[i],
237 template->cm_fields[i])) return 1;
241 /* All compares succeeded: we have a match! */
249 * API function to perform an operation for each qualifying message in the
252 void CtdlForEachMessage(int mode, long ref,
254 struct CtdlMessage *compare,
255 void (*CallBack) (long msgnum))
260 struct cdbdata *cdbfr;
261 long *msglist = NULL;
264 struct SuppMsgInfo smi;
265 struct CtdlMessage *msg;
267 /* Learn about the user and room in question */
269 getuser(&CC->usersupp, CC->curr_user);
270 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
272 /* Load the message list */
273 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
275 msglist = mallok(cdbfr->len);
276 memcpy(msglist, cdbfr->ptr, cdbfr->len);
277 num_msgs = cdbfr->len / sizeof(long);
280 return; /* No messages at all? No further action. */
284 /* If the caller is looking for a specific MIME type, then filter
285 * out all messages which are not of the type requested.
288 if (content_type != NULL)
289 if (strlen(content_type) > 0)
290 for (a = 0; a < num_msgs; ++a) {
291 GetSuppMsgInfo(&smi, msglist[a]);
292 if (strcasecmp(smi.smi_content_type, content_type)) {
297 num_msgs = sort_msglist(msglist, num_msgs);
299 /* If a template was supplied, filter out the messages which
300 * don't match. (This could induce some delays!)
303 if (compare != NULL) {
304 for (a = 0; a < num_msgs; ++a) {
305 msg = CtdlFetchMessage(msglist[a]);
307 if (CtdlMsgCmp(msg, compare)) {
310 CtdlFreeMessage(msg);
318 * Now iterate through the message list, according to the
319 * criteria supplied by the caller.
322 for (a = 0; a < num_msgs; ++a) {
323 thismsg = msglist[a];
328 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
329 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
330 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
331 && (CC->usersupp.flags & US_LASTOLD))
332 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
333 || ((mode == MSGS_FIRST) && (a < ref))
334 || ((mode == MSGS_GT) && (thismsg > ref))
340 phree(msglist); /* Clean up */
346 * cmd_msgs() - get list of message #'s in this room
347 * implements the MSGS server command using CtdlForEachMessage()
349 void cmd_msgs(char *cmdbuf)
358 int with_template = 0;
359 struct CtdlMessage *template = NULL;
361 extract(which, cmdbuf, 0);
362 cm_ref = extract_int(cmdbuf, 1);
363 with_template = extract_int(cmdbuf, 2);
367 if (!strncasecmp(which, "OLD", 3))
369 else if (!strncasecmp(which, "NEW", 3))
371 else if (!strncasecmp(which, "FIRST", 5))
373 else if (!strncasecmp(which, "LAST", 4))
375 else if (!strncasecmp(which, "GT", 2))
378 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
379 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
384 cprintf("%d Send template then receive message list\n",
386 template = (struct CtdlMessage *)
387 mallok(sizeof(struct CtdlMessage));
388 memset(template, 0, sizeof(struct CtdlMessage));
389 while(client_gets(buf), strcmp(buf,"000")) {
390 extract(tfield, buf, 0);
391 extract(tvalue, buf, 1);
392 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
393 if (!strcasecmp(tfield, msgkeys[i])) {
394 template->cm_fields[i] =
401 cprintf("%d Message list...\n", LISTING_FOLLOWS);
404 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
405 if (template != NULL) CtdlFreeMessage(template);
413 * help_subst() - support routine for help file viewer
415 void help_subst(char *strbuf, char *source, char *dest)
420 while (p = pattern2(strbuf, source), (p >= 0)) {
421 strcpy(workbuf, &strbuf[p + strlen(source)]);
422 strcpy(&strbuf[p], dest);
423 strcat(strbuf, workbuf);
428 void do_help_subst(char *buffer)
432 help_subst(buffer, "^nodename", config.c_nodename);
433 help_subst(buffer, "^humannode", config.c_humannode);
434 help_subst(buffer, "^fqdn", config.c_fqdn);
435 help_subst(buffer, "^username", CC->usersupp.fullname);
436 sprintf(buf2, "%ld", CC->usersupp.usernum);
437 help_subst(buffer, "^usernum", buf2);
438 help_subst(buffer, "^sysadm", config.c_sysadm);
439 help_subst(buffer, "^variantname", CITADEL);
440 sprintf(buf2, "%d", config.c_maxsessions);
441 help_subst(buffer, "^maxsessions", buf2);
447 * memfmout() - Citadel text formatter and paginator.
448 * Although the original purpose of this routine was to format
449 * text to the reader's screen width, all we're really using it
450 * for here is to format text out to 80 columns before sending it
451 * to the client. The client software may reformat it again.
453 void memfmout(int width, char *mptr, char subst)
454 /* screen width to use */
455 /* where are we going to get our text from? */
456 /* nonzero if we should use hypertext mode */
468 c = 1; /* c is the current pos */
471 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
473 buffer[strlen(buffer) + 1] = 0;
474 buffer[strlen(buffer)] = ch;
477 if (buffer[0] == '^')
478 do_help_subst(buffer);
480 buffer[strlen(buffer) + 1] = 0;
482 strcpy(buffer, &buffer[1]);
492 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
494 if (((old == 13) || (old == 10)) && (isspace(real))) {
502 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
503 cprintf("\n%s", aaa);
512 if ((strlen(aaa) + c) > (width - 5)) {
522 if ((ch == 13) || (ch == 10)) {
523 cprintf("%s\n", aaa);
530 FMTEND: cprintf("%s\n", aaa);
536 * Callback function for mime parser that simply lists the part
538 void list_this_part(char *name, char *filename, char *partnum, char *disp,
539 void *content, char *cbtype, size_t length)
542 cprintf("part=%s|%s|%s|%s|%s|%d\n",
543 name, filename, partnum, disp, cbtype, length);
548 * Callback function for mime parser that wants to display text
550 void fixed_output(char *name, char *filename, char *partnum, char *disp,
551 void *content, char *cbtype, size_t length)
555 if (!strcasecmp(cbtype, "multipart/alternative")) {
556 strcpy(ma->prefix, partnum);
557 strcat(ma->prefix, ".");
563 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
565 && (ma->did_print == 1) ) {
566 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
572 if (!strcasecmp(cbtype, "text/plain")) {
573 client_write(content, length);
575 else if (!strcasecmp(cbtype, "text/html")) {
576 ptr = html_to_ascii(content, 80, 0);
577 client_write(ptr, strlen(ptr));
580 else if (strncasecmp(cbtype, "multipart/", 10)) {
581 cprintf("Part %s: %s (%s) (%d bytes)\n",
582 partnum, filename, cbtype, length);
588 * Callback function for mime parser that opens a section for downloading
590 void mime_download(char *name, char *filename, char *partnum, char *disp,
591 void *content, char *cbtype, size_t length)
594 /* Silently go away if there's already a download open... */
595 if (CC->download_fp != NULL)
598 /* ...or if this is not the desired section */
599 if (strcasecmp(desired_section, partnum))
602 CC->download_fp = tmpfile();
603 if (CC->download_fp == NULL)
606 fwrite(content, length, 1, CC->download_fp);
607 fflush(CC->download_fp);
608 rewind(CC->download_fp);
610 OpenCmdResult(filename, cbtype);
616 * Load a message from disk into memory.
617 * This is used by CtdlOutputMsg() and other fetch functions.
619 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
620 * using the CtdlMessageFree() function.
622 struct CtdlMessage *CtdlFetchMessage(long msgnum)
624 struct cdbdata *dmsgtext;
625 struct CtdlMessage *ret = NULL;
628 CIT_UBYTE field_header;
631 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
632 if (dmsgtext == NULL) {
635 mptr = dmsgtext->ptr;
637 /* Parse the three bytes that begin EVERY message on disk.
638 * The first is always 0xFF, the on-disk magic number.
639 * The second is the anonymous/public type byte.
640 * The third is the format type byte (vari, fixed, or MIME).
644 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
648 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
649 memset(ret, 0, sizeof(struct CtdlMessage));
651 ret->cm_magic = CTDLMESSAGE_MAGIC;
652 ret->cm_anon_type = *mptr++; /* Anon type byte */
653 ret->cm_format_type = *mptr++; /* Format type byte */
656 * The rest is zero or more arbitrary fields. Load them in.
657 * We're done when we encounter either a zero-length field or
658 * have just processed the 'M' (message text) field.
661 field_length = strlen(mptr);
662 if (field_length == 0)
664 field_header = *mptr++;
665 ret->cm_fields[field_header] = mallok(field_length);
666 strcpy(ret->cm_fields[field_header], mptr);
668 while (*mptr++ != 0); /* advance to next field */
670 } while ((field_length > 0) && (field_header != 'M'));
674 /* Always make sure there's something in the msg text field */
675 if (ret->cm_fields['M'] == NULL)
676 ret->cm_fields['M'] = strdoop("<no text>\n");
678 /* Perform "before read" hooks (aborting if any return nonzero) */
679 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
680 CtdlFreeMessage(ret);
689 * Returns 1 if the supplied pointer points to a valid Citadel message.
690 * If the pointer is NULL or the magic number check fails, returns 0.
692 int is_valid_message(struct CtdlMessage *msg) {
695 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
696 lprintf(3, "is_valid_message() -- self-check failed\n");
704 * 'Destructor' for struct CtdlMessage
706 void CtdlFreeMessage(struct CtdlMessage *msg)
710 if (is_valid_message(msg) == 0) return;
712 for (i = 0; i < 256; ++i)
713 if (msg->cm_fields[i] != NULL)
714 phree(msg->cm_fields[i]);
716 msg->cm_magic = 0; /* just in case */
722 * Get a message off disk. (return value is the message's timestamp)
725 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
726 int mode, /* how would you like that message? */
727 int headers_only, /* eschew the message body? */
728 int do_proto /* do Citadel protocol responses? */
735 char display_name[256];
737 struct CtdlMessage *TheMessage = NULL;
741 /* buffers needed for RFC822 translation */
749 sprintf(mid, "%ld", msg_num);
751 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
752 if (do_proto) cprintf("%d Not logged in.\n",
753 ERROR + NOT_LOGGED_IN);
754 return(om_not_logged_in);
757 /* FIX ... small security issue
758 * We need to check to make sure the requested message is actually
759 * in the current room, and set msg_ok to 1 only if it is. This
760 * functionality is currently missing because I'm in a hurry to replace
761 * broken production code with nonbroken pre-beta code. :( -- ajc
764 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
766 return(om_no_such_msg);
771 * Fetch the message from disk
773 TheMessage = CtdlFetchMessage(msg_num);
774 if (TheMessage == NULL) {
775 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
777 return(om_no_such_msg);
780 /* Are we downloading a MIME component? */
781 if (mode == MT_DOWNLOAD) {
782 if (TheMessage->cm_format_type != FMT_RFC822) {
784 cprintf("%d This is not a MIME message.\n",
786 } else if (CC->download_fp != NULL) {
787 if (do_proto) cprintf(
788 "%d You already have a download open.\n",
791 /* Parse the message text component */
792 mptr = TheMessage->cm_fields['M'];
793 mime_parser(mptr, NULL, *mime_download);
794 /* If there's no file open by this time, the requested
795 * section wasn't found, so print an error
797 if (CC->download_fp == NULL) {
798 if (do_proto) cprintf(
799 "%d Section %s not found.\n",
800 ERROR + FILE_NOT_FOUND,
804 CtdlFreeMessage(TheMessage);
805 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
808 /* now for the user-mode message reading loops */
809 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
811 /* Tell the client which format type we're using. If this is a
812 * MIME message, *lie* about it and tell the user it's fixed-format.
814 if (mode == MT_CITADEL) {
815 if (TheMessage->cm_format_type == FMT_RFC822) {
816 if (do_proto) cprintf("type=1\n");
819 if (do_proto) cprintf("type=%d\n",
820 TheMessage->cm_format_type);
824 /* nhdr=yes means that we're only displaying headers, no body */
825 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
826 if (do_proto) cprintf("nhdr=yes\n");
829 /* begin header processing loop for Citadel message format */
831 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
833 strcpy(display_name, "<unknown>");
834 if (TheMessage->cm_fields['A']) {
835 strcpy(buf, TheMessage->cm_fields['A']);
836 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
837 if (TheMessage->cm_anon_type == MES_ANON)
838 strcpy(display_name, "****");
839 else if (TheMessage->cm_anon_type == MES_AN2)
840 strcpy(display_name, "anonymous");
842 strcpy(display_name, buf);
844 && ((TheMessage->cm_anon_type == MES_ANON)
845 || (TheMessage->cm_anon_type == MES_AN2))) {
846 sprintf(&display_name[strlen(display_name)],
851 strcpy(allkeys, FORDER);
852 for (i=0; i<strlen(allkeys); ++i) {
853 k = (int) allkeys[i];
855 if (TheMessage->cm_fields[k] != NULL) {
857 if (do_proto) cprintf("%s=%s\n",
862 if (do_proto) cprintf("%s=%s\n",
864 TheMessage->cm_fields[k]
873 /* begin header processing loop for RFC822 transfer format */
877 strcpy(snode, NODENAME);
878 strcpy(lnode, HUMANNODE);
879 if (mode == MT_RFC822) {
880 for (i = 0; i < 256; ++i) {
881 if (TheMessage->cm_fields[i]) {
882 mptr = TheMessage->cm_fields[i];
886 } else if (i == 'P') {
887 cprintf("Path: %s\n", mptr);
888 for (a = 0; a < strlen(mptr); ++a) {
889 if (mptr[a] == '!') {
890 strcpy(mptr, &mptr[a + 1]);
896 cprintf("Subject: %s\n", mptr);
902 cprintf("X-Citadel-Room: %s\n", mptr);
906 cprintf("To: %s\n", mptr);
909 cprintf("Date: %s", asctime(localtime(&xtime)));
915 if (mode == MT_RFC822) {
916 if (!strcasecmp(snode, NODENAME)) {
919 cprintf("Message-ID: <%s@%s>\n", mid, snode);
920 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
921 cprintf("From: %s@%s (%s)\n", suser, snode, luser);
922 cprintf("Organization: %s\n", lnode);
925 /* end header processing loop ... at this point, we're in the text */
927 mptr = TheMessage->cm_fields['M'];
929 /* Tell the client about the MIME parts in this message */
930 if (TheMessage->cm_format_type == FMT_RFC822) { /* legacy text dump */
931 if (mode == MT_CITADEL) {
932 mime_parser(mptr, NULL, *list_this_part);
934 else if (mode == MT_MIME) { /* list parts only */
935 mime_parser(mptr, NULL, *list_this_part);
936 if (do_proto) cprintf("000\n");
937 CtdlFreeMessage(TheMessage);
943 if (do_proto) cprintf("000\n");
944 CtdlFreeMessage(TheMessage);
948 /* signify start of msg text */
949 if (mode == MT_CITADEL)
950 if (do_proto) cprintf("text\n");
951 if ((mode == MT_RFC822) && (TheMessage->cm_format_type != FMT_RFC822))
954 /* If the format type on disk is 1 (fixed-format), then we want
955 * everything to be output completely literally ... regardless of
956 * what message transfer format is in use.
958 if (TheMessage->cm_format_type == FMT_FIXED) {
960 while (ch = *mptr++, ch > 0) {
963 if ((ch == 10) || (strlen(buf) > 250)) {
964 cprintf("%s\n", buf);
967 buf[strlen(buf) + 1] = 0;
968 buf[strlen(buf)] = ch;
972 cprintf("%s\n", buf);
975 /* If the message on disk is format 0 (Citadel vari-format), we
976 * output using the formatter at 80 columns. This is the final output
977 * form if the transfer format is RFC822, but if the transfer format
978 * is Citadel proprietary, it'll still work, because the indentation
979 * for new paragraphs is correct and the client will reformat the
980 * message to the reader's screen width.
982 if (TheMessage->cm_format_type == FMT_CITADEL) {
983 memfmout(80, mptr, 0);
986 /* If the message on disk is format 4 (MIME), we've gotta hand it
987 * off to the MIME parser. The client has already been told that
988 * this message is format 1 (fixed format), so the callback function
989 * we use will display those parts as-is.
991 if (TheMessage->cm_format_type == FMT_RFC822) {
992 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
993 memset(ma, 0, sizeof(struct ma_info));
994 mime_parser(mptr, NULL, *fixed_output);
998 if (do_proto) cprintf("000\n");
999 CtdlFreeMessage(TheMessage);
1006 * display a message (mode 0 - Citadel proprietary)
1008 void cmd_msg0(char *cmdbuf)
1011 int headers_only = 0;
1013 msgid = extract_long(cmdbuf, 0);
1014 headers_only = extract_int(cmdbuf, 1);
1016 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1);
1022 * display a message (mode 2 - RFC822)
1024 void cmd_msg2(char *cmdbuf)
1027 int headers_only = 0;
1029 msgid = extract_long(cmdbuf, 0);
1030 headers_only = extract_int(cmdbuf, 1);
1032 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1);
1038 * display a message (mode 3 - IGnet raw format - internal programs only)
1040 void cmd_msg3(char *cmdbuf)
1043 struct CtdlMessage *msg;
1046 if (CC->internal_pgm == 0) {
1047 cprintf("%d This command is for internal programs only.\n",
1052 msgnum = extract_long(cmdbuf, 0);
1053 msg = CtdlFetchMessage(msgnum);
1055 cprintf("%d Message %ld not found.\n",
1060 serialize_message(&smr, msg);
1061 CtdlFreeMessage(msg);
1064 cprintf("%d Unable to serialize message\n",
1065 ERROR+INTERNAL_ERROR);
1069 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1070 client_write(smr.ser, smr.len);
1077 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1079 void cmd_msg4(char *cmdbuf)
1083 msgid = extract_long(cmdbuf, 0);
1084 CtdlOutputMsg(msgid, MT_MIME, 0, 1);
1088 * Open a component of a MIME message as a download file
1090 void cmd_opna(char *cmdbuf)
1094 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1096 msgid = extract_long(cmdbuf, 0);
1097 extract(desired_section, cmdbuf, 1);
1099 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1);
1104 * Save a message pointer into a specified room
1105 * (Returns 0 for success, nonzero for failure)
1106 * roomname may be NULL to use the current room
1108 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1110 char hold_rm[ROOMNAMELEN];
1111 struct cdbdata *cdbfr;
1114 long highest_msg = 0L;
1115 struct CtdlMessage *msg = NULL;
1117 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1118 roomname, msgid, flags);
1120 strcpy(hold_rm, CC->quickroom.QRname);
1122 /* We may need to check to see if this message is real */
1123 if ( (flags & SM_VERIFY_GOODNESS)
1124 || (flags & SM_DO_REPL_CHECK)
1126 msg = CtdlFetchMessage(msgid);
1127 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1130 /* Perform replication checks if necessary */
1131 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1133 if (getroom(&CC->quickroom,
1134 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1136 lprintf(9, "No such room <%s>\n", roomname);
1137 if (msg != NULL) CtdlFreeMessage(msg);
1138 return(ERROR + ROOM_NOT_FOUND);
1141 if (ReplicationChecks(msg) != 0) {
1142 getroom(&CC->quickroom, hold_rm);
1143 if (msg != NULL) CtdlFreeMessage(msg);
1144 lprintf(9, "Did replication, and newer exists\n");
1149 /* Now the regular stuff */
1150 if (lgetroom(&CC->quickroom,
1151 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1153 lprintf(9, "No such room <%s>\n", roomname);
1154 if (msg != NULL) CtdlFreeMessage(msg);
1155 return(ERROR + ROOM_NOT_FOUND);
1158 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1159 if (cdbfr == NULL) {
1163 msglist = mallok(cdbfr->len);
1164 if (msglist == NULL)
1165 lprintf(3, "ERROR malloc msglist!\n");
1166 num_msgs = cdbfr->len / sizeof(long);
1167 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1172 /* Make sure the message doesn't already exist in this room. It
1173 * is absolutely taboo to have more than one reference to the same
1174 * message in a room.
1176 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1177 if (msglist[i] == msgid) {
1178 lputroom(&CC->quickroom); /* unlock the room */
1179 getroom(&CC->quickroom, hold_rm);
1180 if (msg != NULL) CtdlFreeMessage(msg);
1181 return(ERROR + ALREADY_EXISTS);
1185 /* Now add the new message */
1187 msglist = reallok(msglist,
1188 (num_msgs * sizeof(long)));
1190 if (msglist == NULL) {
1191 lprintf(3, "ERROR: can't realloc message list!\n");
1193 msglist[num_msgs - 1] = msgid;
1195 /* Sort the message list, so all the msgid's are in order */
1196 num_msgs = sort_msglist(msglist, num_msgs);
1198 /* Determine the highest message number */
1199 highest_msg = msglist[num_msgs - 1];
1201 /* Write it back to disk. */
1202 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1203 msglist, num_msgs * sizeof(long));
1205 /* Free up the memory we used. */
1208 /* Update the highest-message pointer and unlock the room. */
1209 CC->quickroom.QRhighest = highest_msg;
1210 lputroom(&CC->quickroom);
1211 getroom(&CC->quickroom, hold_rm);
1213 /* Bump the reference count for this message. */
1214 AdjRefCount(msgid, +1);
1216 /* Return success. */
1217 if (msg != NULL) CtdlFreeMessage(msg);
1224 * Message base operation to send a message to the master file
1225 * (returns new message number)
1227 * This is the back end for CtdlSaveMsg() and should not be directly
1228 * called by server-side modules.
1231 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1232 int generate_id, /* generate 'I' field? */
1233 FILE *save_a_copy) /* save a copy to disk? */
1240 /* Get a new message number */
1241 newmsgid = get_new_message_number();
1242 sprintf(msgidbuf, "%ld", newmsgid);
1245 msg->cm_fields['I'] = strdoop(msgidbuf);
1248 serialize_message(&smr, msg);
1251 cprintf("%d Unable to serialize message\n",
1252 ERROR+INTERNAL_ERROR);
1256 /* Write our little bundle of joy into the message base */
1257 begin_critical_section(S_MSGMAIN);
1258 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1259 smr.ser, smr.len) < 0) {
1260 lprintf(2, "Can't store message\n");
1265 end_critical_section(S_MSGMAIN);
1267 /* If the caller specified that a copy should be saved to a particular
1268 * file handle, do that now too.
1270 if (save_a_copy != NULL) {
1271 fwrite(smr.ser, smr.len, 1, save_a_copy);
1274 /* Free the memory we used for the serialized message */
1277 /* Return the *local* message ID to the caller
1278 * (even if we're storing an incoming network message)
1286 * Serialize a struct CtdlMessage into the format used on disk and network.
1288 * This function loads up a "struct ser_ret" (defined in server.h) which
1289 * contains the length of the serialized message and a pointer to the
1290 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1292 void serialize_message(struct ser_ret *ret, /* return values */
1293 struct CtdlMessage *msg) /* unserialized msg */
1297 static char *forder = FORDER;
1299 if (is_valid_message(msg) == 0) return; /* self check */
1302 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1303 ret->len = ret->len +
1304 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1306 lprintf(9, "calling malloc\n");
1307 ret->ser = mallok(ret->len);
1308 if (ret->ser == NULL) {
1314 ret->ser[1] = msg->cm_anon_type;
1315 ret->ser[2] = msg->cm_format_type;
1318 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1319 ret->ser[wlen++] = (char)forder[i];
1320 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1321 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1323 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1332 * Back end for the ReplicationChecks() function
1334 void check_repl(long msgnum) {
1335 struct CtdlMessage *msg;
1336 time_t timestamp = (-1L);
1338 lprintf(9, "check_repl() found message %ld\n", msgnum);
1339 msg = CtdlFetchMessage(msgnum);
1340 if (msg == NULL) return;
1341 if (msg->cm_fields['T'] != NULL) {
1342 timestamp = atol(msg->cm_fields['T']);
1344 CtdlFreeMessage(msg);
1346 if (timestamp > msg_repl->highest) {
1347 msg_repl->highest = timestamp; /* newer! */
1348 lprintf(9, "newer!\n");
1351 lprintf(9, "older!\n");
1353 /* Existing isn't newer? Then delete the old one(s). */
1354 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1359 * Check to see if any messages already exist which carry the same Extended ID
1363 * -> With older timestamps: delete them and return 0. Message will be saved.
1364 * -> With newer timestamps: return 1. Message save will be aborted.
1366 int ReplicationChecks(struct CtdlMessage *msg) {
1367 struct CtdlMessage *template;
1370 lprintf(9, "ReplicationChecks() started\n");
1371 /* No extended id? Don't do anything. */
1372 if (msg->cm_fields['E'] == NULL) return 0;
1373 if (strlen(msg->cm_fields['E']) == 0) return 0;
1374 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1376 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1377 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1378 msg_repl->highest = atol(msg->cm_fields['T']);
1380 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1381 memset(template, 0, sizeof(struct CtdlMessage));
1382 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1384 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1386 /* If a newer message exists with the same Extended ID, abort
1389 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1393 CtdlFreeMessage(template);
1394 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1402 * Save a message to disk
1404 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1405 char *rec, /* Recipient (mail) */
1406 char *force, /* force a particular room? */
1407 int mailtype, /* local or remote type */
1408 int generate_id) /* 1 = generate 'I' field */
1411 char hold_rm[ROOMNAMELEN];
1412 char actual_rm[ROOMNAMELEN];
1413 char force_room[ROOMNAMELEN];
1414 char content_type[256]; /* We have to learn this */
1415 char recipient[256];
1418 struct usersupp userbuf;
1420 struct SuppMsgInfo smi;
1421 FILE *network_fp = NULL;
1422 static int seqnum = 1;
1424 lprintf(9, "CtdlSaveMsg() called\n");
1425 if (is_valid_message(msg) == 0) return(-1); /* self check */
1427 /* If this message has no timestamp, we take the liberty of
1428 * giving it one, right now.
1430 if (msg->cm_fields['T'] == NULL) {
1431 lprintf(9, "Generating timestamp\n");
1432 sprintf(aaa, "%ld", time(NULL));
1433 msg->cm_fields['T'] = strdoop(aaa);
1436 /* If this message has no path, we generate one.
1438 if (msg->cm_fields['P'] == NULL) {
1439 lprintf(9, "Generating path\n");
1440 if (msg->cm_fields['A'] != NULL) {
1441 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1442 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1443 if (isspace(msg->cm_fields['P'][a])) {
1444 msg->cm_fields['P'][a] = ' ';
1449 msg->cm_fields['P'] = strdoop("unknown");
1453 strcpy(force_room, force);
1455 /* Strip non-printable characters out of the recipient name */
1456 strcpy(recipient, rec);
1457 for (a = 0; a < strlen(recipient); ++a)
1458 if (!isprint(recipient[a]))
1459 strcpy(&recipient[a], &recipient[a + 1]);
1461 /* Learn about what's inside, because it's what's inside that counts */
1462 lprintf(9, "Learning what's inside\n");
1463 if (msg->cm_fields['M'] == NULL) {
1464 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1467 switch (msg->cm_format_type) {
1469 strcpy(content_type, "text/x-citadel-variformat");
1472 strcpy(content_type, "text/plain");
1475 strcpy(content_type, "text/plain");
1476 /* advance past header fields */
1477 mptr = msg->cm_fields['M'];
1480 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1481 safestrncpy(content_type, mptr,
1482 sizeof(content_type));
1483 strcpy(content_type, &content_type[14]);
1484 for (a = 0; a < strlen(content_type); ++a)
1485 if ((content_type[a] == ';')
1486 || (content_type[a] == ' ')
1487 || (content_type[a] == 13)
1488 || (content_type[a] == 10))
1489 content_type[a] = 0;
1496 /* Goto the correct room */
1497 lprintf(9, "Switching rooms\n");
1498 strcpy(hold_rm, CC->quickroom.QRname);
1499 strcpy(actual_rm, CC->quickroom.QRname);
1501 /* If the user is a twit, move to the twit room for posting */
1502 lprintf(9, "Handling twit stuff\n");
1504 if (CC->usersupp.axlevel == 2) {
1505 strcpy(hold_rm, actual_rm);
1506 strcpy(actual_rm, config.c_twitroom);
1510 /* ...or if this message is destined for Aide> then go there. */
1511 if (strlen(force_room) > 0) {
1512 strcpy(actual_rm, force_room);
1515 lprintf(9, "Possibly relocating\n");
1516 if (strcasecmp(actual_rm, CC->quickroom.QRname))
1517 getroom(&CC->quickroom, actual_rm);
1519 /* Perform "before save" hooks (aborting if any return nonzero) */
1520 lprintf(9, "Performing before-save hooks\n");
1521 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1523 /* If this message has an Extended ID, perform replication checks */
1524 lprintf(9, "Performing replication checks\n");
1525 if (ReplicationChecks(msg) > 0) return(-1);
1527 /* Network mail - send a copy to the network program. */
1528 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1529 lprintf(9, "Sending network spool\n");
1530 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1531 (long) getpid(), CC->cs_pid, ++seqnum);
1532 lprintf(9, "Saving a copy to %s\n", aaa);
1533 network_fp = fopen(aaa, "ab+");
1534 if (network_fp == NULL)
1535 lprintf(2, "ERROR: %s\n", strerror(errno));
1538 /* Save it to disk */
1539 lprintf(9, "Saving to disk\n");
1540 newmsgid = send_message(msg, generate_id, network_fp);
1541 if (network_fp != NULL) {
1543 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1546 if (newmsgid <= 0L) return(-1);
1548 /* Write a supplemental message info record. This doesn't have to
1549 * be a critical section because nobody else knows about this message
1552 lprintf(9, "Creating SuppMsgInfo record\n");
1553 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1554 smi.smi_msgnum = newmsgid;
1555 smi.smi_refcount = 0;
1556 safestrncpy(smi.smi_content_type, content_type, 64);
1557 PutSuppMsgInfo(&smi);
1559 /* Now figure out where to store the pointers */
1560 lprintf(9, "Storing pointers\n");
1563 /* If this is being done by the networker delivering a private
1564 * message, we want to BYPASS saving the sender's copy (because there
1565 * is no local sender; it would otherwise go to the Trashcan).
1567 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1568 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1571 /* For internet mail, drop a copy in the outbound queue room */
1572 /* FIX ... nothing's going to get delivered until we add
1573 some delivery instructions!!! */
1574 if (mailtype == MES_INTERNET) {
1575 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1578 /* Bump this user's messages posted counter. */
1579 lprintf(9, "Updating user\n");
1580 lgetuser(&CC->usersupp, CC->curr_user);
1581 CC->usersupp.posted = CC->usersupp.posted + 1;
1582 lputuser(&CC->usersupp);
1584 /* If this is private, local mail, make a copy in the
1585 * recipient's mailbox and bump the reference count.
1587 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1588 if (getuser(&userbuf, recipient) == 0) {
1589 lprintf(9, "Delivering private mail\n");
1590 MailboxName(actual_rm, &userbuf, MAILROOM);
1591 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1595 /* Perform "after save" hooks */
1596 lprintf(9, "Performing after-save hooks\n");
1597 PerformMessageHooks(msg, EVT_AFTERSAVE);
1600 lprintf(9, "Returning to original room\n");
1601 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1602 getroom(&CC->quickroom, hold_rm);
1610 * Convenience function for generating small administrative messages.
1612 void quickie_message(char *from, char *to, char *room, char *text)
1614 struct CtdlMessage *msg;
1616 msg = mallok(sizeof(struct CtdlMessage));
1617 memset(msg, 0, sizeof(struct CtdlMessage));
1618 msg->cm_magic = CTDLMESSAGE_MAGIC;
1619 msg->cm_anon_type = MES_NORMAL;
1620 msg->cm_format_type = 0;
1621 msg->cm_fields['A'] = strdoop(from);
1622 msg->cm_fields['O'] = strdoop(room);
1623 msg->cm_fields['N'] = strdoop(NODENAME);
1625 msg->cm_fields['R'] = strdoop(to);
1626 msg->cm_fields['M'] = strdoop(text);
1628 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1629 CtdlFreeMessage(msg);
1630 syslog(LOG_NOTICE, text);
1636 * Back end function used by make_message() and similar functions
1638 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1639 size_t maxlen, /* maximum message length */
1640 char *exist /* if non-null, append to it;
1641 exist is ALWAYS freed */
1644 size_t message_len = 0;
1645 size_t buffer_len = 0;
1649 if (exist == NULL) {
1653 m = reallok(exist, strlen(exist) + 4096);
1654 if (m == NULL) phree(exist);
1657 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1664 /* read in the lines of message text one by one */
1666 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1668 /* augment the buffer if we have to */
1669 if ((message_len + strlen(buf) + 2) > buffer_len) {
1670 lprintf(9, "realloking\n");
1671 ptr = reallok(m, (buffer_len * 2) );
1672 if (ptr == NULL) { /* flush if can't allocate */
1673 while ( (client_gets(buf)>0) &&
1674 strcmp(buf, terminator)) ;;
1677 buffer_len = (buffer_len * 2);
1680 lprintf(9, "buffer_len is %d\n", buffer_len);
1684 if (append == NULL) append = m;
1685 while (strlen(append) > 0) ++append;
1686 strcpy(append, buf);
1687 strcat(append, "\n");
1688 message_len = message_len + strlen(buf) + 1;
1690 /* if we've hit the max msg length, flush the rest */
1691 if (message_len >= maxlen) {
1692 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1703 * Build a binary message to be saved on disk.
1706 struct CtdlMessage *make_message(
1707 struct usersupp *author, /* author's usersupp structure */
1708 char *recipient, /* NULL if it's not mail */
1709 char *room, /* room where it's going */
1710 int type, /* see MES_ types in header file */
1711 int net_type, /* see MES_ types in header file */
1712 int format_type, /* local or remote (see citadel.h) */
1713 char *fake_name) /* who we're masquerading as */
1719 struct CtdlMessage *msg;
1721 msg = mallok(sizeof(struct CtdlMessage));
1722 memset(msg, 0, sizeof(struct CtdlMessage));
1723 msg->cm_magic = CTDLMESSAGE_MAGIC;
1724 msg->cm_anon_type = type;
1725 msg->cm_format_type = format_type;
1727 /* Don't confuse the poor folks if it's not routed mail. */
1728 strcpy(dest_node, "");
1730 /* If net_type is MES_BINARY, split out the destination node. */
1731 if (net_type == MES_BINARY) {
1732 strcpy(dest_node, NODENAME);
1733 for (a = 0; a < strlen(recipient); ++a) {
1734 if (recipient[a] == '@') {
1736 strcpy(dest_node, &recipient[a + 1]);
1741 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1742 if (net_type == MES_INTERNET) {
1743 strcpy(dest_node, "internet");
1746 while (isspace(recipient[strlen(recipient) - 1]))
1747 recipient[strlen(recipient) - 1] = 0;
1749 sprintf(buf, "cit%ld", author->usernum); /* Path */
1750 msg->cm_fields['P'] = strdoop(buf);
1752 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1753 msg->cm_fields['T'] = strdoop(buf);
1755 if (fake_name[0]) /* author */
1756 msg->cm_fields['A'] = strdoop(fake_name);
1758 msg->cm_fields['A'] = strdoop(author->fullname);
1760 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1761 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1763 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1765 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1766 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1768 if (recipient[0] != 0)
1769 msg->cm_fields['R'] = strdoop(recipient);
1770 if (dest_node[0] != 0)
1771 msg->cm_fields['D'] = strdoop(dest_node);
1774 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1775 config.c_maxmsglen, NULL);
1786 * message entry - mode 0 (normal)
1788 void cmd_ent0(char *entargs)
1791 char recipient[256];
1793 int format_type = 0;
1794 char newusername[256];
1795 struct CtdlMessage *msg;
1799 struct usersupp tempUS;
1802 post = extract_int(entargs, 0);
1803 extract(recipient, entargs, 1);
1804 anon_flag = extract_int(entargs, 2);
1805 format_type = extract_int(entargs, 3);
1807 /* first check to make sure the request is valid. */
1809 if (!(CC->logged_in)) {
1810 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1813 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1814 cprintf("%d Need to be validated to enter ",
1815 ERROR + HIGHER_ACCESS_REQUIRED);
1816 cprintf("(except in %s> to sysop)\n", MAILROOM);
1819 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1820 cprintf("%d Need net privileges to enter here.\n",
1821 ERROR + HIGHER_ACCESS_REQUIRED);
1824 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1825 cprintf("%d Sorry, this is a read-only room.\n",
1826 ERROR + HIGHER_ACCESS_REQUIRED);
1833 if (CC->usersupp.axlevel < 6) {
1834 cprintf("%d You don't have permission to masquerade.\n",
1835 ERROR + HIGHER_ACCESS_REQUIRED);
1838 extract(newusername, entargs, 4);
1839 memset(CC->fake_postname, 0, 32);
1840 strcpy(CC->fake_postname, newusername);
1841 cprintf("%d Ok\n", OK);
1844 CC->cs_flags |= CS_POSTING;
1847 if (CC->quickroom.QRflags & QR_MAILBOX) {
1848 if (CC->usersupp.axlevel >= 2) {
1849 strcpy(buf, recipient);
1851 strcpy(buf, "sysop");
1852 e = alias(buf); /* alias and mail type */
1853 if ((buf[0] == 0) || (e == MES_ERROR)) {
1854 cprintf("%d Unknown address - cannot send message.\n",
1855 ERROR + NO_SUCH_USER);
1858 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1859 cprintf("%d Net privileges required for network mail.\n",
1860 ERROR + HIGHER_ACCESS_REQUIRED);
1863 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1864 && ((CC->usersupp.flags & US_INTERNET) == 0)
1865 && (!CC->internal_pgm)) {
1866 cprintf("%d You don't have access to Internet mail.\n",
1867 ERROR + HIGHER_ACCESS_REQUIRED);
1870 if (!strcasecmp(buf, "sysop")) {
1875 goto SKFALL; /* don't search local file */
1876 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1877 cprintf("%d Can't send mail to yourself!\n",
1878 ERROR + NO_SUCH_USER);
1881 /* Check to make sure the user exists; also get the correct
1882 * upper/lower casing of the name.
1884 a = getuser(&tempUS, buf);
1886 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1889 strcpy(buf, tempUS.fullname);
1892 SKFALL: b = MES_NORMAL;
1893 if (CC->quickroom.QRflags & QR_ANONONLY)
1895 if (CC->quickroom.QRflags & QR_ANONOPT) {
1899 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1902 /* If we're only checking the validity of the request, return
1903 * success without creating the message.
1906 cprintf("%d %s\n", OK, buf);
1910 cprintf("%d send message\n", SEND_LISTING);
1912 /* Read in the message from the client. */
1913 if (CC->fake_postname[0])
1914 msg = make_message(&CC->usersupp, buf,
1915 CC->quickroom.QRname, b, e, format_type,
1917 else if (CC->fake_username[0])
1918 msg = make_message(&CC->usersupp, buf,
1919 CC->quickroom.QRname, b, e, format_type,
1922 msg = make_message(&CC->usersupp, buf,
1923 CC->quickroom.QRname, b, e, format_type, "");
1926 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1927 CtdlFreeMessage(msg);
1928 CC->fake_postname[0] = '\0';
1935 * message entry - mode 3 (raw)
1937 void cmd_ent3(char *entargs)
1943 unsigned char ch, which_field;
1944 struct usersupp tempUS;
1946 struct CtdlMessage *msg;
1949 if (CC->internal_pgm == 0) {
1950 cprintf("%d This command is for internal programs only.\n",
1955 /* See if there's a recipient, but make sure it's a real one */
1956 extract(recp, entargs, 1);
1957 for (a = 0; a < strlen(recp); ++a)
1958 if (!isprint(recp[a]))
1959 strcpy(&recp[a], &recp[a + 1]);
1960 while (isspace(recp[0]))
1961 strcpy(recp, &recp[1]);
1962 while (isspace(recp[strlen(recp) - 1]))
1963 recp[strlen(recp) - 1] = 0;
1965 /* If we're in Mail, check the recipient */
1966 if (strlen(recp) > 0) {
1967 e = alias(recp); /* alias and mail type */
1968 if ((recp[0] == 0) || (e == MES_ERROR)) {
1969 cprintf("%d Unknown address - cannot send message.\n",
1970 ERROR + NO_SUCH_USER);
1973 if (e == MES_LOCAL) {
1974 a = getuser(&tempUS, recp);
1976 cprintf("%d No such user.\n",
1977 ERROR + NO_SUCH_USER);
1983 /* At this point, message has been approved. */
1984 if (extract_int(entargs, 0) == 0) {
1985 cprintf("%d OK to send\n", OK);
1989 msglen = extract_long(entargs, 2);
1990 msg = mallok(sizeof(struct CtdlMessage));
1992 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
1996 memset(msg, 0, sizeof(struct CtdlMessage));
1997 tempbuf = mallok(msglen);
1998 if (tempbuf == NULL) {
1999 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2004 cprintf("%d %ld\n", SEND_BINARY, msglen);
2006 client_read(&ch, 1); /* 0xFF magic number */
2007 msg->cm_magic = CTDLMESSAGE_MAGIC;
2008 client_read(&ch, 1); /* anon type */
2009 msg->cm_anon_type = ch;
2010 client_read(&ch, 1); /* format type */
2011 msg->cm_format_type = ch;
2012 msglen = msglen - 3;
2014 while (msglen > 0) {
2015 client_read(&which_field, 1);
2016 if (!isalpha(which_field)) valid_msg = 0;
2020 client_read(&ch, 1);
2022 a = strlen(tempbuf);
2025 } while ( (ch != 0) && (msglen > 0) );
2027 msg->cm_fields[which_field] = strdoop(tempbuf);
2030 msg->cm_flags = CM_SKIP_HOOKS;
2031 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2032 CtdlFreeMessage(msg);
2038 * API function to delete messages which match a set of criteria
2039 * (returns the actual number of messages deleted)
2041 int CtdlDeleteMessages(char *room_name, /* which room */
2042 long dmsgnum, /* or "0" for any */
2043 char *content_type /* or NULL for any */
2047 struct quickroom qrbuf;
2048 struct cdbdata *cdbfr;
2049 long *msglist = NULL;
2052 int num_deleted = 0;
2054 struct SuppMsgInfo smi;
2056 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2057 room_name, dmsgnum, content_type);
2059 /* get room record, obtaining a lock... */
2060 if (lgetroom(&qrbuf, room_name) != 0) {
2061 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2063 return (0); /* room not found */
2065 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2067 if (cdbfr != NULL) {
2068 msglist = mallok(cdbfr->len);
2069 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2070 num_msgs = cdbfr->len / sizeof(long);
2074 for (i = 0; i < num_msgs; ++i) {
2077 /* Set/clear a bit for each criterion */
2079 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2080 delete_this |= 0x01;
2082 if (content_type == NULL) {
2083 delete_this |= 0x02;
2085 GetSuppMsgInfo(&smi, msglist[i]);
2086 if (!strcasecmp(smi.smi_content_type,
2088 delete_this |= 0x02;
2092 /* Delete message only if all bits are set */
2093 if (delete_this == 0x03) {
2094 AdjRefCount(msglist[i], -1);
2100 num_msgs = sort_msglist(msglist, num_msgs);
2101 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2102 msglist, (num_msgs * sizeof(long)));
2104 qrbuf.QRhighest = msglist[num_msgs - 1];
2108 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2109 return (num_deleted);
2115 * Delete message from current room
2117 void cmd_dele(char *delstr)
2122 getuser(&CC->usersupp, CC->curr_user);
2123 if ((CC->usersupp.axlevel < 6)
2124 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2125 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2126 && (!(CC->internal_pgm))) {
2127 cprintf("%d Higher access required.\n",
2128 ERROR + HIGHER_ACCESS_REQUIRED);
2131 delnum = extract_long(delstr, 0);
2133 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2136 cprintf("%d %d message%s deleted.\n", OK,
2137 num_deleted, ((num_deleted != 1) ? "s" : ""));
2139 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2145 * move or copy a message to another room
2147 void cmd_move(char *args)
2151 struct quickroom qtemp;
2155 num = extract_long(args, 0);
2156 extract(targ, args, 1);
2157 targ[ROOMNAMELEN - 1] = 0;
2158 is_copy = extract_int(args, 2);
2160 getuser(&CC->usersupp, CC->curr_user);
2161 if ((CC->usersupp.axlevel < 6)
2162 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2163 cprintf("%d Higher access required.\n",
2164 ERROR + HIGHER_ACCESS_REQUIRED);
2168 if (getroom(&qtemp, targ) != 0) {
2169 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2173 err = CtdlSaveMsgPointerInRoom(targ, num,
2174 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2176 cprintf("%d Cannot store message in %s: error %d\n",
2181 /* Now delete the message from the source room,
2182 * if this is a 'move' rather than a 'copy' operation.
2184 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2186 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2192 * GetSuppMsgInfo() - Get the supplementary record for a message
2194 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2197 struct cdbdata *cdbsmi;
2200 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2201 smibuf->smi_msgnum = msgnum;
2202 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2204 /* Use the negative of the message number for its supp record index */
2205 TheIndex = (0L - msgnum);
2207 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2208 if (cdbsmi == NULL) {
2209 return; /* record not found; go with defaults */
2211 memcpy(smibuf, cdbsmi->ptr,
2212 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2213 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2220 * PutSuppMsgInfo() - (re)write supplementary record for a message
2222 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2226 /* Use the negative of the message number for its supp record index */
2227 TheIndex = (0L - smibuf->smi_msgnum);
2229 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2230 smibuf->smi_msgnum, smibuf->smi_refcount);
2232 cdb_store(CDB_MSGMAIN,
2233 &TheIndex, sizeof(long),
2234 smibuf, sizeof(struct SuppMsgInfo));
2239 * AdjRefCount - change the reference count for a message;
2240 * delete the message if it reaches zero
2242 void AdjRefCount(long msgnum, int incr)
2245 struct SuppMsgInfo smi;
2248 /* This is a *tight* critical section; please keep it that way, as
2249 * it may get called while nested in other critical sections.
2250 * Complicating this any further will surely cause deadlock!
2252 begin_critical_section(S_SUPPMSGMAIN);
2253 GetSuppMsgInfo(&smi, msgnum);
2254 smi.smi_refcount += incr;
2255 PutSuppMsgInfo(&smi);
2256 end_critical_section(S_SUPPMSGMAIN);
2258 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2259 msgnum, smi.smi_refcount);
2261 /* If the reference count is now zero, delete the message
2262 * (and its supplementary record as well).
2264 if (smi.smi_refcount == 0) {
2265 lprintf(9, "Deleting message <%ld>\n", msgnum);
2267 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2268 delnum = (0L - msgnum);
2269 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2274 * Write a generic object to this room
2276 * Note: this could be much more efficient. Right now we use two temporary
2277 * files, and still pull the message into memory as with all others.
2279 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2280 char *content_type, /* MIME type of this object */
2281 char *tempfilename, /* Where to fetch it from */
2282 struct usersupp *is_mailbox, /* Mailbox room? */
2283 int is_binary, /* Is encoding necessary? */
2284 int is_unique, /* Del others of this type? */
2285 unsigned int flags /* Internal save flags */
2290 char filename[PATH_MAX];
2293 struct quickroom qrbuf;
2294 char roomname[ROOMNAMELEN];
2295 struct CtdlMessage *msg;
2298 if (is_mailbox != NULL)
2299 MailboxName(roomname, is_mailbox, req_room);
2301 safestrncpy(roomname, req_room, sizeof(roomname));
2302 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2304 strcpy(filename, tmpnam(NULL));
2305 fp = fopen(filename, "w");
2309 tempfp = fopen(tempfilename, "r");
2310 if (tempfp == NULL) {
2316 fprintf(fp, "Content-type: %s\n", content_type);
2317 lprintf(9, "Content-type: %s\n", content_type);
2319 if (is_binary == 0) {
2320 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2321 while (ch = getc(tempfp), ch > 0)
2327 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2330 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2331 tempfilename, filename);
2335 lprintf(9, "Allocating\n");
2336 msg = mallok(sizeof(struct CtdlMessage));
2337 memset(msg, 0, sizeof(struct CtdlMessage));
2338 msg->cm_magic = CTDLMESSAGE_MAGIC;
2339 msg->cm_anon_type = MES_NORMAL;
2340 msg->cm_format_type = 4;
2341 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2342 msg->cm_fields['O'] = strdoop(req_room);
2343 msg->cm_fields['N'] = strdoop(config.c_nodename);
2344 msg->cm_fields['H'] = strdoop(config.c_humannode);
2345 msg->cm_flags = flags;
2347 lprintf(9, "Loading\n");
2348 fp = fopen(filename, "rb");
2349 fseek(fp, 0L, SEEK_END);
2352 msg->cm_fields['M'] = mallok(len);
2353 fread(msg->cm_fields['M'], len, 1, fp);
2357 /* Create the requested room if we have to. */
2358 if (getroom(&qrbuf, roomname) != 0) {
2359 create_room(roomname,
2360 ( (is_mailbox != NULL) ? 4 : 3 ),
2363 /* If the caller specified this object as unique, delete all
2364 * other objects of this type that are currently in the room.
2367 lprintf(9, "Deleted %d other msgs of this type\n",
2368 CtdlDeleteMessages(roomname, 0L, content_type));
2370 /* Now write the data */
2371 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2372 CtdlFreeMessage(msg);
2380 void CtdlGetSysConfigBackend(long msgnum) {
2381 config_msgnum = msgnum;
2385 char *CtdlGetSysConfig(char *sysconfname) {
2386 char hold_rm[ROOMNAMELEN];
2389 struct CtdlMessage *msg;
2392 strcpy(hold_rm, CC->quickroom.QRname);
2393 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2394 getroom(&CC->quickroom, hold_rm);
2399 /* We want the last (and probably only) config in this room */
2400 begin_critical_section(S_CONFIG);
2401 config_msgnum = (-1L);
2402 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2403 CtdlGetSysConfigBackend);
2404 msgnum = config_msgnum;
2405 end_critical_section(S_CONFIG);
2411 msg = CtdlFetchMessage(msgnum);
2413 conf = strdoop(msg->cm_fields['M']);
2414 CtdlFreeMessage(msg);
2421 getroom(&CC->quickroom, hold_rm);
2423 lprintf(9, "eggstracting...\n");
2424 if (conf != NULL) do {
2425 extract_token(buf, conf, 0, '\n');
2426 lprintf(9, "eggstracted <%s>\n", buf);
2427 strcpy(conf, &conf[strlen(buf)+1]);
2428 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2433 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2434 char temp[PATH_MAX];
2437 strcpy(temp, tmpnam(NULL));
2439 fp = fopen(temp, "w");
2440 if (fp == NULL) return;
2441 fprintf(fp, "%s", sysconfdata);
2444 /* this handy API function does all the work for us */
2445 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);