21 #include "sysdep_decls.h"
22 #include "citserver.h"
27 #include "dynloader.h"
29 #include "mime_parser.h"
32 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
33 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
34 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
36 extern struct config config;
40 "", "", "", "", "", "", "", "",
41 "", "", "", "", "", "", "", "",
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
47 "", "", "", "", "", "", "", "",
73 * This function is self explanatory.
74 * (What can I say, I'm in a weird mood today...)
76 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
80 for (i = 0; i < strlen(name); ++i)
83 if (isspace(name[i - 1])) {
84 strcpy(&name[i - 1], &name[i]);
87 while (isspace(name[i + 1])) {
88 strcpy(&name[i + 1], &name[i + 2]);
95 * Aliasing for network mail.
96 * (Error messages have been commented out, because this is a server.)
99 { /* process alias and routing info for mail */
102 char aaa[300], bbb[300];
104 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
106 fp = fopen("network/mail.aliases", "r");
108 fp = fopen("/dev/null", "r");
113 while (fgets(aaa, sizeof aaa, fp) != NULL) {
114 while (isspace(name[0]))
115 strcpy(name, &name[1]);
116 aaa[strlen(aaa) - 1] = 0;
118 for (a = 0; a < strlen(aaa); ++a) {
120 strcpy(bbb, &aaa[a + 1]);
124 if (!strcasecmp(name, aaa))
128 lprintf(7, "Mail is being forwarded to %s\n", name);
130 /* determine local or remote type, see citadel.h */
131 for (a = 0; a < strlen(name); ++a)
133 return (MES_INTERNET);
134 for (a = 0; a < strlen(name); ++a)
136 for (b = a; b < strlen(name); ++b)
138 return (MES_INTERNET);
140 for (a = 0; a < strlen(name); ++a)
144 lprintf(7, "Too many @'s in address\n");
148 for (a = 0; a < strlen(name); ++a)
150 strcpy(bbb, &name[a + 1]);
152 strcpy(bbb, &bbb[1]);
153 fp = fopen("network/mail.sysinfo", "r");
157 a = getstring(fp, aaa);
158 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
159 a = getstring(fp, aaa);
160 if (!strncmp(aaa, "use ", 4)) {
161 strcpy(bbb, &aaa[4]);
166 if (!strncmp(aaa, "uum", 3)) {
168 for (a = 0; a < strlen(bbb); ++a) {
174 while (bbb[strlen(bbb) - 1] == '_')
175 bbb[strlen(bbb) - 1] = 0;
176 sprintf(name, &aaa[4], bbb);
177 return (MES_INTERNET);
179 if (!strncmp(aaa, "bin", 3)) {
182 while (aaa[strlen(aaa) - 1] != '@')
183 aaa[strlen(aaa) - 1] = 0;
184 aaa[strlen(aaa) - 1] = 0;
185 while (aaa[strlen(aaa) - 1] == ' ')
186 aaa[strlen(aaa) - 1] = 0;
187 while (bbb[0] != '@')
188 strcpy(bbb, &bbb[1]);
189 strcpy(bbb, &bbb[1]);
190 while (bbb[0] == ' ')
191 strcpy(bbb, &bbb[1]);
192 sprintf(name, "%s @%s", aaa, bbb);
205 fp = fopen("citadel.control", "r");
206 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
212 void simple_listing(long msgnum)
214 cprintf("%ld\n", msgnum);
219 /* Determine if a given message matches the fields in a message template.
220 * Return 0 for a successful match.
222 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
225 /* If there aren't any fields in the template, all messages will
228 if (template == NULL) return(0);
230 /* Null messages are bogus. */
231 if (msg == NULL) return(1);
233 for (i='A'; i<='Z'; ++i) {
234 if (template->cm_fields[i] != NULL) {
235 if (msg->cm_fields[i] == NULL) {
238 if (strcasecmp(msg->cm_fields[i],
239 template->cm_fields[i])) return 1;
243 /* All compares succeeded: we have a match! */
251 * API function to perform an operation for each qualifying message in the
254 void CtdlForEachMessage(int mode, long ref,
256 struct CtdlMessage *compare,
257 void (*CallBack) (long msgnum))
262 struct cdbdata *cdbfr;
263 long *msglist = NULL;
266 struct SuppMsgInfo smi;
267 struct CtdlMessage *msg;
269 /* Learn about the user and room in question */
271 getuser(&CC->usersupp, CC->curr_user);
272 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
274 /* Load the message list */
275 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
277 msglist = mallok(cdbfr->len);
278 memcpy(msglist, cdbfr->ptr, cdbfr->len);
279 num_msgs = cdbfr->len / sizeof(long);
282 return; /* No messages at all? No further action. */
286 /* If the caller is looking for a specific MIME type, then filter
287 * out all messages which are not of the type requested.
290 if (content_type != NULL)
291 if (strlen(content_type) > 0)
292 for (a = 0; a < num_msgs; ++a) {
293 GetSuppMsgInfo(&smi, msglist[a]);
294 if (strcasecmp(smi.smi_content_type, content_type)) {
299 num_msgs = sort_msglist(msglist, num_msgs);
301 /* If a template was supplied, filter out the messages which
302 * don't match. (This could induce some delays!)
305 if (compare != NULL) {
306 for (a = 0; a < num_msgs; ++a) {
307 msg = CtdlFetchMessage(msglist[a]);
309 if (CtdlMsgCmp(msg, compare)) {
312 CtdlFreeMessage(msg);
320 * Now iterate through the message list, according to the
321 * criteria supplied by the caller.
324 for (a = 0; a < num_msgs; ++a) {
325 thismsg = msglist[a];
330 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
331 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
332 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
333 && (CC->usersupp.flags & US_LASTOLD))
334 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
335 || ((mode == MSGS_FIRST) && (a < ref))
336 || ((mode == MSGS_GT) && (thismsg > ref))
342 phree(msglist); /* Clean up */
348 * cmd_msgs() - get list of message #'s in this room
349 * implements the MSGS server command using CtdlForEachMessage()
351 void cmd_msgs(char *cmdbuf)
360 int with_template = 0;
361 struct CtdlMessage *template = NULL;
363 extract(which, cmdbuf, 0);
364 cm_ref = extract_int(cmdbuf, 1);
365 with_template = extract_int(cmdbuf, 2);
369 if (!strncasecmp(which, "OLD", 3))
371 else if (!strncasecmp(which, "NEW", 3))
373 else if (!strncasecmp(which, "FIRST", 5))
375 else if (!strncasecmp(which, "LAST", 4))
377 else if (!strncasecmp(which, "GT", 2))
380 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
381 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
386 cprintf("%d Send template then receive message list\n",
388 template = (struct CtdlMessage *)
389 mallok(sizeof(struct CtdlMessage));
390 memset(template, 0, sizeof(struct CtdlMessage));
391 while(client_gets(buf), strcmp(buf,"000")) {
392 extract(tfield, buf, 0);
393 extract(tvalue, buf, 1);
394 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
395 if (!strcasecmp(tfield, msgkeys[i])) {
396 template->cm_fields[i] =
403 cprintf("%d Message list...\n", LISTING_FOLLOWS);
406 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
407 if (template != NULL) CtdlFreeMessage(template);
415 * help_subst() - support routine for help file viewer
417 void help_subst(char *strbuf, char *source, char *dest)
422 while (p = pattern2(strbuf, source), (p >= 0)) {
423 strcpy(workbuf, &strbuf[p + strlen(source)]);
424 strcpy(&strbuf[p], dest);
425 strcat(strbuf, workbuf);
430 void do_help_subst(char *buffer)
434 help_subst(buffer, "^nodename", config.c_nodename);
435 help_subst(buffer, "^humannode", config.c_humannode);
436 help_subst(buffer, "^fqdn", config.c_fqdn);
437 help_subst(buffer, "^username", CC->usersupp.fullname);
438 sprintf(buf2, "%ld", CC->usersupp.usernum);
439 help_subst(buffer, "^usernum", buf2);
440 help_subst(buffer, "^sysadm", config.c_sysadm);
441 help_subst(buffer, "^variantname", CITADEL);
442 sprintf(buf2, "%d", config.c_maxsessions);
443 help_subst(buffer, "^maxsessions", buf2);
449 * memfmout() - Citadel text formatter and paginator.
450 * Although the original purpose of this routine was to format
451 * text to the reader's screen width, all we're really using it
452 * for here is to format text out to 80 columns before sending it
453 * to the client. The client software may reformat it again.
456 int width, /* screen width to use */
457 char *mptr, /* where are we going to get our text from? */
458 char subst) /* nonzero if we should do substitutions */
470 c = 1; /* c is the current pos */
473 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
475 buffer[strlen(buffer) + 1] = 0;
476 buffer[strlen(buffer)] = ch;
479 if (buffer[0] == '^')
480 do_help_subst(buffer);
482 buffer[strlen(buffer) + 1] = 0;
484 strcpy(buffer, &buffer[1]);
494 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
496 if (((old == 13) || (old == 10)) && (isspace(real))) {
504 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
505 cprintf("\n%s", aaa);
514 if ((strlen(aaa) + c) > (width - 5)) {
524 if ((ch == 13) || (ch == 10)) {
525 cprintf("%s\n", aaa);
532 FMTEND: cprintf("%s\n", aaa);
538 * Callback function for mime parser that simply lists the part
540 void list_this_part(char *name, char *filename, char *partnum, char *disp,
541 void *content, char *cbtype, size_t length)
544 cprintf("part=%s|%s|%s|%s|%s|%d\n",
545 name, filename, partnum, disp, cbtype, length);
550 * Callback function for mime parser that opens a section for downloading
552 void mime_download(char *name, char *filename, char *partnum, char *disp,
553 void *content, char *cbtype, size_t length)
556 /* Silently go away if there's already a download open... */
557 if (CC->download_fp != NULL)
560 /* ...or if this is not the desired section */
561 if (strcasecmp(desired_section, partnum))
564 CC->download_fp = tmpfile();
565 if (CC->download_fp == NULL)
568 fwrite(content, length, 1, CC->download_fp);
569 fflush(CC->download_fp);
570 rewind(CC->download_fp);
572 OpenCmdResult(filename, cbtype);
578 * Load a message from disk into memory.
579 * This is used by CtdlOutputMsg() and other fetch functions.
581 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
582 * using the CtdlMessageFree() function.
584 struct CtdlMessage *CtdlFetchMessage(long msgnum)
586 struct cdbdata *dmsgtext;
587 struct CtdlMessage *ret = NULL;
590 CIT_UBYTE field_header;
593 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
594 if (dmsgtext == NULL) {
597 mptr = dmsgtext->ptr;
599 /* Parse the three bytes that begin EVERY message on disk.
600 * The first is always 0xFF, the on-disk magic number.
601 * The second is the anonymous/public type byte.
602 * The third is the format type byte (vari, fixed, or MIME).
606 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
610 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
611 memset(ret, 0, sizeof(struct CtdlMessage));
613 ret->cm_magic = CTDLMESSAGE_MAGIC;
614 ret->cm_anon_type = *mptr++; /* Anon type byte */
615 ret->cm_format_type = *mptr++; /* Format type byte */
618 * The rest is zero or more arbitrary fields. Load them in.
619 * We're done when we encounter either a zero-length field or
620 * have just processed the 'M' (message text) field.
623 field_length = strlen(mptr);
624 if (field_length == 0)
626 field_header = *mptr++;
627 ret->cm_fields[field_header] = mallok(field_length);
628 strcpy(ret->cm_fields[field_header], mptr);
630 while (*mptr++ != 0); /* advance to next field */
632 } while ((field_length > 0) && (field_header != 'M'));
636 /* Always make sure there's something in the msg text field */
637 if (ret->cm_fields['M'] == NULL)
638 ret->cm_fields['M'] = strdoop("<no text>\n");
640 /* Perform "before read" hooks (aborting if any return nonzero) */
641 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
642 CtdlFreeMessage(ret);
651 * Returns 1 if the supplied pointer points to a valid Citadel message.
652 * If the pointer is NULL or the magic number check fails, returns 0.
654 int is_valid_message(struct CtdlMessage *msg) {
657 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
658 lprintf(3, "is_valid_message() -- self-check failed\n");
666 * 'Destructor' for struct CtdlMessage
668 void CtdlFreeMessage(struct CtdlMessage *msg)
672 if (is_valid_message(msg) == 0) return;
674 for (i = 0; i < 256; ++i)
675 if (msg->cm_fields[i] != NULL)
676 phree(msg->cm_fields[i]);
678 msg->cm_magic = 0; /* just in case */
684 * Callback function for mime parser that wants to display text
686 void fixed_output(char *name, char *filename, char *partnum, char *disp,
687 void *content, char *cbtype, size_t length)
694 if (!strcasecmp(cbtype, "multipart/alternative")) {
695 strcpy(ma->prefix, partnum);
696 strcat(ma->prefix, ".");
702 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
704 && (ma->did_print == 1) ) {
705 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
711 if ( (!strcasecmp(cbtype, "text/plain"))
712 || (strlen(cbtype)==0) ) {
717 if (ch==10) cprintf("\r\n");
718 else cprintf("%c", ch);
721 else if (!strcasecmp(cbtype, "text/html")) {
722 ptr = html_to_ascii(content, 80, 0);
727 if (ch==10) cprintf("\r\n");
728 else cprintf("%c", ch);
732 else if (strncasecmp(cbtype, "multipart/", 10)) {
733 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
734 partnum, filename, cbtype, length);
740 * Get a message off disk. (returns om_* values found in msgbase.h)
743 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
744 int mode, /* how would you like that message? */
745 int headers_only, /* eschew the message body? */
746 int do_proto, /* do Citadel protocol responses? */
747 int crlf /* Use CRLF newlines instead of LF? */
754 char display_name[256];
755 struct CtdlMessage *TheMessage;
757 char *nl; /* newline string */
759 /* buffers needed for RFC822 translation */
767 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
771 sprintf(mid, "%ld", msg_num);
772 nl = (crlf ? "\r\n" : "\n");
774 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
775 if (do_proto) cprintf("%d Not logged in.\n",
776 ERROR + NOT_LOGGED_IN);
777 return(om_not_logged_in);
780 /* FIX ... small security issue
781 * We need to check to make sure the requested message is actually
782 * in the current room, and set msg_ok to 1 only if it is. This
783 * functionality is currently missing because I'm in a hurry to replace
784 * broken production code with nonbroken pre-beta code. :( -- ajc
787 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
789 return(om_no_such_msg);
794 * Fetch the message from disk
796 TheMessage = CtdlFetchMessage(msg_num);
797 if (TheMessage == NULL) {
798 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
800 return(om_no_such_msg);
803 /* Are we downloading a MIME component? */
804 if (mode == MT_DOWNLOAD) {
805 if (TheMessage->cm_format_type != FMT_RFC822) {
807 cprintf("%d This is not a MIME message.\n",
809 } else if (CC->download_fp != NULL) {
810 if (do_proto) cprintf(
811 "%d You already have a download open.\n",
814 /* Parse the message text component */
815 mptr = TheMessage->cm_fields['M'];
816 mime_parser(mptr, NULL, *mime_download);
817 /* If there's no file open by this time, the requested
818 * section wasn't found, so print an error
820 if (CC->download_fp == NULL) {
821 if (do_proto) cprintf(
822 "%d Section %s not found.\n",
823 ERROR + FILE_NOT_FOUND,
827 CtdlFreeMessage(TheMessage);
828 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
831 /* now for the user-mode message reading loops */
832 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
834 /* Tell the client which format type we're using. If this is a
835 * MIME message, *lie* about it and tell the user it's fixed-format.
837 if (mode == MT_CITADEL) {
838 if (TheMessage->cm_format_type == FMT_RFC822) {
839 if (do_proto) cprintf("type=1\n");
842 if (do_proto) cprintf("type=%d\n",
843 TheMessage->cm_format_type);
847 /* nhdr=yes means that we're only displaying headers, no body */
848 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
849 if (do_proto) cprintf("nhdr=yes\n");
852 /* begin header processing loop for Citadel message format */
854 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
856 strcpy(display_name, "<unknown>");
857 if (TheMessage->cm_fields['A']) {
858 strcpy(buf, TheMessage->cm_fields['A']);
859 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
860 if (TheMessage->cm_anon_type == MES_ANON)
861 strcpy(display_name, "****");
862 else if (TheMessage->cm_anon_type == MES_AN2)
863 strcpy(display_name, "anonymous");
865 strcpy(display_name, buf);
867 && ((TheMessage->cm_anon_type == MES_ANON)
868 || (TheMessage->cm_anon_type == MES_AN2))) {
869 sprintf(&display_name[strlen(display_name)],
874 strcpy(allkeys, FORDER);
875 for (i=0; i<strlen(allkeys); ++i) {
876 k = (int) allkeys[i];
878 if (TheMessage->cm_fields[k] != NULL) {
880 if (do_proto) cprintf("%s=%s\n",
885 if (do_proto) cprintf("%s=%s\n",
887 TheMessage->cm_fields[k]
896 /* begin header processing loop for RFC822 transfer format */
900 strcpy(snode, NODENAME);
901 strcpy(lnode, HUMANNODE);
902 if (mode == MT_RFC822) {
903 cprintf("X-UIDL: %ld%s", msg_num, nl);
904 for (i = 0; i < 256; ++i) {
905 if (TheMessage->cm_fields[i]) {
906 mptr = TheMessage->cm_fields[i];
910 } else if (i == 'P') {
911 cprintf("Path: %s%s", mptr, nl);
912 for (a = 0; a < strlen(mptr); ++a) {
913 if (mptr[a] == '!') {
914 strcpy(mptr, &mptr[a + 1]);
920 cprintf("Subject: %s%s", mptr, nl);
926 cprintf("X-Citadel-Room: %s%s",
931 cprintf("To: %s%s", mptr, nl);
934 cprintf("Date: %s", asctime(localtime(&xtime)));
940 if (mode == MT_RFC822) {
941 if (!strcasecmp(snode, NODENAME)) {
944 cprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
945 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
946 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
947 cprintf("Organization: %s%s", lnode, nl);
950 /* end header processing loop ... at this point, we're in the text */
952 mptr = TheMessage->cm_fields['M'];
954 /* Tell the client about the MIME parts in this message */
955 if (TheMessage->cm_format_type == FMT_RFC822) {
956 if (mode == MT_CITADEL) {
957 mime_parser(mptr, NULL, *list_this_part);
959 else if (mode == MT_MIME) { /* list parts only */
960 mime_parser(mptr, NULL, *list_this_part);
961 if (do_proto) cprintf("000\n");
962 CtdlFreeMessage(TheMessage);
965 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
966 /* FIX ... we have to put some code in here to avoid
967 * printing duplicate header information when both
968 * Citadel and RFC822 headers exist. Preference should
969 * probably be given to the RFC822 headers.
971 while (ch=*(mptr++), ch!=0) {
973 else if (ch==10) cprintf("%s", nl);
974 else cprintf("%c", ch);
976 if (do_proto) cprintf("000\n");
977 CtdlFreeMessage(TheMessage);
983 if (do_proto) cprintf("000\n");
984 CtdlFreeMessage(TheMessage);
988 /* signify start of msg text */
989 if (mode == MT_CITADEL)
990 if (do_proto) cprintf("text\n");
991 if (mode == MT_RFC822) {
995 /* If the format type on disk is 1 (fixed-format), then we want
996 * everything to be output completely literally ... regardless of
997 * what message transfer format is in use.
999 if (TheMessage->cm_format_type == FMT_FIXED) {
1001 while (ch = *mptr++, ch > 0) {
1004 if ((ch == 10) || (strlen(buf) > 250)) {
1005 cprintf("%s%s", buf, nl);
1008 buf[strlen(buf) + 1] = 0;
1009 buf[strlen(buf)] = ch;
1012 if (strlen(buf) > 0)
1013 cprintf("%s%s", buf, nl);
1016 /* If the message on disk is format 0 (Citadel vari-format), we
1017 * output using the formatter at 80 columns. This is the final output
1018 * form if the transfer format is RFC822, but if the transfer format
1019 * is Citadel proprietary, it'll still work, because the indentation
1020 * for new paragraphs is correct and the client will reformat the
1021 * message to the reader's screen width.
1023 if (TheMessage->cm_format_type == FMT_CITADEL) {
1024 memfmout(80, mptr, 0);
1027 /* If the message on disk is format 4 (MIME), we've gotta hand it
1028 * off to the MIME parser. The client has already been told that
1029 * this message is format 1 (fixed format), so the callback function
1030 * we use will display those parts as-is.
1032 if (TheMessage->cm_format_type == FMT_RFC822) {
1033 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1034 memset(ma, 0, sizeof(struct ma_info));
1035 mime_parser(mptr, NULL, *fixed_output);
1038 /* now we're done */
1039 if (do_proto) cprintf("000\n");
1040 CtdlFreeMessage(TheMessage);
1047 * display a message (mode 0 - Citadel proprietary)
1049 void cmd_msg0(char *cmdbuf)
1052 int headers_only = 0;
1054 msgid = extract_long(cmdbuf, 0);
1055 headers_only = extract_int(cmdbuf, 1);
1057 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1063 * display a message (mode 2 - RFC822)
1065 void cmd_msg2(char *cmdbuf)
1068 int headers_only = 0;
1070 msgid = extract_long(cmdbuf, 0);
1071 headers_only = extract_int(cmdbuf, 1);
1073 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1079 * display a message (mode 3 - IGnet raw format - internal programs only)
1081 void cmd_msg3(char *cmdbuf)
1084 struct CtdlMessage *msg;
1087 if (CC->internal_pgm == 0) {
1088 cprintf("%d This command is for internal programs only.\n",
1093 msgnum = extract_long(cmdbuf, 0);
1094 msg = CtdlFetchMessage(msgnum);
1096 cprintf("%d Message %ld not found.\n",
1101 serialize_message(&smr, msg);
1102 CtdlFreeMessage(msg);
1105 cprintf("%d Unable to serialize message\n",
1106 ERROR+INTERNAL_ERROR);
1110 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1111 client_write(smr.ser, smr.len);
1118 * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
1120 void cmd_msg4(char *cmdbuf)
1124 msgid = extract_long(cmdbuf, 0);
1125 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1129 * Open a component of a MIME message as a download file
1131 void cmd_opna(char *cmdbuf)
1135 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1137 msgid = extract_long(cmdbuf, 0);
1138 extract(desired_section, cmdbuf, 1);
1140 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1145 * Save a message pointer into a specified room
1146 * (Returns 0 for success, nonzero for failure)
1147 * roomname may be NULL to use the current room
1149 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1151 char hold_rm[ROOMNAMELEN];
1152 struct cdbdata *cdbfr;
1155 long highest_msg = 0L;
1156 struct CtdlMessage *msg = NULL;
1158 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1159 roomname, msgid, flags);
1161 strcpy(hold_rm, CC->quickroom.QRname);
1163 /* We may need to check to see if this message is real */
1164 if ( (flags & SM_VERIFY_GOODNESS)
1165 || (flags & SM_DO_REPL_CHECK)
1167 msg = CtdlFetchMessage(msgid);
1168 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1171 /* Perform replication checks if necessary */
1172 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1174 if (getroom(&CC->quickroom,
1175 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1177 lprintf(9, "No such room <%s>\n", roomname);
1178 if (msg != NULL) CtdlFreeMessage(msg);
1179 return(ERROR + ROOM_NOT_FOUND);
1182 if (ReplicationChecks(msg) != 0) {
1183 getroom(&CC->quickroom, hold_rm);
1184 if (msg != NULL) CtdlFreeMessage(msg);
1185 lprintf(9, "Did replication, and newer exists\n");
1190 /* Now the regular stuff */
1191 if (lgetroom(&CC->quickroom,
1192 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1194 lprintf(9, "No such room <%s>\n", roomname);
1195 if (msg != NULL) CtdlFreeMessage(msg);
1196 return(ERROR + ROOM_NOT_FOUND);
1199 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1200 if (cdbfr == NULL) {
1204 msglist = mallok(cdbfr->len);
1205 if (msglist == NULL)
1206 lprintf(3, "ERROR malloc msglist!\n");
1207 num_msgs = cdbfr->len / sizeof(long);
1208 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1213 /* Make sure the message doesn't already exist in this room. It
1214 * is absolutely taboo to have more than one reference to the same
1215 * message in a room.
1217 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1218 if (msglist[i] == msgid) {
1219 lputroom(&CC->quickroom); /* unlock the room */
1220 getroom(&CC->quickroom, hold_rm);
1221 if (msg != NULL) CtdlFreeMessage(msg);
1222 return(ERROR + ALREADY_EXISTS);
1226 /* Now add the new message */
1228 msglist = reallok(msglist,
1229 (num_msgs * sizeof(long)));
1231 if (msglist == NULL) {
1232 lprintf(3, "ERROR: can't realloc message list!\n");
1234 msglist[num_msgs - 1] = msgid;
1236 /* Sort the message list, so all the msgid's are in order */
1237 num_msgs = sort_msglist(msglist, num_msgs);
1239 /* Determine the highest message number */
1240 highest_msg = msglist[num_msgs - 1];
1242 /* Write it back to disk. */
1243 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1244 msglist, num_msgs * sizeof(long));
1246 /* Free up the memory we used. */
1249 /* Update the highest-message pointer and unlock the room. */
1250 CC->quickroom.QRhighest = highest_msg;
1251 lputroom(&CC->quickroom);
1252 getroom(&CC->quickroom, hold_rm);
1254 /* Bump the reference count for this message. */
1255 if ((flags & SM_DONT_BUMP_REF)==0) {
1256 AdjRefCount(msgid, +1);
1259 /* Return success. */
1260 if (msg != NULL) CtdlFreeMessage(msg);
1267 * Message base operation to send a message to the master file
1268 * (returns new message number)
1270 * This is the back end for CtdlSaveMsg() and should not be directly
1271 * called by server-side modules.
1274 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1275 int generate_id, /* generate 'I' field? */
1276 FILE *save_a_copy) /* save a copy to disk? */
1283 /* Get a new message number */
1284 newmsgid = get_new_message_number();
1285 sprintf(msgidbuf, "%ld", newmsgid);
1288 msg->cm_fields['I'] = strdoop(msgidbuf);
1291 serialize_message(&smr, msg);
1294 cprintf("%d Unable to serialize message\n",
1295 ERROR+INTERNAL_ERROR);
1299 /* Write our little bundle of joy into the message base */
1300 begin_critical_section(S_MSGMAIN);
1301 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1302 smr.ser, smr.len) < 0) {
1303 lprintf(2, "Can't store message\n");
1308 end_critical_section(S_MSGMAIN);
1310 /* If the caller specified that a copy should be saved to a particular
1311 * file handle, do that now too.
1313 if (save_a_copy != NULL) {
1314 fwrite(smr.ser, smr.len, 1, save_a_copy);
1317 /* Free the memory we used for the serialized message */
1320 /* Return the *local* message ID to the caller
1321 * (even if we're storing an incoming network message)
1329 * Serialize a struct CtdlMessage into the format used on disk and network.
1331 * This function loads up a "struct ser_ret" (defined in server.h) which
1332 * contains the length of the serialized message and a pointer to the
1333 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1335 void serialize_message(struct ser_ret *ret, /* return values */
1336 struct CtdlMessage *msg) /* unserialized msg */
1340 static char *forder = FORDER;
1342 if (is_valid_message(msg) == 0) return; /* self check */
1345 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1346 ret->len = ret->len +
1347 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1349 lprintf(9, "calling malloc\n");
1350 ret->ser = mallok(ret->len);
1351 if (ret->ser == NULL) {
1357 ret->ser[1] = msg->cm_anon_type;
1358 ret->ser[2] = msg->cm_format_type;
1361 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1362 ret->ser[wlen++] = (char)forder[i];
1363 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1364 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1366 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1375 * Back end for the ReplicationChecks() function
1377 void check_repl(long msgnum) {
1378 struct CtdlMessage *msg;
1379 time_t timestamp = (-1L);
1381 lprintf(9, "check_repl() found message %ld\n", msgnum);
1382 msg = CtdlFetchMessage(msgnum);
1383 if (msg == NULL) return;
1384 if (msg->cm_fields['T'] != NULL) {
1385 timestamp = atol(msg->cm_fields['T']);
1387 CtdlFreeMessage(msg);
1389 if (timestamp > msg_repl->highest) {
1390 msg_repl->highest = timestamp; /* newer! */
1391 lprintf(9, "newer!\n");
1394 lprintf(9, "older!\n");
1396 /* Existing isn't newer? Then delete the old one(s). */
1397 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1402 * Check to see if any messages already exist which carry the same Extended ID
1406 * -> With older timestamps: delete them and return 0. Message will be saved.
1407 * -> With newer timestamps: return 1. Message save will be aborted.
1409 int ReplicationChecks(struct CtdlMessage *msg) {
1410 struct CtdlMessage *template;
1413 lprintf(9, "ReplicationChecks() started\n");
1414 /* No extended id? Don't do anything. */
1415 if (msg->cm_fields['E'] == NULL) return 0;
1416 if (strlen(msg->cm_fields['E']) == 0) return 0;
1417 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1419 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1420 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1421 msg_repl->highest = atol(msg->cm_fields['T']);
1423 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1424 memset(template, 0, sizeof(struct CtdlMessage));
1425 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1427 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1429 /* If a newer message exists with the same Extended ID, abort
1432 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1436 CtdlFreeMessage(template);
1437 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1445 * Save a message to disk
1447 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1448 char *rec, /* Recipient (mail) */
1449 char *force, /* force a particular room? */
1450 int mailtype, /* local or remote type */
1451 int generate_id) /* 1 = generate 'I' field */
1454 char hold_rm[ROOMNAMELEN];
1455 char actual_rm[ROOMNAMELEN];
1456 char force_room[ROOMNAMELEN];
1457 char content_type[256]; /* We have to learn this */
1458 char recipient[256];
1461 struct usersupp userbuf;
1463 struct SuppMsgInfo smi;
1464 FILE *network_fp = NULL;
1465 static int seqnum = 1;
1466 struct CtdlMessage *imsg;
1469 lprintf(9, "CtdlSaveMsg() called\n");
1470 if (is_valid_message(msg) == 0) return(-1); /* self check */
1472 /* If this message has no timestamp, we take the liberty of
1473 * giving it one, right now.
1475 if (msg->cm_fields['T'] == NULL) {
1476 lprintf(9, "Generating timestamp\n");
1477 sprintf(aaa, "%ld", time(NULL));
1478 msg->cm_fields['T'] = strdoop(aaa);
1481 /* If this message has no path, we generate one.
1483 if (msg->cm_fields['P'] == NULL) {
1484 lprintf(9, "Generating path\n");
1485 if (msg->cm_fields['A'] != NULL) {
1486 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1487 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1488 if (isspace(msg->cm_fields['P'][a])) {
1489 msg->cm_fields['P'][a] = ' ';
1494 msg->cm_fields['P'] = strdoop("unknown");
1498 strcpy(force_room, force);
1500 /* Strip non-printable characters out of the recipient name */
1501 strcpy(recipient, rec);
1502 for (a = 0; a < strlen(recipient); ++a)
1503 if (!isprint(recipient[a]))
1504 strcpy(&recipient[a], &recipient[a + 1]);
1506 /* Learn about what's inside, because it's what's inside that counts */
1507 lprintf(9, "Learning what's inside\n");
1508 if (msg->cm_fields['M'] == NULL) {
1509 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1512 switch (msg->cm_format_type) {
1514 strcpy(content_type, "text/x-citadel-variformat");
1517 strcpy(content_type, "text/plain");
1520 strcpy(content_type, "text/plain");
1521 /* advance past header fields */
1522 mptr = msg->cm_fields['M'];
1525 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1526 safestrncpy(content_type, mptr,
1527 sizeof(content_type));
1528 strcpy(content_type, &content_type[14]);
1529 for (a = 0; a < strlen(content_type); ++a)
1530 if ((content_type[a] == ';')
1531 || (content_type[a] == ' ')
1532 || (content_type[a] == 13)
1533 || (content_type[a] == 10))
1534 content_type[a] = 0;
1541 /* Goto the correct room */
1542 lprintf(9, "Switching rooms\n");
1543 strcpy(hold_rm, CC->quickroom.QRname);
1544 strcpy(actual_rm, CC->quickroom.QRname);
1546 /* If the user is a twit, move to the twit room for posting */
1547 lprintf(9, "Handling twit stuff\n");
1549 if (CC->usersupp.axlevel == 2) {
1550 strcpy(hold_rm, actual_rm);
1551 strcpy(actual_rm, config.c_twitroom);
1555 /* ...or if this message is destined for Aide> then go there. */
1556 if (strlen(force_room) > 0) {
1557 strcpy(actual_rm, force_room);
1560 lprintf(9, "Possibly relocating\n");
1561 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1562 getroom(&CC->quickroom, actual_rm);
1566 * If this message has no O (room) field, generate one.
1568 if (msg->cm_fields['O'] == NULL) {
1569 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1572 /* Perform "before save" hooks (aborting if any return nonzero) */
1573 lprintf(9, "Performing before-save hooks\n");
1574 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1576 /* If this message has an Extended ID, perform replication checks */
1577 lprintf(9, "Performing replication checks\n");
1578 if (ReplicationChecks(msg) > 0) return(-1);
1580 /* Network mail - send a copy to the network program. */
1581 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1582 lprintf(9, "Sending network spool\n");
1583 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1584 (long) getpid(), CC->cs_pid, ++seqnum);
1585 lprintf(9, "Saving a copy to %s\n", aaa);
1586 network_fp = fopen(aaa, "ab+");
1587 if (network_fp == NULL)
1588 lprintf(2, "ERROR: %s\n", strerror(errno));
1591 /* Save it to disk */
1592 lprintf(9, "Saving to disk\n");
1593 newmsgid = send_message(msg, generate_id, network_fp);
1594 if (network_fp != NULL) {
1596 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1599 if (newmsgid <= 0L) return(-1);
1601 /* Write a supplemental message info record. This doesn't have to
1602 * be a critical section because nobody else knows about this message
1605 lprintf(9, "Creating SuppMsgInfo record\n");
1606 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1607 smi.smi_msgnum = newmsgid;
1608 smi.smi_refcount = 0;
1609 safestrncpy(smi.smi_content_type, content_type, 64);
1610 PutSuppMsgInfo(&smi);
1612 /* Now figure out where to store the pointers */
1613 lprintf(9, "Storing pointers\n");
1615 /* If this is being done by the networker delivering a private
1616 * message, we want to BYPASS saving the sender's copy (because there
1617 * is no local sender; it would otherwise go to the Trashcan).
1619 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1620 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1623 /* For internet mail, drop a copy in the outbound queue room */
1624 if (mailtype == MES_INTERNET) {
1625 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1628 /* Bump this user's messages posted counter. */
1629 lprintf(9, "Updating user\n");
1630 lgetuser(&CC->usersupp, CC->curr_user);
1631 CC->usersupp.posted = CC->usersupp.posted + 1;
1632 lputuser(&CC->usersupp);
1634 /* If this is private, local mail, make a copy in the
1635 * recipient's mailbox and bump the reference count.
1637 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1638 if (getuser(&userbuf, recipient) == 0) {
1639 lprintf(9, "Delivering private mail\n");
1640 MailboxName(actual_rm, &userbuf, MAILROOM);
1641 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1645 /* Perform "after save" hooks */
1646 lprintf(9, "Performing after-save hooks\n");
1647 PerformMessageHooks(msg, EVT_AFTERSAVE);
1650 lprintf(9, "Returning to original room\n");
1651 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1652 getroom(&CC->quickroom, hold_rm);
1654 /* For internet mail, generate delivery instructions
1655 * (Yes, this is recursive! Deal with it!)
1657 if (mailtype == MES_INTERNET) {
1658 lprintf(9, "Generating delivery instructions\n");
1659 instr = mallok(2048);
1661 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1663 SPOOLMIME, newmsgid, time(NULL), recipient );
1665 imsg = mallok(sizeof(struct CtdlMessage));
1666 memset(imsg, 0, sizeof(struct CtdlMessage));
1667 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1668 imsg->cm_anon_type = MES_NORMAL;
1669 imsg->cm_format_type = FMT_RFC822;
1670 imsg->cm_fields['A'] = strdoop("Citadel");
1671 imsg->cm_fields['M'] = instr;
1672 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1673 CtdlFreeMessage(imsg);
1682 * Convenience function for generating small administrative messages.
1684 void quickie_message(char *from, char *to, char *room, char *text)
1686 struct CtdlMessage *msg;
1688 msg = mallok(sizeof(struct CtdlMessage));
1689 memset(msg, 0, sizeof(struct CtdlMessage));
1690 msg->cm_magic = CTDLMESSAGE_MAGIC;
1691 msg->cm_anon_type = MES_NORMAL;
1692 msg->cm_format_type = 0;
1693 msg->cm_fields['A'] = strdoop(from);
1694 msg->cm_fields['O'] = strdoop(room);
1695 msg->cm_fields['N'] = strdoop(NODENAME);
1697 msg->cm_fields['R'] = strdoop(to);
1698 msg->cm_fields['M'] = strdoop(text);
1700 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1701 CtdlFreeMessage(msg);
1702 syslog(LOG_NOTICE, text);
1708 * Back end function used by make_message() and similar functions
1710 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1711 size_t maxlen, /* maximum message length */
1712 char *exist /* if non-null, append to it;
1713 exist is ALWAYS freed */
1716 size_t message_len = 0;
1717 size_t buffer_len = 0;
1721 if (exist == NULL) {
1725 m = reallok(exist, strlen(exist) + 4096);
1726 if (m == NULL) phree(exist);
1729 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1736 /* read in the lines of message text one by one */
1738 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1740 /* augment the buffer if we have to */
1741 if ((message_len + strlen(buf) + 2) > buffer_len) {
1742 lprintf(9, "realloking\n");
1743 ptr = reallok(m, (buffer_len * 2) );
1744 if (ptr == NULL) { /* flush if can't allocate */
1745 while ( (client_gets(buf)>0) &&
1746 strcmp(buf, terminator)) ;;
1749 buffer_len = (buffer_len * 2);
1752 lprintf(9, "buffer_len is %d\n", buffer_len);
1756 if (append == NULL) append = m;
1757 while (strlen(append) > 0) ++append;
1758 strcpy(append, buf);
1759 strcat(append, "\n");
1760 message_len = message_len + strlen(buf) + 1;
1762 /* if we've hit the max msg length, flush the rest */
1763 if (message_len >= maxlen) {
1764 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1775 * Build a binary message to be saved on disk.
1778 struct CtdlMessage *make_message(
1779 struct usersupp *author, /* author's usersupp structure */
1780 char *recipient, /* NULL if it's not mail */
1781 char *room, /* room where it's going */
1782 int type, /* see MES_ types in header file */
1783 int net_type, /* see MES_ types in header file */
1784 int format_type, /* local or remote (see citadel.h) */
1785 char *fake_name) /* who we're masquerading as */
1791 struct CtdlMessage *msg;
1793 msg = mallok(sizeof(struct CtdlMessage));
1794 memset(msg, 0, sizeof(struct CtdlMessage));
1795 msg->cm_magic = CTDLMESSAGE_MAGIC;
1796 msg->cm_anon_type = type;
1797 msg->cm_format_type = format_type;
1799 /* Don't confuse the poor folks if it's not routed mail. */
1800 strcpy(dest_node, "");
1802 /* If net_type is MES_BINARY, split out the destination node. */
1803 if (net_type == MES_BINARY) {
1804 strcpy(dest_node, NODENAME);
1805 for (a = 0; a < strlen(recipient); ++a) {
1806 if (recipient[a] == '@') {
1808 strcpy(dest_node, &recipient[a + 1]);
1813 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1814 if (net_type == MES_INTERNET) {
1815 strcpy(dest_node, "internet");
1818 while (isspace(recipient[strlen(recipient) - 1]))
1819 recipient[strlen(recipient) - 1] = 0;
1821 sprintf(buf, "cit%ld", author->usernum); /* Path */
1822 msg->cm_fields['P'] = strdoop(buf);
1824 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1825 msg->cm_fields['T'] = strdoop(buf);
1827 if (fake_name[0]) /* author */
1828 msg->cm_fields['A'] = strdoop(fake_name);
1830 msg->cm_fields['A'] = strdoop(author->fullname);
1832 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1833 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1835 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1837 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1838 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1840 if (recipient[0] != 0)
1841 msg->cm_fields['R'] = strdoop(recipient);
1842 if (dest_node[0] != 0)
1843 msg->cm_fields['D'] = strdoop(dest_node);
1846 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1847 config.c_maxmsglen, NULL);
1858 * message entry - mode 0 (normal)
1860 void cmd_ent0(char *entargs)
1863 char recipient[256];
1865 int format_type = 0;
1866 char newusername[256];
1867 struct CtdlMessage *msg;
1871 struct usersupp tempUS;
1874 post = extract_int(entargs, 0);
1875 extract(recipient, entargs, 1);
1876 anon_flag = extract_int(entargs, 2);
1877 format_type = extract_int(entargs, 3);
1879 /* first check to make sure the request is valid. */
1881 if (!(CC->logged_in)) {
1882 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1885 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1886 cprintf("%d Need to be validated to enter ",
1887 ERROR + HIGHER_ACCESS_REQUIRED);
1888 cprintf("(except in %s> to sysop)\n", MAILROOM);
1891 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1892 cprintf("%d Need net privileges to enter here.\n",
1893 ERROR + HIGHER_ACCESS_REQUIRED);
1896 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1897 cprintf("%d Sorry, this is a read-only room.\n",
1898 ERROR + HIGHER_ACCESS_REQUIRED);
1905 if (CC->usersupp.axlevel < 6) {
1906 cprintf("%d You don't have permission to masquerade.\n",
1907 ERROR + HIGHER_ACCESS_REQUIRED);
1910 extract(newusername, entargs, 4);
1911 memset(CC->fake_postname, 0, 32);
1912 strcpy(CC->fake_postname, newusername);
1913 cprintf("%d Ok\n", OK);
1916 CC->cs_flags |= CS_POSTING;
1919 if (CC->quickroom.QRflags & QR_MAILBOX) {
1920 if (CC->usersupp.axlevel >= 2) {
1921 strcpy(buf, recipient);
1923 strcpy(buf, "sysop");
1924 e = alias(buf); /* alias and mail type */
1925 if ((buf[0] == 0) || (e == MES_ERROR)) {
1926 cprintf("%d Unknown address - cannot send message.\n",
1927 ERROR + NO_SUCH_USER);
1930 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1931 cprintf("%d Net privileges required for network mail.\n",
1932 ERROR + HIGHER_ACCESS_REQUIRED);
1935 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1936 && ((CC->usersupp.flags & US_INTERNET) == 0)
1937 && (!CC->internal_pgm)) {
1938 cprintf("%d You don't have access to Internet mail.\n",
1939 ERROR + HIGHER_ACCESS_REQUIRED);
1942 if (!strcasecmp(buf, "sysop")) {
1947 goto SKFALL; /* don't search local file */
1948 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1949 cprintf("%d Can't send mail to yourself!\n",
1950 ERROR + NO_SUCH_USER);
1953 /* Check to make sure the user exists; also get the correct
1954 * upper/lower casing of the name.
1956 a = getuser(&tempUS, buf);
1958 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1961 strcpy(buf, tempUS.fullname);
1964 SKFALL: b = MES_NORMAL;
1965 if (CC->quickroom.QRflags & QR_ANONONLY)
1967 if (CC->quickroom.QRflags & QR_ANONOPT) {
1971 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
1974 /* If we're only checking the validity of the request, return
1975 * success without creating the message.
1978 cprintf("%d %s\n", OK, buf);
1982 cprintf("%d send message\n", SEND_LISTING);
1984 /* Read in the message from the client. */
1985 if (CC->fake_postname[0])
1986 msg = make_message(&CC->usersupp, buf,
1987 CC->quickroom.QRname, b, e, format_type,
1989 else if (CC->fake_username[0])
1990 msg = make_message(&CC->usersupp, buf,
1991 CC->quickroom.QRname, b, e, format_type,
1994 msg = make_message(&CC->usersupp, buf,
1995 CC->quickroom.QRname, b, e, format_type, "");
1998 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
1999 CtdlFreeMessage(msg);
2000 CC->fake_postname[0] = '\0';
2007 * message entry - mode 3 (raw)
2009 void cmd_ent3(char *entargs)
2015 unsigned char ch, which_field;
2016 struct usersupp tempUS;
2018 struct CtdlMessage *msg;
2021 if (CC->internal_pgm == 0) {
2022 cprintf("%d This command is for internal programs only.\n",
2027 /* See if there's a recipient, but make sure it's a real one */
2028 extract(recp, entargs, 1);
2029 for (a = 0; a < strlen(recp); ++a)
2030 if (!isprint(recp[a]))
2031 strcpy(&recp[a], &recp[a + 1]);
2032 while (isspace(recp[0]))
2033 strcpy(recp, &recp[1]);
2034 while (isspace(recp[strlen(recp) - 1]))
2035 recp[strlen(recp) - 1] = 0;
2037 /* If we're in Mail, check the recipient */
2038 if (strlen(recp) > 0) {
2039 e = alias(recp); /* alias and mail type */
2040 if ((recp[0] == 0) || (e == MES_ERROR)) {
2041 cprintf("%d Unknown address - cannot send message.\n",
2042 ERROR + NO_SUCH_USER);
2045 if (e == MES_LOCAL) {
2046 a = getuser(&tempUS, recp);
2048 cprintf("%d No such user.\n",
2049 ERROR + NO_SUCH_USER);
2055 /* At this point, message has been approved. */
2056 if (extract_int(entargs, 0) == 0) {
2057 cprintf("%d OK to send\n", OK);
2061 msglen = extract_long(entargs, 2);
2062 msg = mallok(sizeof(struct CtdlMessage));
2064 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2068 memset(msg, 0, sizeof(struct CtdlMessage));
2069 tempbuf = mallok(msglen);
2070 if (tempbuf == NULL) {
2071 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2076 cprintf("%d %ld\n", SEND_BINARY, msglen);
2078 client_read(&ch, 1); /* 0xFF magic number */
2079 msg->cm_magic = CTDLMESSAGE_MAGIC;
2080 client_read(&ch, 1); /* anon type */
2081 msg->cm_anon_type = ch;
2082 client_read(&ch, 1); /* format type */
2083 msg->cm_format_type = ch;
2084 msglen = msglen - 3;
2086 while (msglen > 0) {
2087 client_read(&which_field, 1);
2088 if (!isalpha(which_field)) valid_msg = 0;
2092 client_read(&ch, 1);
2094 a = strlen(tempbuf);
2097 } while ( (ch != 0) && (msglen > 0) );
2099 msg->cm_fields[which_field] = strdoop(tempbuf);
2102 msg->cm_flags = CM_SKIP_HOOKS;
2103 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2104 CtdlFreeMessage(msg);
2110 * API function to delete messages which match a set of criteria
2111 * (returns the actual number of messages deleted)
2113 int CtdlDeleteMessages(char *room_name, /* which room */
2114 long dmsgnum, /* or "0" for any */
2115 char *content_type /* or NULL for any */
2119 struct quickroom qrbuf;
2120 struct cdbdata *cdbfr;
2121 long *msglist = NULL;
2124 int num_deleted = 0;
2126 struct SuppMsgInfo smi;
2128 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2129 room_name, dmsgnum, content_type);
2131 /* get room record, obtaining a lock... */
2132 if (lgetroom(&qrbuf, room_name) != 0) {
2133 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2135 return (0); /* room not found */
2137 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2139 if (cdbfr != NULL) {
2140 msglist = mallok(cdbfr->len);
2141 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2142 num_msgs = cdbfr->len / sizeof(long);
2146 for (i = 0; i < num_msgs; ++i) {
2149 /* Set/clear a bit for each criterion */
2151 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2152 delete_this |= 0x01;
2154 if (content_type == NULL) {
2155 delete_this |= 0x02;
2157 GetSuppMsgInfo(&smi, msglist[i]);
2158 if (!strcasecmp(smi.smi_content_type,
2160 delete_this |= 0x02;
2164 /* Delete message only if all bits are set */
2165 if (delete_this == 0x03) {
2166 AdjRefCount(msglist[i], -1);
2172 num_msgs = sort_msglist(msglist, num_msgs);
2173 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2174 msglist, (num_msgs * sizeof(long)));
2176 qrbuf.QRhighest = msglist[num_msgs - 1];
2180 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2181 return (num_deleted);
2187 * Delete message from current room
2189 void cmd_dele(char *delstr)
2194 getuser(&CC->usersupp, CC->curr_user);
2195 if ((CC->usersupp.axlevel < 6)
2196 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2197 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2198 && (!(CC->internal_pgm))) {
2199 cprintf("%d Higher access required.\n",
2200 ERROR + HIGHER_ACCESS_REQUIRED);
2203 delnum = extract_long(delstr, 0);
2205 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2208 cprintf("%d %d message%s deleted.\n", OK,
2209 num_deleted, ((num_deleted != 1) ? "s" : ""));
2211 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2217 * move or copy a message to another room
2219 void cmd_move(char *args)
2223 struct quickroom qtemp;
2227 num = extract_long(args, 0);
2228 extract(targ, args, 1);
2229 targ[ROOMNAMELEN - 1] = 0;
2230 is_copy = extract_int(args, 2);
2232 getuser(&CC->usersupp, CC->curr_user);
2233 if ((CC->usersupp.axlevel < 6)
2234 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2235 cprintf("%d Higher access required.\n",
2236 ERROR + HIGHER_ACCESS_REQUIRED);
2240 if (getroom(&qtemp, targ) != 0) {
2241 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2245 err = CtdlSaveMsgPointerInRoom(targ, num,
2246 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2248 cprintf("%d Cannot store message in %s: error %d\n",
2253 /* Now delete the message from the source room,
2254 * if this is a 'move' rather than a 'copy' operation.
2256 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2258 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2264 * GetSuppMsgInfo() - Get the supplementary record for a message
2266 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2269 struct cdbdata *cdbsmi;
2272 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2273 smibuf->smi_msgnum = msgnum;
2274 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2276 /* Use the negative of the message number for its supp record index */
2277 TheIndex = (0L - msgnum);
2279 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2280 if (cdbsmi == NULL) {
2281 return; /* record not found; go with defaults */
2283 memcpy(smibuf, cdbsmi->ptr,
2284 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2285 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2292 * PutSuppMsgInfo() - (re)write supplementary record for a message
2294 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2298 /* Use the negative of the message number for its supp record index */
2299 TheIndex = (0L - smibuf->smi_msgnum);
2301 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2302 smibuf->smi_msgnum, smibuf->smi_refcount);
2304 cdb_store(CDB_MSGMAIN,
2305 &TheIndex, sizeof(long),
2306 smibuf, sizeof(struct SuppMsgInfo));
2311 * AdjRefCount - change the reference count for a message;
2312 * delete the message if it reaches zero
2314 void AdjRefCount(long msgnum, int incr)
2317 struct SuppMsgInfo smi;
2320 /* This is a *tight* critical section; please keep it that way, as
2321 * it may get called while nested in other critical sections.
2322 * Complicating this any further will surely cause deadlock!
2324 begin_critical_section(S_SUPPMSGMAIN);
2325 GetSuppMsgInfo(&smi, msgnum);
2326 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2327 msgnum, smi.smi_refcount);
2328 smi.smi_refcount += incr;
2329 PutSuppMsgInfo(&smi);
2330 end_critical_section(S_SUPPMSGMAIN);
2331 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2332 msgnum, smi.smi_refcount);
2334 /* If the reference count is now zero, delete the message
2335 * (and its supplementary record as well).
2337 if (smi.smi_refcount == 0) {
2338 lprintf(9, "Deleting message <%ld>\n", msgnum);
2340 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2341 delnum = (0L - msgnum);
2342 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2347 * Write a generic object to this room
2349 * Note: this could be much more efficient. Right now we use two temporary
2350 * files, and still pull the message into memory as with all others.
2352 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2353 char *content_type, /* MIME type of this object */
2354 char *tempfilename, /* Where to fetch it from */
2355 struct usersupp *is_mailbox, /* Mailbox room? */
2356 int is_binary, /* Is encoding necessary? */
2357 int is_unique, /* Del others of this type? */
2358 unsigned int flags /* Internal save flags */
2363 char filename[PATH_MAX];
2366 struct quickroom qrbuf;
2367 char roomname[ROOMNAMELEN];
2368 struct CtdlMessage *msg;
2371 if (is_mailbox != NULL)
2372 MailboxName(roomname, is_mailbox, req_room);
2374 safestrncpy(roomname, req_room, sizeof(roomname));
2375 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2377 strcpy(filename, tmpnam(NULL));
2378 fp = fopen(filename, "w");
2382 tempfp = fopen(tempfilename, "r");
2383 if (tempfp == NULL) {
2389 fprintf(fp, "Content-type: %s\n", content_type);
2390 lprintf(9, "Content-type: %s\n", content_type);
2392 if (is_binary == 0) {
2393 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2394 while (ch = getc(tempfp), ch > 0)
2400 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2403 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2404 tempfilename, filename);
2408 lprintf(9, "Allocating\n");
2409 msg = mallok(sizeof(struct CtdlMessage));
2410 memset(msg, 0, sizeof(struct CtdlMessage));
2411 msg->cm_magic = CTDLMESSAGE_MAGIC;
2412 msg->cm_anon_type = MES_NORMAL;
2413 msg->cm_format_type = 4;
2414 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2415 msg->cm_fields['O'] = strdoop(req_room);
2416 msg->cm_fields['N'] = strdoop(config.c_nodename);
2417 msg->cm_fields['H'] = strdoop(config.c_humannode);
2418 msg->cm_flags = flags;
2420 lprintf(9, "Loading\n");
2421 fp = fopen(filename, "rb");
2422 fseek(fp, 0L, SEEK_END);
2425 msg->cm_fields['M'] = mallok(len);
2426 fread(msg->cm_fields['M'], len, 1, fp);
2430 /* Create the requested room if we have to. */
2431 if (getroom(&qrbuf, roomname) != 0) {
2432 create_room(roomname,
2433 ( (is_mailbox != NULL) ? 4 : 3 ),
2436 /* If the caller specified this object as unique, delete all
2437 * other objects of this type that are currently in the room.
2440 lprintf(9, "Deleted %d other msgs of this type\n",
2441 CtdlDeleteMessages(roomname, 0L, content_type));
2443 /* Now write the data */
2444 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2445 CtdlFreeMessage(msg);
2453 void CtdlGetSysConfigBackend(long msgnum) {
2454 config_msgnum = msgnum;
2458 char *CtdlGetSysConfig(char *sysconfname) {
2459 char hold_rm[ROOMNAMELEN];
2462 struct CtdlMessage *msg;
2465 strcpy(hold_rm, CC->quickroom.QRname);
2466 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2467 getroom(&CC->quickroom, hold_rm);
2472 /* We want the last (and probably only) config in this room */
2473 begin_critical_section(S_CONFIG);
2474 config_msgnum = (-1L);
2475 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2476 CtdlGetSysConfigBackend);
2477 msgnum = config_msgnum;
2478 end_critical_section(S_CONFIG);
2484 msg = CtdlFetchMessage(msgnum);
2486 conf = strdoop(msg->cm_fields['M']);
2487 CtdlFreeMessage(msg);
2494 getroom(&CC->quickroom, hold_rm);
2496 lprintf(9, "eggstracting...\n");
2497 if (conf != NULL) do {
2498 extract_token(buf, conf, 0, '\n');
2499 lprintf(9, "eggstracted <%s>\n", buf);
2500 strcpy(conf, &conf[strlen(buf)+1]);
2501 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2506 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2507 char temp[PATH_MAX];
2510 strcpy(temp, tmpnam(NULL));
2512 fp = fopen(temp, "w");
2513 if (fp == NULL) return;
2514 fprintf(fp, "%s", sysconfdata);
2517 /* this handy API function does all the work for us */
2518 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);