21 #include "sysdep_decls.h"
22 #include "citserver.h"
27 #include "dynloader.h"
29 #include "mime_parser.h"
32 #include "internet_addressing.h"
34 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
35 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
36 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
38 extern struct config config;
42 "", "", "", "", "", "", "", "",
43 "", "", "", "", "", "", "", "",
44 "", "", "", "", "", "", "", "",
45 "", "", "", "", "", "", "", "",
46 "", "", "", "", "", "", "", "",
47 "", "", "", "", "", "", "", "",
48 "", "", "", "", "", "", "", "",
49 "", "", "", "", "", "", "", "",
76 * This function is self explanatory.
77 * (What can I say, I'm in a weird mood today...)
79 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
83 for (i = 0; i < strlen(name); ++i)
86 if (isspace(name[i - 1])) {
87 strcpy(&name[i - 1], &name[i]);
90 while (isspace(name[i + 1])) {
91 strcpy(&name[i + 1], &name[i + 2]);
98 * Aliasing for network mail.
99 * (Error messages have been commented out, because this is a server.)
101 int alias(char *name)
102 { /* process alias and routing info for mail */
105 char aaa[300], bbb[300];
107 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
109 fp = fopen("network/mail.aliases", "r");
111 fp = fopen("/dev/null", "r");
116 while (fgets(aaa, sizeof aaa, fp) != NULL) {
117 while (isspace(name[0]))
118 strcpy(name, &name[1]);
119 aaa[strlen(aaa) - 1] = 0;
121 for (a = 0; a < strlen(aaa); ++a) {
123 strcpy(bbb, &aaa[a + 1]);
127 if (!strcasecmp(name, aaa))
131 lprintf(7, "Mail is being forwarded to %s\n", name);
133 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
134 for (a=0; a<strlen(name); ++a) {
135 if (name[a] == '@') {
136 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
138 lprintf(7, "Changed to <%s>\n", name);
143 /* determine local or remote type, see citadel.h */
144 for (a = 0; a < strlen(name); ++a)
146 return (MES_INTERNET);
147 for (a = 0; a < strlen(name); ++a)
149 for (b = a; b < strlen(name); ++b)
151 return (MES_INTERNET);
153 for (a = 0; a < strlen(name); ++a)
157 lprintf(7, "Too many @'s in address\n");
161 for (a = 0; a < strlen(name); ++a)
163 strcpy(bbb, &name[a + 1]);
165 strcpy(bbb, &bbb[1]);
166 fp = fopen("network/mail.sysinfo", "r");
170 a = getstring(fp, aaa);
171 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
172 a = getstring(fp, aaa);
173 if (!strncmp(aaa, "use ", 4)) {
174 strcpy(bbb, &aaa[4]);
179 if (!strncmp(aaa, "uum", 3)) {
181 for (a = 0; a < strlen(bbb); ++a) {
187 while (bbb[strlen(bbb) - 1] == '_')
188 bbb[strlen(bbb) - 1] = 0;
189 sprintf(name, &aaa[4], bbb);
190 return (MES_INTERNET);
192 if (!strncmp(aaa, "bin", 3)) {
195 while (aaa[strlen(aaa) - 1] != '@')
196 aaa[strlen(aaa) - 1] = 0;
197 aaa[strlen(aaa) - 1] = 0;
198 while (aaa[strlen(aaa) - 1] == ' ')
199 aaa[strlen(aaa) - 1] = 0;
200 while (bbb[0] != '@')
201 strcpy(bbb, &bbb[1]);
202 strcpy(bbb, &bbb[1]);
203 while (bbb[0] == ' ')
204 strcpy(bbb, &bbb[1]);
205 sprintf(name, "%s @%s", aaa, bbb);
218 fp = fopen("citadel.control", "r");
219 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
225 void simple_listing(long msgnum)
227 cprintf("%ld\n", msgnum);
232 /* Determine if a given message matches the fields in a message template.
233 * Return 0 for a successful match.
235 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
238 /* If there aren't any fields in the template, all messages will
241 if (template == NULL) return(0);
243 /* Null messages are bogus. */
244 if (msg == NULL) return(1);
246 for (i='A'; i<='Z'; ++i) {
247 if (template->cm_fields[i] != NULL) {
248 if (msg->cm_fields[i] == NULL) {
251 if (strcasecmp(msg->cm_fields[i],
252 template->cm_fields[i])) return 1;
256 /* All compares succeeded: we have a match! */
264 * API function to perform an operation for each qualifying message in the
267 void CtdlForEachMessage(int mode, long ref,
269 struct CtdlMessage *compare,
270 void (*CallBack) (long msgnum))
275 struct cdbdata *cdbfr;
276 long *msglist = NULL;
279 struct SuppMsgInfo smi;
280 struct CtdlMessage *msg;
282 /* Learn about the user and room in question */
284 getuser(&CC->usersupp, CC->curr_user);
285 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
287 /* Load the message list */
288 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
290 msglist = mallok(cdbfr->len);
291 memcpy(msglist, cdbfr->ptr, cdbfr->len);
292 num_msgs = cdbfr->len / sizeof(long);
295 return; /* No messages at all? No further action. */
299 /* If the caller is looking for a specific MIME type, then filter
300 * out all messages which are not of the type requested.
303 if (content_type != NULL)
304 if (strlen(content_type) > 0)
305 for (a = 0; a < num_msgs; ++a) {
306 GetSuppMsgInfo(&smi, msglist[a]);
307 if (strcasecmp(smi.smi_content_type, content_type)) {
312 num_msgs = sort_msglist(msglist, num_msgs);
314 /* If a template was supplied, filter out the messages which
315 * don't match. (This could induce some delays!)
318 if (compare != NULL) {
319 for (a = 0; a < num_msgs; ++a) {
320 msg = CtdlFetchMessage(msglist[a]);
322 if (CtdlMsgCmp(msg, compare)) {
325 CtdlFreeMessage(msg);
333 * Now iterate through the message list, according to the
334 * criteria supplied by the caller.
337 for (a = 0; a < num_msgs; ++a) {
338 thismsg = msglist[a];
343 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
344 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
345 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
346 && (CC->usersupp.flags & US_LASTOLD))
347 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
348 || ((mode == MSGS_FIRST) && (a < ref))
349 || ((mode == MSGS_GT) && (thismsg > ref))
355 phree(msglist); /* Clean up */
361 * cmd_msgs() - get list of message #'s in this room
362 * implements the MSGS server command using CtdlForEachMessage()
364 void cmd_msgs(char *cmdbuf)
373 int with_template = 0;
374 struct CtdlMessage *template = NULL;
376 extract(which, cmdbuf, 0);
377 cm_ref = extract_int(cmdbuf, 1);
378 with_template = extract_int(cmdbuf, 2);
382 if (!strncasecmp(which, "OLD", 3))
384 else if (!strncasecmp(which, "NEW", 3))
386 else if (!strncasecmp(which, "FIRST", 5))
388 else if (!strncasecmp(which, "LAST", 4))
390 else if (!strncasecmp(which, "GT", 2))
393 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
394 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
399 cprintf("%d Send template then receive message list\n",
401 template = (struct CtdlMessage *)
402 mallok(sizeof(struct CtdlMessage));
403 memset(template, 0, sizeof(struct CtdlMessage));
404 while(client_gets(buf), strcmp(buf,"000")) {
405 extract(tfield, buf, 0);
406 extract(tvalue, buf, 1);
407 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
408 if (!strcasecmp(tfield, msgkeys[i])) {
409 template->cm_fields[i] =
416 cprintf("%d Message list...\n", LISTING_FOLLOWS);
419 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
420 if (template != NULL) CtdlFreeMessage(template);
428 * help_subst() - support routine for help file viewer
430 void help_subst(char *strbuf, char *source, char *dest)
435 while (p = pattern2(strbuf, source), (p >= 0)) {
436 strcpy(workbuf, &strbuf[p + strlen(source)]);
437 strcpy(&strbuf[p], dest);
438 strcat(strbuf, workbuf);
443 void do_help_subst(char *buffer)
447 help_subst(buffer, "^nodename", config.c_nodename);
448 help_subst(buffer, "^humannode", config.c_humannode);
449 help_subst(buffer, "^fqdn", config.c_fqdn);
450 help_subst(buffer, "^username", CC->usersupp.fullname);
451 sprintf(buf2, "%ld", CC->usersupp.usernum);
452 help_subst(buffer, "^usernum", buf2);
453 help_subst(buffer, "^sysadm", config.c_sysadm);
454 help_subst(buffer, "^variantname", CITADEL);
455 sprintf(buf2, "%d", config.c_maxsessions);
456 help_subst(buffer, "^maxsessions", buf2);
462 * memfmout() - Citadel text formatter and paginator.
463 * Although the original purpose of this routine was to format
464 * text to the reader's screen width, all we're really using it
465 * for here is to format text out to 80 columns before sending it
466 * to the client. The client software may reformat it again.
469 int width, /* screen width to use */
470 char *mptr, /* where are we going to get our text from? */
471 char subst, /* nonzero if we should do substitutions */
472 char *nl) /* string to terminate lines with */
484 c = 1; /* c is the current pos */
487 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
489 buffer[strlen(buffer) + 1] = 0;
490 buffer[strlen(buffer)] = ch;
493 if (buffer[0] == '^')
494 do_help_subst(buffer);
496 buffer[strlen(buffer) + 1] = 0;
498 strcpy(buffer, &buffer[1]);
508 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
510 if (((old == 13) || (old == 10)) && (isspace(real))) {
518 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
519 cprintf("%s%s", nl, aaa);
528 if ((strlen(aaa) + c) > (width - 5)) {
538 if ((ch == 13) || (ch == 10)) {
539 cprintf("%s%s", aaa, nl);
546 FMTEND: cprintf("%s%s", aaa, nl);
552 * Callback function for mime parser that simply lists the part
554 void list_this_part(char *name, char *filename, char *partnum, char *disp,
555 void *content, char *cbtype, size_t length)
558 cprintf("part=%s|%s|%s|%s|%s|%d\n",
559 name, filename, partnum, disp, cbtype, length);
564 * Callback function for mime parser that opens a section for downloading
566 void mime_download(char *name, char *filename, char *partnum, char *disp,
567 void *content, char *cbtype, size_t length)
570 /* Silently go away if there's already a download open... */
571 if (CC->download_fp != NULL)
574 /* ...or if this is not the desired section */
575 if (strcasecmp(desired_section, partnum))
578 CC->download_fp = tmpfile();
579 if (CC->download_fp == NULL)
582 fwrite(content, length, 1, CC->download_fp);
583 fflush(CC->download_fp);
584 rewind(CC->download_fp);
586 OpenCmdResult(filename, cbtype);
592 * Load a message from disk into memory.
593 * This is used by CtdlOutputMsg() and other fetch functions.
595 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
596 * using the CtdlMessageFree() function.
598 struct CtdlMessage *CtdlFetchMessage(long msgnum)
600 struct cdbdata *dmsgtext;
601 struct CtdlMessage *ret = NULL;
604 CIT_UBYTE field_header;
607 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
608 if (dmsgtext == NULL) {
611 mptr = dmsgtext->ptr;
613 /* Parse the three bytes that begin EVERY message on disk.
614 * The first is always 0xFF, the on-disk magic number.
615 * The second is the anonymous/public type byte.
616 * The third is the format type byte (vari, fixed, or MIME).
620 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
624 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
625 memset(ret, 0, sizeof(struct CtdlMessage));
627 ret->cm_magic = CTDLMESSAGE_MAGIC;
628 ret->cm_anon_type = *mptr++; /* Anon type byte */
629 ret->cm_format_type = *mptr++; /* Format type byte */
632 * The rest is zero or more arbitrary fields. Load them in.
633 * We're done when we encounter either a zero-length field or
634 * have just processed the 'M' (message text) field.
637 field_length = strlen(mptr);
638 if (field_length == 0)
640 field_header = *mptr++;
641 ret->cm_fields[field_header] = mallok(field_length);
642 strcpy(ret->cm_fields[field_header], mptr);
644 while (*mptr++ != 0); /* advance to next field */
646 } while ((field_length > 0) && (field_header != 'M'));
650 /* Always make sure there's something in the msg text field */
651 if (ret->cm_fields['M'] == NULL)
652 ret->cm_fields['M'] = strdoop("<no text>\n");
654 /* Perform "before read" hooks (aborting if any return nonzero) */
655 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
656 CtdlFreeMessage(ret);
665 * Returns 1 if the supplied pointer points to a valid Citadel message.
666 * If the pointer is NULL or the magic number check fails, returns 0.
668 int is_valid_message(struct CtdlMessage *msg) {
671 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
672 lprintf(3, "is_valid_message() -- self-check failed\n");
680 * 'Destructor' for struct CtdlMessage
682 void CtdlFreeMessage(struct CtdlMessage *msg)
686 if (is_valid_message(msg) == 0) return;
688 for (i = 0; i < 256; ++i)
689 if (msg->cm_fields[i] != NULL) {
690 phree(msg->cm_fields[i]);
693 msg->cm_magic = 0; /* just in case */
699 * Callback function for mime parser that wants to display text
701 void fixed_output(char *name, char *filename, char *partnum, char *disp,
702 void *content, char *cbtype, size_t length)
709 if (!strcasecmp(cbtype, "multipart/alternative")) {
710 strcpy(ma->prefix, partnum);
711 strcat(ma->prefix, ".");
717 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
719 && (ma->did_print == 1) ) {
720 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
726 if ( (!strcasecmp(cbtype, "text/plain"))
727 || (strlen(cbtype)==0) ) {
732 if (ch==10) cprintf("\r\n");
733 else cprintf("%c", ch);
736 else if (!strcasecmp(cbtype, "text/html")) {
737 ptr = html_to_ascii(content, 80, 0);
742 if (ch==10) cprintf("\r\n");
743 else cprintf("%c", ch);
747 else if (strncasecmp(cbtype, "multipart/", 10)) {
748 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
749 partnum, filename, cbtype, length);
755 * Get a message off disk. (returns om_* values found in msgbase.h)
758 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
759 int mode, /* how would you like that message? */
760 int headers_only, /* eschew the message body? */
761 int do_proto, /* do Citadel protocol responses? */
762 int crlf /* Use CRLF newlines instead of LF? */
768 char display_name[256];
769 struct CtdlMessage *TheMessage;
771 char *nl; /* newline string */
773 /* buffers needed for RFC822 translation */
783 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
787 sprintf(mid, "%ld", msg_num);
788 nl = (crlf ? "\r\n" : "\n");
790 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
791 if (do_proto) cprintf("%d Not logged in.\n",
792 ERROR + NOT_LOGGED_IN);
793 return(om_not_logged_in);
796 /* FIXME ... small security issue
797 * We need to check to make sure the requested message is actually
798 * in the current room, and set msg_ok to 1 only if it is. This
799 * functionality is currently missing because I'm in a hurry to replace
800 * broken production code with nonbroken pre-beta code. :( -- ajc
803 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
805 return(om_no_such_msg);
810 * Fetch the message from disk
812 TheMessage = CtdlFetchMessage(msg_num);
813 if (TheMessage == NULL) {
814 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
816 return(om_no_such_msg);
819 /* Are we downloading a MIME component? */
820 if (mode == MT_DOWNLOAD) {
821 if (TheMessage->cm_format_type != FMT_RFC822) {
823 cprintf("%d This is not a MIME message.\n",
825 } else if (CC->download_fp != NULL) {
826 if (do_proto) cprintf(
827 "%d You already have a download open.\n",
830 /* Parse the message text component */
831 mptr = TheMessage->cm_fields['M'];
832 mime_parser(mptr, NULL, *mime_download);
833 /* If there's no file open by this time, the requested
834 * section wasn't found, so print an error
836 if (CC->download_fp == NULL) {
837 if (do_proto) cprintf(
838 "%d Section %s not found.\n",
839 ERROR + FILE_NOT_FOUND,
843 CtdlFreeMessage(TheMessage);
844 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
847 /* now for the user-mode message reading loops */
848 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
850 /* Tell the client which format type we're using. If this is a
851 * MIME message, *lie* about it and tell the user it's fixed-format.
853 if (mode == MT_CITADEL) {
854 if (TheMessage->cm_format_type == FMT_RFC822) {
855 if (do_proto) cprintf("type=1\n");
858 if (do_proto) cprintf("type=%d\n",
859 TheMessage->cm_format_type);
863 /* nhdr=yes means that we're only displaying headers, no body */
864 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
865 if (do_proto) cprintf("nhdr=yes\n");
868 /* begin header processing loop for Citadel message format */
870 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
872 strcpy(display_name, "<unknown>");
873 if (TheMessage->cm_fields['A']) {
874 strcpy(buf, TheMessage->cm_fields['A']);
875 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
876 if (TheMessage->cm_anon_type == MES_ANON)
877 strcpy(display_name, "****");
878 else if (TheMessage->cm_anon_type == MES_AN2)
879 strcpy(display_name, "anonymous");
881 strcpy(display_name, buf);
883 && ((TheMessage->cm_anon_type == MES_ANON)
884 || (TheMessage->cm_anon_type == MES_AN2))) {
885 sprintf(&display_name[strlen(display_name)],
890 strcpy(allkeys, FORDER);
891 for (i=0; i<strlen(allkeys); ++i) {
892 k = (int) allkeys[i];
894 if (TheMessage->cm_fields[k] != NULL) {
896 if (do_proto) cprintf("%s=%s\n",
901 if (do_proto) cprintf("%s=%s\n",
903 TheMessage->cm_fields[k]
912 /* begin header processing loop for RFC822 transfer format */
917 strcpy(snode, NODENAME);
918 strcpy(lnode, HUMANNODE);
919 if (mode == MT_RFC822) {
920 cprintf("X-UIDL: %ld%s", msg_num, nl);
921 for (i = 0; i < 256; ++i) {
922 if (TheMessage->cm_fields[i]) {
923 mptr = TheMessage->cm_fields[i];
930 cprintf("Path: %s%s", mptr, nl);
933 cprintf("Subject: %s%s", mptr, nl);
939 cprintf("X-Citadel-Room: %s%s",
944 cprintf("To: %s%s", mptr, nl);
946 generate_rfc822_datestamp(datestamp,
948 cprintf("Date: %s%s", datestamp, nl);
954 for (i=0; i<strlen(suser); ++i) {
955 suser[i] = tolower(suser[i]);
956 if (!isalnum(suser[i])) suser[i]='_';
959 if (mode == MT_RFC822) {
960 if (!strcasecmp(snode, NODENAME)) {
963 cprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
964 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
966 if (strlen(fuser) > 0) {
967 cprintf("From: %s (%s)%s", fuser, luser, nl);
970 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
973 cprintf("Organization: %s%s", lnode, nl);
976 /* end header processing loop ... at this point, we're in the text */
978 mptr = TheMessage->cm_fields['M'];
980 /* Tell the client about the MIME parts in this message */
981 if (TheMessage->cm_format_type == FMT_RFC822) {
982 if (mode == MT_CITADEL) {
983 mime_parser(mptr, NULL, *list_this_part);
985 else if (mode == MT_MIME) { /* list parts only */
986 mime_parser(mptr, NULL, *list_this_part);
987 if (do_proto) cprintf("000\n");
988 CtdlFreeMessage(TheMessage);
991 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
992 /* FIXME ... we have to put some code in here to avoid
993 * printing duplicate header information when both
994 * Citadel and RFC822 headers exist. Preference should
995 * probably be given to the RFC822 headers.
997 while (ch=*(mptr++), ch!=0) {
999 else if (ch==10) cprintf("%s", nl);
1000 else cprintf("%c", ch);
1002 if (do_proto) cprintf("000\n");
1003 CtdlFreeMessage(TheMessage);
1009 if (do_proto) cprintf("000\n");
1010 CtdlFreeMessage(TheMessage);
1014 /* signify start of msg text */
1015 if (mode == MT_CITADEL)
1016 if (do_proto) cprintf("text\n");
1017 if (mode == MT_RFC822) {
1018 if (TheMessage->cm_fields['U'] == NULL) {
1019 cprintf("Subject: (no subject)%s", nl);
1024 /* If the format type on disk is 1 (fixed-format), then we want
1025 * everything to be output completely literally ... regardless of
1026 * what message transfer format is in use.
1028 if (TheMessage->cm_format_type == FMT_FIXED) {
1030 while (ch = *mptr++, ch > 0) {
1033 if ((ch == 10) || (strlen(buf) > 250)) {
1034 cprintf("%s%s", buf, nl);
1037 buf[strlen(buf) + 1] = 0;
1038 buf[strlen(buf)] = ch;
1041 if (strlen(buf) > 0)
1042 cprintf("%s%s", buf, nl);
1045 /* If the message on disk is format 0 (Citadel vari-format), we
1046 * output using the formatter at 80 columns. This is the final output
1047 * form if the transfer format is RFC822, but if the transfer format
1048 * is Citadel proprietary, it'll still work, because the indentation
1049 * for new paragraphs is correct and the client will reformat the
1050 * message to the reader's screen width.
1052 if (TheMessage->cm_format_type == FMT_CITADEL) {
1053 memfmout(80, mptr, 0, nl);
1056 /* If the message on disk is format 4 (MIME), we've gotta hand it
1057 * off to the MIME parser. The client has already been told that
1058 * this message is format 1 (fixed format), so the callback function
1059 * we use will display those parts as-is.
1061 if (TheMessage->cm_format_type == FMT_RFC822) {
1062 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1063 memset(ma, 0, sizeof(struct ma_info));
1064 mime_parser(mptr, NULL, *fixed_output);
1067 /* now we're done */
1068 if (do_proto) cprintf("000\n");
1069 CtdlFreeMessage(TheMessage);
1076 * display a message (mode 0 - Citadel proprietary)
1078 void cmd_msg0(char *cmdbuf)
1081 int headers_only = 0;
1083 msgid = extract_long(cmdbuf, 0);
1084 headers_only = extract_int(cmdbuf, 1);
1086 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1092 * display a message (mode 2 - RFC822)
1094 void cmd_msg2(char *cmdbuf)
1097 int headers_only = 0;
1099 msgid = extract_long(cmdbuf, 0);
1100 headers_only = extract_int(cmdbuf, 1);
1102 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1108 * display a message (mode 3 - IGnet raw format - internal programs only)
1110 void cmd_msg3(char *cmdbuf)
1113 struct CtdlMessage *msg;
1116 if (CC->internal_pgm == 0) {
1117 cprintf("%d This command is for internal programs only.\n",
1122 msgnum = extract_long(cmdbuf, 0);
1123 msg = CtdlFetchMessage(msgnum);
1125 cprintf("%d Message %ld not found.\n",
1130 serialize_message(&smr, msg);
1131 CtdlFreeMessage(msg);
1134 cprintf("%d Unable to serialize message\n",
1135 ERROR+INTERNAL_ERROR);
1139 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1140 client_write(smr.ser, smr.len);
1147 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1149 void cmd_msg4(char *cmdbuf)
1153 msgid = extract_long(cmdbuf, 0);
1154 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1158 * Open a component of a MIME message as a download file
1160 void cmd_opna(char *cmdbuf)
1164 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1166 msgid = extract_long(cmdbuf, 0);
1167 extract(desired_section, cmdbuf, 1);
1169 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1174 * Save a message pointer into a specified room
1175 * (Returns 0 for success, nonzero for failure)
1176 * roomname may be NULL to use the current room
1178 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1180 char hold_rm[ROOMNAMELEN];
1181 struct cdbdata *cdbfr;
1184 long highest_msg = 0L;
1185 struct CtdlMessage *msg = NULL;
1187 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1188 roomname, msgid, flags);
1190 strcpy(hold_rm, CC->quickroom.QRname);
1192 /* We may need to check to see if this message is real */
1193 if ( (flags & SM_VERIFY_GOODNESS)
1194 || (flags & SM_DO_REPL_CHECK)
1196 msg = CtdlFetchMessage(msgid);
1197 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1200 /* Perform replication checks if necessary */
1201 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1203 if (getroom(&CC->quickroom,
1204 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1206 lprintf(9, "No such room <%s>\n", roomname);
1207 if (msg != NULL) CtdlFreeMessage(msg);
1208 return(ERROR + ROOM_NOT_FOUND);
1211 if (ReplicationChecks(msg) != 0) {
1212 getroom(&CC->quickroom, hold_rm);
1213 if (msg != NULL) CtdlFreeMessage(msg);
1214 lprintf(9, "Did replication, and newer exists\n");
1219 /* Now the regular stuff */
1220 if (lgetroom(&CC->quickroom,
1221 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1223 lprintf(9, "No such room <%s>\n", roomname);
1224 if (msg != NULL) CtdlFreeMessage(msg);
1225 return(ERROR + ROOM_NOT_FOUND);
1228 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1229 if (cdbfr == NULL) {
1233 msglist = mallok(cdbfr->len);
1234 if (msglist == NULL)
1235 lprintf(3, "ERROR malloc msglist!\n");
1236 num_msgs = cdbfr->len / sizeof(long);
1237 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1242 /* Make sure the message doesn't already exist in this room. It
1243 * is absolutely taboo to have more than one reference to the same
1244 * message in a room.
1246 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1247 if (msglist[i] == msgid) {
1248 lputroom(&CC->quickroom); /* unlock the room */
1249 getroom(&CC->quickroom, hold_rm);
1250 if (msg != NULL) CtdlFreeMessage(msg);
1251 return(ERROR + ALREADY_EXISTS);
1255 /* Now add the new message */
1257 msglist = reallok(msglist,
1258 (num_msgs * sizeof(long)));
1260 if (msglist == NULL) {
1261 lprintf(3, "ERROR: can't realloc message list!\n");
1263 msglist[num_msgs - 1] = msgid;
1265 /* Sort the message list, so all the msgid's are in order */
1266 num_msgs = sort_msglist(msglist, num_msgs);
1268 /* Determine the highest message number */
1269 highest_msg = msglist[num_msgs - 1];
1271 /* Write it back to disk. */
1272 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1273 msglist, num_msgs * sizeof(long));
1275 /* Free up the memory we used. */
1278 /* Update the highest-message pointer and unlock the room. */
1279 CC->quickroom.QRhighest = highest_msg;
1280 lputroom(&CC->quickroom);
1281 getroom(&CC->quickroom, hold_rm);
1283 /* Bump the reference count for this message. */
1284 if ((flags & SM_DONT_BUMP_REF)==0) {
1285 AdjRefCount(msgid, +1);
1288 /* Return success. */
1289 if (msg != NULL) CtdlFreeMessage(msg);
1296 * Message base operation to send a message to the master file
1297 * (returns new message number)
1299 * This is the back end for CtdlSaveMsg() and should not be directly
1300 * called by server-side modules.
1303 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1304 int generate_id, /* generate 'I' field? */
1305 FILE *save_a_copy) /* save a copy to disk? */
1312 /* Get a new message number */
1313 newmsgid = get_new_message_number();
1314 sprintf(msgidbuf, "%ld", newmsgid);
1317 msg->cm_fields['I'] = strdoop(msgidbuf);
1320 serialize_message(&smr, msg);
1323 cprintf("%d Unable to serialize message\n",
1324 ERROR+INTERNAL_ERROR);
1328 /* Write our little bundle of joy into the message base */
1329 begin_critical_section(S_MSGMAIN);
1330 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1331 smr.ser, smr.len) < 0) {
1332 lprintf(2, "Can't store message\n");
1337 end_critical_section(S_MSGMAIN);
1339 /* If the caller specified that a copy should be saved to a particular
1340 * file handle, do that now too.
1342 if (save_a_copy != NULL) {
1343 fwrite(smr.ser, smr.len, 1, save_a_copy);
1346 /* Free the memory we used for the serialized message */
1349 /* Return the *local* message ID to the caller
1350 * (even if we're storing an incoming network message)
1358 * Serialize a struct CtdlMessage into the format used on disk and network.
1360 * This function loads up a "struct ser_ret" (defined in server.h) which
1361 * contains the length of the serialized message and a pointer to the
1362 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1364 void serialize_message(struct ser_ret *ret, /* return values */
1365 struct CtdlMessage *msg) /* unserialized msg */
1369 static char *forder = FORDER;
1371 if (is_valid_message(msg) == 0) return; /* self check */
1374 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1375 ret->len = ret->len +
1376 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1378 lprintf(9, "calling malloc\n");
1379 ret->ser = mallok(ret->len);
1380 if (ret->ser == NULL) {
1386 ret->ser[1] = msg->cm_anon_type;
1387 ret->ser[2] = msg->cm_format_type;
1390 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1391 ret->ser[wlen++] = (char)forder[i];
1392 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1393 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1395 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1404 * Back end for the ReplicationChecks() function
1406 void check_repl(long msgnum) {
1407 struct CtdlMessage *msg;
1408 time_t timestamp = (-1L);
1410 lprintf(9, "check_repl() found message %ld\n", msgnum);
1411 msg = CtdlFetchMessage(msgnum);
1412 if (msg == NULL) return;
1413 if (msg->cm_fields['T'] != NULL) {
1414 timestamp = atol(msg->cm_fields['T']);
1416 CtdlFreeMessage(msg);
1418 if (timestamp > msg_repl->highest) {
1419 msg_repl->highest = timestamp; /* newer! */
1420 lprintf(9, "newer!\n");
1423 lprintf(9, "older!\n");
1425 /* Existing isn't newer? Then delete the old one(s). */
1426 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1431 * Check to see if any messages already exist which carry the same Extended ID
1435 * -> With older timestamps: delete them and return 0. Message will be saved.
1436 * -> With newer timestamps: return 1. Message save will be aborted.
1438 int ReplicationChecks(struct CtdlMessage *msg) {
1439 struct CtdlMessage *template;
1442 lprintf(9, "ReplicationChecks() started\n");
1443 /* No extended id? Don't do anything. */
1444 if (msg->cm_fields['E'] == NULL) return 0;
1445 if (strlen(msg->cm_fields['E']) == 0) return 0;
1446 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1448 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1449 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1450 msg_repl->highest = atol(msg->cm_fields['T']);
1452 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1453 memset(template, 0, sizeof(struct CtdlMessage));
1454 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1456 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1458 /* If a newer message exists with the same Extended ID, abort
1461 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1465 CtdlFreeMessage(template);
1466 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1474 * Save a message to disk
1476 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1477 char *rec, /* Recipient (mail) */
1478 char *force, /* force a particular room? */
1479 int supplied_mailtype, /* local or remote type */
1480 int generate_id) /* 1 = generate 'I' field */
1483 char hold_rm[ROOMNAMELEN];
1484 char actual_rm[ROOMNAMELEN];
1485 char force_room[ROOMNAMELEN];
1486 char content_type[256]; /* We have to learn this */
1487 char recipient[256];
1490 struct usersupp userbuf;
1492 struct SuppMsgInfo smi;
1493 FILE *network_fp = NULL;
1494 static int seqnum = 1;
1495 struct CtdlMessage *imsg;
1499 lprintf(9, "CtdlSaveMsg() called\n");
1500 if (is_valid_message(msg) == 0) return(-1); /* self check */
1501 mailtype = supplied_mailtype;
1503 /* If this message has no timestamp, we take the liberty of
1504 * giving it one, right now.
1506 if (msg->cm_fields['T'] == NULL) {
1507 lprintf(9, "Generating timestamp\n");
1508 sprintf(aaa, "%ld", time(NULL));
1509 msg->cm_fields['T'] = strdoop(aaa);
1512 /* If this message has no path, we generate one.
1514 if (msg->cm_fields['P'] == NULL) {
1515 lprintf(9, "Generating path\n");
1516 if (msg->cm_fields['A'] != NULL) {
1517 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1518 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1519 if (isspace(msg->cm_fields['P'][a])) {
1520 msg->cm_fields['P'][a] = ' ';
1525 msg->cm_fields['P'] = strdoop("unknown");
1529 strcpy(force_room, force);
1531 /* Strip non-printable characters out of the recipient name */
1532 lprintf(9, "Checking recipient (if present)\n");
1533 strcpy(recipient, rec);
1534 for (a = 0; a < strlen(recipient); ++a)
1535 if (!isprint(recipient[a]))
1536 strcpy(&recipient[a], &recipient[a + 1]);
1538 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1539 for (a=0; a<strlen(recipient); ++a) {
1540 if (recipient[a] == '@') {
1541 if (CtdlHostAlias(&recipient[a+1])
1542 == hostalias_localhost) {
1544 lprintf(7, "Changed to <%s>\n", recipient);
1545 mailtype = MES_LOCAL;
1550 lprintf(9, "Recipient is <%s>\n", recipient);
1552 /* Learn about what's inside, because it's what's inside that counts */
1553 lprintf(9, "Learning what's inside\n");
1554 if (msg->cm_fields['M'] == NULL) {
1555 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1558 switch (msg->cm_format_type) {
1560 strcpy(content_type, "text/x-citadel-variformat");
1563 strcpy(content_type, "text/plain");
1566 strcpy(content_type, "text/plain");
1567 /* advance past header fields */
1568 mptr = msg->cm_fields['M'];
1571 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1572 safestrncpy(content_type, mptr,
1573 sizeof(content_type));
1574 strcpy(content_type, &content_type[14]);
1575 for (a = 0; a < strlen(content_type); ++a)
1576 if ((content_type[a] == ';')
1577 || (content_type[a] == ' ')
1578 || (content_type[a] == 13)
1579 || (content_type[a] == 10))
1580 content_type[a] = 0;
1587 /* Goto the correct room */
1588 lprintf(9, "Switching rooms\n");
1589 strcpy(hold_rm, CC->quickroom.QRname);
1590 strcpy(actual_rm, CC->quickroom.QRname);
1592 /* If the user is a twit, move to the twit room for posting */
1593 lprintf(9, "Handling twit stuff\n");
1595 if (CC->usersupp.axlevel == 2) {
1596 strcpy(hold_rm, actual_rm);
1597 strcpy(actual_rm, config.c_twitroom);
1601 /* ...or if this message is destined for Aide> then go there. */
1602 if (strlen(force_room) > 0) {
1603 strcpy(actual_rm, force_room);
1606 lprintf(9, "Possibly relocating\n");
1607 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1608 getroom(&CC->quickroom, actual_rm);
1612 * If this message has no O (room) field, generate one.
1614 if (msg->cm_fields['O'] == NULL) {
1615 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1618 /* Perform "before save" hooks (aborting if any return nonzero) */
1619 lprintf(9, "Performing before-save hooks\n");
1620 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1622 /* If this message has an Extended ID, perform replication checks */
1623 lprintf(9, "Performing replication checks\n");
1624 if (ReplicationChecks(msg) > 0) return(-1);
1626 /* Network mail - send a copy to the network program. */
1627 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1628 lprintf(9, "Sending network spool\n");
1629 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1630 (long) getpid(), CC->cs_pid, ++seqnum);
1631 lprintf(9, "Saving a copy to %s\n", aaa);
1632 network_fp = fopen(aaa, "ab+");
1633 if (network_fp == NULL)
1634 lprintf(2, "ERROR: %s\n", strerror(errno));
1637 /* Save it to disk */
1638 lprintf(9, "Saving to disk\n");
1639 newmsgid = send_message(msg, generate_id, network_fp);
1640 if (network_fp != NULL) {
1642 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1645 if (newmsgid <= 0L) return(-1);
1647 /* Write a supplemental message info record. This doesn't have to
1648 * be a critical section because nobody else knows about this message
1651 lprintf(9, "Creating SuppMsgInfo record\n");
1652 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1653 smi.smi_msgnum = newmsgid;
1654 smi.smi_refcount = 0;
1655 safestrncpy(smi.smi_content_type, content_type, 64);
1656 PutSuppMsgInfo(&smi);
1658 /* Now figure out where to store the pointers */
1659 lprintf(9, "Storing pointers\n");
1661 /* If this is being done by the networker delivering a private
1662 * message, we want to BYPASS saving the sender's copy (because there
1663 * is no local sender; it would otherwise go to the Trashcan).
1665 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1666 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1667 lprintf(3, "ERROR saving message pointer!\n");
1668 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1672 /* For internet mail, drop a copy in the outbound queue room */
1673 if (mailtype == MES_INTERNET) {
1674 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1677 /* Bump this user's messages posted counter. */
1678 lprintf(9, "Updating user\n");
1679 lgetuser(&CC->usersupp, CC->curr_user);
1680 CC->usersupp.posted = CC->usersupp.posted + 1;
1681 lputuser(&CC->usersupp);
1683 /* If this is private, local mail, make a copy in the
1684 * recipient's mailbox and bump the reference count.
1686 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1687 if (getuser(&userbuf, recipient) == 0) {
1688 lprintf(9, "Delivering private mail\n");
1689 MailboxName(actual_rm, &userbuf, MAILROOM);
1690 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1693 lprintf(9, "No user <%s>, saving in %s> instead\n",
1694 recipient, AIDEROOM);
1695 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1699 /* Perform "after save" hooks */
1700 lprintf(9, "Performing after-save hooks\n");
1701 PerformMessageHooks(msg, EVT_AFTERSAVE);
1704 lprintf(9, "Returning to original room\n");
1705 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1706 getroom(&CC->quickroom, hold_rm);
1708 /* For internet mail, generate delivery instructions
1709 * (Yes, this is recursive! Deal with it!)
1711 if (mailtype == MES_INTERNET) {
1712 lprintf(9, "Generating delivery instructions\n");
1713 instr = mallok(2048);
1715 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1718 SPOOLMIME, newmsgid, time(NULL),
1719 msg->cm_fields['A'], msg->cm_fields['N'],
1722 imsg = mallok(sizeof(struct CtdlMessage));
1723 memset(imsg, 0, sizeof(struct CtdlMessage));
1724 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1725 imsg->cm_anon_type = MES_NORMAL;
1726 imsg->cm_format_type = FMT_RFC822;
1727 imsg->cm_fields['A'] = strdoop("Citadel");
1728 imsg->cm_fields['M'] = instr;
1729 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1730 CtdlFreeMessage(imsg);
1739 * Convenience function for generating small administrative messages.
1741 void quickie_message(char *from, char *to, char *room, char *text)
1743 struct CtdlMessage *msg;
1745 msg = mallok(sizeof(struct CtdlMessage));
1746 memset(msg, 0, sizeof(struct CtdlMessage));
1747 msg->cm_magic = CTDLMESSAGE_MAGIC;
1748 msg->cm_anon_type = MES_NORMAL;
1749 msg->cm_format_type = 0;
1750 msg->cm_fields['A'] = strdoop(from);
1751 msg->cm_fields['O'] = strdoop(room);
1752 msg->cm_fields['N'] = strdoop(NODENAME);
1754 msg->cm_fields['R'] = strdoop(to);
1755 msg->cm_fields['M'] = strdoop(text);
1757 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1758 CtdlFreeMessage(msg);
1759 syslog(LOG_NOTICE, text);
1765 * Back end function used by make_message() and similar functions
1767 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1768 size_t maxlen, /* maximum message length */
1769 char *exist /* if non-null, append to it;
1770 exist is ALWAYS freed */
1773 size_t message_len = 0;
1774 size_t buffer_len = 0;
1778 if (exist == NULL) {
1782 m = reallok(exist, strlen(exist) + 4096);
1783 if (m == NULL) phree(exist);
1786 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1793 /* read in the lines of message text one by one */
1795 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1797 /* augment the buffer if we have to */
1798 if ((message_len + strlen(buf) + 2) > buffer_len) {
1799 lprintf(9, "realloking\n");
1800 ptr = reallok(m, (buffer_len * 2) );
1801 if (ptr == NULL) { /* flush if can't allocate */
1802 while ( (client_gets(buf)>0) &&
1803 strcmp(buf, terminator)) ;;
1806 buffer_len = (buffer_len * 2);
1809 lprintf(9, "buffer_len is %d\n", buffer_len);
1813 if (append == NULL) append = m;
1814 while (strlen(append) > 0) ++append;
1815 strcpy(append, buf);
1816 strcat(append, "\n");
1817 message_len = message_len + strlen(buf) + 1;
1819 /* if we've hit the max msg length, flush the rest */
1820 if (message_len >= maxlen) {
1821 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1832 * Build a binary message to be saved on disk.
1835 struct CtdlMessage *make_message(
1836 struct usersupp *author, /* author's usersupp structure */
1837 char *recipient, /* NULL if it's not mail */
1838 char *room, /* room where it's going */
1839 int type, /* see MES_ types in header file */
1840 int net_type, /* see MES_ types in header file */
1841 int format_type, /* local or remote (see citadel.h) */
1842 char *fake_name) /* who we're masquerading as */
1848 struct CtdlMessage *msg;
1850 msg = mallok(sizeof(struct CtdlMessage));
1851 memset(msg, 0, sizeof(struct CtdlMessage));
1852 msg->cm_magic = CTDLMESSAGE_MAGIC;
1853 msg->cm_anon_type = type;
1854 msg->cm_format_type = format_type;
1856 /* Don't confuse the poor folks if it's not routed mail. */
1857 strcpy(dest_node, "");
1859 /* If net_type is MES_BINARY, split out the destination node. */
1860 if (net_type == MES_BINARY) {
1861 strcpy(dest_node, NODENAME);
1862 for (a = 0; a < strlen(recipient); ++a) {
1863 if (recipient[a] == '@') {
1865 strcpy(dest_node, &recipient[a + 1]);
1870 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1871 if (net_type == MES_INTERNET) {
1872 strcpy(dest_node, "internet");
1875 while (isspace(recipient[strlen(recipient) - 1]))
1876 recipient[strlen(recipient) - 1] = 0;
1878 sprintf(buf, "cit%ld", author->usernum); /* Path */
1879 msg->cm_fields['P'] = strdoop(buf);
1881 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1882 msg->cm_fields['T'] = strdoop(buf);
1884 if (fake_name[0]) /* author */
1885 msg->cm_fields['A'] = strdoop(fake_name);
1887 msg->cm_fields['A'] = strdoop(author->fullname);
1889 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1890 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1892 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1894 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1895 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1897 if (recipient[0] != 0)
1898 msg->cm_fields['R'] = strdoop(recipient);
1899 if (dest_node[0] != 0)
1900 msg->cm_fields['D'] = strdoop(dest_node);
1903 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1904 config.c_maxmsglen, NULL);
1915 * message entry - mode 0 (normal)
1917 void cmd_ent0(char *entargs)
1920 char recipient[256];
1922 int format_type = 0;
1923 char newusername[256];
1924 struct CtdlMessage *msg;
1928 struct usersupp tempUS;
1931 post = extract_int(entargs, 0);
1932 extract(recipient, entargs, 1);
1933 anon_flag = extract_int(entargs, 2);
1934 format_type = extract_int(entargs, 3);
1936 /* first check to make sure the request is valid. */
1938 if (!(CC->logged_in)) {
1939 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1942 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1943 cprintf("%d Need to be validated to enter ",
1944 ERROR + HIGHER_ACCESS_REQUIRED);
1945 cprintf("(except in %s> to sysop)\n", MAILROOM);
1948 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1949 cprintf("%d Need net privileges to enter here.\n",
1950 ERROR + HIGHER_ACCESS_REQUIRED);
1953 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1954 cprintf("%d Sorry, this is a read-only room.\n",
1955 ERROR + HIGHER_ACCESS_REQUIRED);
1962 if (CC->usersupp.axlevel < 6) {
1963 cprintf("%d You don't have permission to masquerade.\n",
1964 ERROR + HIGHER_ACCESS_REQUIRED);
1967 extract(newusername, entargs, 4);
1968 memset(CC->fake_postname, 0, 32);
1969 strcpy(CC->fake_postname, newusername);
1970 cprintf("%d Ok\n", OK);
1973 CC->cs_flags |= CS_POSTING;
1976 if (CC->quickroom.QRflags & QR_MAILBOX) {
1977 if (CC->usersupp.axlevel >= 2) {
1978 strcpy(buf, recipient);
1980 strcpy(buf, "sysop");
1981 e = alias(buf); /* alias and mail type */
1982 if ((buf[0] == 0) || (e == MES_ERROR)) {
1983 cprintf("%d Unknown address - cannot send message.\n",
1984 ERROR + NO_SUCH_USER);
1987 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1988 cprintf("%d Net privileges required for network mail.\n",
1989 ERROR + HIGHER_ACCESS_REQUIRED);
1992 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1993 && ((CC->usersupp.flags & US_INTERNET) == 0)
1994 && (!CC->internal_pgm)) {
1995 cprintf("%d You don't have access to Internet mail.\n",
1996 ERROR + HIGHER_ACCESS_REQUIRED);
1999 if (!strcasecmp(buf, "sysop")) {
2004 goto SKFALL; /* don't search local file */
2005 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2006 cprintf("%d Can't send mail to yourself!\n",
2007 ERROR + NO_SUCH_USER);
2010 /* Check to make sure the user exists; also get the correct
2011 * upper/lower casing of the name.
2013 a = getuser(&tempUS, buf);
2015 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2018 strcpy(buf, tempUS.fullname);
2021 SKFALL: b = MES_NORMAL;
2022 if (CC->quickroom.QRflags & QR_ANONONLY)
2024 if (CC->quickroom.QRflags & QR_ANONOPT) {
2028 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2031 /* If we're only checking the validity of the request, return
2032 * success without creating the message.
2035 cprintf("%d %s\n", OK, buf);
2039 cprintf("%d send message\n", SEND_LISTING);
2041 /* Read in the message from the client. */
2042 if (CC->fake_postname[0])
2043 msg = make_message(&CC->usersupp, buf,
2044 CC->quickroom.QRname, b, e, format_type,
2046 else if (CC->fake_username[0])
2047 msg = make_message(&CC->usersupp, buf,
2048 CC->quickroom.QRname, b, e, format_type,
2051 msg = make_message(&CC->usersupp, buf,
2052 CC->quickroom.QRname, b, e, format_type, "");
2055 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2056 CtdlFreeMessage(msg);
2057 CC->fake_postname[0] = '\0';
2064 * message entry - mode 3 (raw)
2066 void cmd_ent3(char *entargs)
2072 unsigned char ch, which_field;
2073 struct usersupp tempUS;
2075 struct CtdlMessage *msg;
2078 if (CC->internal_pgm == 0) {
2079 cprintf("%d This command is for internal programs only.\n",
2084 /* See if there's a recipient, but make sure it's a real one */
2085 extract(recp, entargs, 1);
2086 for (a = 0; a < strlen(recp); ++a)
2087 if (!isprint(recp[a]))
2088 strcpy(&recp[a], &recp[a + 1]);
2089 while (isspace(recp[0]))
2090 strcpy(recp, &recp[1]);
2091 while (isspace(recp[strlen(recp) - 1]))
2092 recp[strlen(recp) - 1] = 0;
2094 /* If we're in Mail, check the recipient */
2095 if (strlen(recp) > 0) {
2096 e = alias(recp); /* alias and mail type */
2097 if ((recp[0] == 0) || (e == MES_ERROR)) {
2098 cprintf("%d Unknown address - cannot send message.\n",
2099 ERROR + NO_SUCH_USER);
2102 if (e == MES_LOCAL) {
2103 a = getuser(&tempUS, recp);
2105 cprintf("%d No such user.\n",
2106 ERROR + NO_SUCH_USER);
2112 /* At this point, message has been approved. */
2113 if (extract_int(entargs, 0) == 0) {
2114 cprintf("%d OK to send\n", OK);
2118 msglen = extract_long(entargs, 2);
2119 msg = mallok(sizeof(struct CtdlMessage));
2121 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2125 memset(msg, 0, sizeof(struct CtdlMessage));
2126 tempbuf = mallok(msglen);
2127 if (tempbuf == NULL) {
2128 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2133 cprintf("%d %ld\n", SEND_BINARY, msglen);
2135 client_read(&ch, 1); /* 0xFF magic number */
2136 msg->cm_magic = CTDLMESSAGE_MAGIC;
2137 client_read(&ch, 1); /* anon type */
2138 msg->cm_anon_type = ch;
2139 client_read(&ch, 1); /* format type */
2140 msg->cm_format_type = ch;
2141 msglen = msglen - 3;
2143 while (msglen > 0) {
2144 client_read(&which_field, 1);
2145 if (!isalpha(which_field)) valid_msg = 0;
2149 client_read(&ch, 1);
2151 a = strlen(tempbuf);
2154 } while ( (ch != 0) && (msglen > 0) );
2156 msg->cm_fields[which_field] = strdoop(tempbuf);
2159 msg->cm_flags = CM_SKIP_HOOKS;
2160 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2161 CtdlFreeMessage(msg);
2167 * API function to delete messages which match a set of criteria
2168 * (returns the actual number of messages deleted)
2170 int CtdlDeleteMessages(char *room_name, /* which room */
2171 long dmsgnum, /* or "0" for any */
2172 char *content_type /* or NULL for any */
2176 struct quickroom qrbuf;
2177 struct cdbdata *cdbfr;
2178 long *msglist = NULL;
2181 int num_deleted = 0;
2183 struct SuppMsgInfo smi;
2185 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2186 room_name, dmsgnum, content_type);
2188 /* get room record, obtaining a lock... */
2189 if (lgetroom(&qrbuf, room_name) != 0) {
2190 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2192 return (0); /* room not found */
2194 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2196 if (cdbfr != NULL) {
2197 msglist = mallok(cdbfr->len);
2198 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2199 num_msgs = cdbfr->len / sizeof(long);
2203 for (i = 0; i < num_msgs; ++i) {
2206 /* Set/clear a bit for each criterion */
2208 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2209 delete_this |= 0x01;
2211 if (content_type == NULL) {
2212 delete_this |= 0x02;
2214 GetSuppMsgInfo(&smi, msglist[i]);
2215 if (!strcasecmp(smi.smi_content_type,
2217 delete_this |= 0x02;
2221 /* Delete message only if all bits are set */
2222 if (delete_this == 0x03) {
2223 AdjRefCount(msglist[i], -1);
2229 num_msgs = sort_msglist(msglist, num_msgs);
2230 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2231 msglist, (num_msgs * sizeof(long)));
2233 qrbuf.QRhighest = msglist[num_msgs - 1];
2237 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2238 return (num_deleted);
2244 * Delete message from current room
2246 void cmd_dele(char *delstr)
2251 getuser(&CC->usersupp, CC->curr_user);
2252 if ((CC->usersupp.axlevel < 6)
2253 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2254 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2255 && (!(CC->internal_pgm))) {
2256 cprintf("%d Higher access required.\n",
2257 ERROR + HIGHER_ACCESS_REQUIRED);
2260 delnum = extract_long(delstr, 0);
2262 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2265 cprintf("%d %d message%s deleted.\n", OK,
2266 num_deleted, ((num_deleted != 1) ? "s" : ""));
2268 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2274 * move or copy a message to another room
2276 void cmd_move(char *args)
2280 struct quickroom qtemp;
2284 num = extract_long(args, 0);
2285 extract(targ, args, 1);
2286 targ[ROOMNAMELEN - 1] = 0;
2287 is_copy = extract_int(args, 2);
2289 getuser(&CC->usersupp, CC->curr_user);
2290 if ((CC->usersupp.axlevel < 6)
2291 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2292 cprintf("%d Higher access required.\n",
2293 ERROR + HIGHER_ACCESS_REQUIRED);
2297 if (getroom(&qtemp, targ) != 0) {
2298 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2302 err = CtdlSaveMsgPointerInRoom(targ, num,
2303 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2305 cprintf("%d Cannot store message in %s: error %d\n",
2310 /* Now delete the message from the source room,
2311 * if this is a 'move' rather than a 'copy' operation.
2313 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2315 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2321 * GetSuppMsgInfo() - Get the supplementary record for a message
2323 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2326 struct cdbdata *cdbsmi;
2329 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2330 smibuf->smi_msgnum = msgnum;
2331 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2333 /* Use the negative of the message number for its supp record index */
2334 TheIndex = (0L - msgnum);
2336 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2337 if (cdbsmi == NULL) {
2338 return; /* record not found; go with defaults */
2340 memcpy(smibuf, cdbsmi->ptr,
2341 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2342 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2349 * PutSuppMsgInfo() - (re)write supplementary record for a message
2351 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2355 /* Use the negative of the message number for its supp record index */
2356 TheIndex = (0L - smibuf->smi_msgnum);
2358 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2359 smibuf->smi_msgnum, smibuf->smi_refcount);
2361 cdb_store(CDB_MSGMAIN,
2362 &TheIndex, sizeof(long),
2363 smibuf, sizeof(struct SuppMsgInfo));
2368 * AdjRefCount - change the reference count for a message;
2369 * delete the message if it reaches zero
2371 void AdjRefCount(long msgnum, int incr)
2374 struct SuppMsgInfo smi;
2377 /* This is a *tight* critical section; please keep it that way, as
2378 * it may get called while nested in other critical sections.
2379 * Complicating this any further will surely cause deadlock!
2381 begin_critical_section(S_SUPPMSGMAIN);
2382 GetSuppMsgInfo(&smi, msgnum);
2383 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2384 msgnum, smi.smi_refcount);
2385 smi.smi_refcount += incr;
2386 PutSuppMsgInfo(&smi);
2387 end_critical_section(S_SUPPMSGMAIN);
2388 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2389 msgnum, smi.smi_refcount);
2391 /* If the reference count is now zero, delete the message
2392 * (and its supplementary record as well).
2394 if (smi.smi_refcount == 0) {
2395 lprintf(9, "Deleting message <%ld>\n", msgnum);
2397 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2398 delnum = (0L - msgnum);
2399 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2404 * Write a generic object to this room
2406 * Note: this could be much more efficient. Right now we use two temporary
2407 * files, and still pull the message into memory as with all others.
2409 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2410 char *content_type, /* MIME type of this object */
2411 char *tempfilename, /* Where to fetch it from */
2412 struct usersupp *is_mailbox, /* Mailbox room? */
2413 int is_binary, /* Is encoding necessary? */
2414 int is_unique, /* Del others of this type? */
2415 unsigned int flags /* Internal save flags */
2420 char filename[PATH_MAX];
2423 struct quickroom qrbuf;
2424 char roomname[ROOMNAMELEN];
2425 struct CtdlMessage *msg;
2428 if (is_mailbox != NULL)
2429 MailboxName(roomname, is_mailbox, req_room);
2431 safestrncpy(roomname, req_room, sizeof(roomname));
2432 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2434 strcpy(filename, tmpnam(NULL));
2435 fp = fopen(filename, "w");
2439 tempfp = fopen(tempfilename, "r");
2440 if (tempfp == NULL) {
2446 fprintf(fp, "Content-type: %s\n", content_type);
2447 lprintf(9, "Content-type: %s\n", content_type);
2449 if (is_binary == 0) {
2450 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2451 while (ch = getc(tempfp), ch > 0)
2457 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2460 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2461 tempfilename, filename);
2465 lprintf(9, "Allocating\n");
2466 msg = mallok(sizeof(struct CtdlMessage));
2467 memset(msg, 0, sizeof(struct CtdlMessage));
2468 msg->cm_magic = CTDLMESSAGE_MAGIC;
2469 msg->cm_anon_type = MES_NORMAL;
2470 msg->cm_format_type = 4;
2471 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2472 msg->cm_fields['O'] = strdoop(req_room);
2473 msg->cm_fields['N'] = strdoop(config.c_nodename);
2474 msg->cm_fields['H'] = strdoop(config.c_humannode);
2475 msg->cm_flags = flags;
2477 lprintf(9, "Loading\n");
2478 fp = fopen(filename, "rb");
2479 fseek(fp, 0L, SEEK_END);
2482 msg->cm_fields['M'] = mallok(len);
2483 fread(msg->cm_fields['M'], len, 1, fp);
2487 /* Create the requested room if we have to. */
2488 if (getroom(&qrbuf, roomname) != 0) {
2489 create_room(roomname,
2490 ( (is_mailbox != NULL) ? 4 : 3 ),
2493 /* If the caller specified this object as unique, delete all
2494 * other objects of this type that are currently in the room.
2497 lprintf(9, "Deleted %d other msgs of this type\n",
2498 CtdlDeleteMessages(roomname, 0L, content_type));
2500 /* Now write the data */
2501 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2502 CtdlFreeMessage(msg);
2510 void CtdlGetSysConfigBackend(long msgnum) {
2511 config_msgnum = msgnum;
2515 char *CtdlGetSysConfig(char *sysconfname) {
2516 char hold_rm[ROOMNAMELEN];
2519 struct CtdlMessage *msg;
2522 strcpy(hold_rm, CC->quickroom.QRname);
2523 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2524 getroom(&CC->quickroom, hold_rm);
2529 /* We want the last (and probably only) config in this room */
2530 begin_critical_section(S_CONFIG);
2531 config_msgnum = (-1L);
2532 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2533 CtdlGetSysConfigBackend);
2534 msgnum = config_msgnum;
2535 end_critical_section(S_CONFIG);
2541 msg = CtdlFetchMessage(msgnum);
2543 conf = strdoop(msg->cm_fields['M']);
2544 CtdlFreeMessage(msg);
2551 getroom(&CC->quickroom, hold_rm);
2553 lprintf(9, "eggstracting...\n");
2554 if (conf != NULL) do {
2555 extract_token(buf, conf, 0, '\n');
2556 lprintf(9, "eggstracted <%s>\n", buf);
2557 strcpy(conf, &conf[strlen(buf)+1]);
2558 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2563 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2564 char temp[PATH_MAX];
2567 strcpy(temp, tmpnam(NULL));
2569 fp = fopen(temp, "w");
2570 if (fp == NULL) return;
2571 fprintf(fp, "%s", sysconfdata);
2574 /* this handy API function does all the work for us */
2575 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);