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 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;
1498 lprintf(9, "CtdlSaveMsg() called\n");
1499 if (is_valid_message(msg) == 0) return(-1); /* self check */
1501 /* If this message has no timestamp, we take the liberty of
1502 * giving it one, right now.
1504 if (msg->cm_fields['T'] == NULL) {
1505 lprintf(9, "Generating timestamp\n");
1506 sprintf(aaa, "%ld", time(NULL));
1507 msg->cm_fields['T'] = strdoop(aaa);
1510 /* If this message has no path, we generate one.
1512 if (msg->cm_fields['P'] == NULL) {
1513 lprintf(9, "Generating path\n");
1514 if (msg->cm_fields['A'] != NULL) {
1515 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1516 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1517 if (isspace(msg->cm_fields['P'][a])) {
1518 msg->cm_fields['P'][a] = ' ';
1523 msg->cm_fields['P'] = strdoop("unknown");
1527 strcpy(force_room, force);
1529 /* Strip non-printable characters out of the recipient name */
1530 lprintf(9, "Checking recipient (if present)\n");
1531 strcpy(recipient, rec);
1532 for (a = 0; a < strlen(recipient); ++a)
1533 if (!isprint(recipient[a]))
1534 strcpy(&recipient[a], &recipient[a + 1]);
1536 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1537 for (a=0; a<strlen(recipient); ++a) {
1538 if (recipient[a] == '@') {
1539 if (CtdlHostAlias(&recipient[a+1])
1540 == hostalias_localhost) {
1542 lprintf(7, "Changed to <%s>\n", recipient);
1547 lprintf(9, "Recipient is <%s>\n", recipient);
1549 /* Learn about what's inside, because it's what's inside that counts */
1550 lprintf(9, "Learning what's inside\n");
1551 if (msg->cm_fields['M'] == NULL) {
1552 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1555 switch (msg->cm_format_type) {
1557 strcpy(content_type, "text/x-citadel-variformat");
1560 strcpy(content_type, "text/plain");
1563 strcpy(content_type, "text/plain");
1564 /* advance past header fields */
1565 mptr = msg->cm_fields['M'];
1568 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1569 safestrncpy(content_type, mptr,
1570 sizeof(content_type));
1571 strcpy(content_type, &content_type[14]);
1572 for (a = 0; a < strlen(content_type); ++a)
1573 if ((content_type[a] == ';')
1574 || (content_type[a] == ' ')
1575 || (content_type[a] == 13)
1576 || (content_type[a] == 10))
1577 content_type[a] = 0;
1584 /* Goto the correct room */
1585 lprintf(9, "Switching rooms\n");
1586 strcpy(hold_rm, CC->quickroom.QRname);
1587 strcpy(actual_rm, CC->quickroom.QRname);
1589 /* If the user is a twit, move to the twit room for posting */
1590 lprintf(9, "Handling twit stuff\n");
1592 if (CC->usersupp.axlevel == 2) {
1593 strcpy(hold_rm, actual_rm);
1594 strcpy(actual_rm, config.c_twitroom);
1598 /* ...or if this message is destined for Aide> then go there. */
1599 if (strlen(force_room) > 0) {
1600 strcpy(actual_rm, force_room);
1603 lprintf(9, "Possibly relocating\n");
1604 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1605 getroom(&CC->quickroom, actual_rm);
1609 * If this message has no O (room) field, generate one.
1611 if (msg->cm_fields['O'] == NULL) {
1612 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1615 /* Perform "before save" hooks (aborting if any return nonzero) */
1616 lprintf(9, "Performing before-save hooks\n");
1617 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1619 /* If this message has an Extended ID, perform replication checks */
1620 lprintf(9, "Performing replication checks\n");
1621 if (ReplicationChecks(msg) > 0) return(-1);
1623 /* Network mail - send a copy to the network program. */
1624 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1625 lprintf(9, "Sending network spool\n");
1626 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1627 (long) getpid(), CC->cs_pid, ++seqnum);
1628 lprintf(9, "Saving a copy to %s\n", aaa);
1629 network_fp = fopen(aaa, "ab+");
1630 if (network_fp == NULL)
1631 lprintf(2, "ERROR: %s\n", strerror(errno));
1634 /* Save it to disk */
1635 lprintf(9, "Saving to disk\n");
1636 newmsgid = send_message(msg, generate_id, network_fp);
1637 if (network_fp != NULL) {
1639 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1642 if (newmsgid <= 0L) return(-1);
1644 /* Write a supplemental message info record. This doesn't have to
1645 * be a critical section because nobody else knows about this message
1648 lprintf(9, "Creating SuppMsgInfo record\n");
1649 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1650 smi.smi_msgnum = newmsgid;
1651 smi.smi_refcount = 0;
1652 safestrncpy(smi.smi_content_type, content_type, 64);
1653 PutSuppMsgInfo(&smi);
1655 /* Now figure out where to store the pointers */
1656 lprintf(9, "Storing pointers\n");
1658 /* If this is being done by the networker delivering a private
1659 * message, we want to BYPASS saving the sender's copy (because there
1660 * is no local sender; it would otherwise go to the Trashcan).
1662 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1663 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1666 /* For internet mail, drop a copy in the outbound queue room */
1667 if (mailtype == MES_INTERNET) {
1668 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1671 /* Bump this user's messages posted counter. */
1672 lprintf(9, "Updating user\n");
1673 lgetuser(&CC->usersupp, CC->curr_user);
1674 CC->usersupp.posted = CC->usersupp.posted + 1;
1675 lputuser(&CC->usersupp);
1677 /* If this is private, local mail, make a copy in the
1678 * recipient's mailbox and bump the reference count.
1680 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1681 if (getuser(&userbuf, recipient) == 0) {
1682 lprintf(9, "Delivering private mail\n");
1683 MailboxName(actual_rm, &userbuf, MAILROOM);
1684 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1688 /* Perform "after save" hooks */
1689 lprintf(9, "Performing after-save hooks\n");
1690 PerformMessageHooks(msg, EVT_AFTERSAVE);
1693 lprintf(9, "Returning to original room\n");
1694 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1695 getroom(&CC->quickroom, hold_rm);
1697 /* For internet mail, generate delivery instructions
1698 * (Yes, this is recursive! Deal with it!)
1700 if (mailtype == MES_INTERNET) {
1701 lprintf(9, "Generating delivery instructions\n");
1702 instr = mallok(2048);
1704 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1707 SPOOLMIME, newmsgid, time(NULL),
1708 msg->cm_fields['A'], msg->cm_fields['N'],
1711 imsg = mallok(sizeof(struct CtdlMessage));
1712 memset(imsg, 0, sizeof(struct CtdlMessage));
1713 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1714 imsg->cm_anon_type = MES_NORMAL;
1715 imsg->cm_format_type = FMT_RFC822;
1716 imsg->cm_fields['A'] = strdoop("Citadel");
1717 imsg->cm_fields['M'] = instr;
1718 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1719 CtdlFreeMessage(imsg);
1728 * Convenience function for generating small administrative messages.
1730 void quickie_message(char *from, char *to, char *room, char *text)
1732 struct CtdlMessage *msg;
1734 msg = mallok(sizeof(struct CtdlMessage));
1735 memset(msg, 0, sizeof(struct CtdlMessage));
1736 msg->cm_magic = CTDLMESSAGE_MAGIC;
1737 msg->cm_anon_type = MES_NORMAL;
1738 msg->cm_format_type = 0;
1739 msg->cm_fields['A'] = strdoop(from);
1740 msg->cm_fields['O'] = strdoop(room);
1741 msg->cm_fields['N'] = strdoop(NODENAME);
1743 msg->cm_fields['R'] = strdoop(to);
1744 msg->cm_fields['M'] = strdoop(text);
1746 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1747 CtdlFreeMessage(msg);
1748 syslog(LOG_NOTICE, text);
1754 * Back end function used by make_message() and similar functions
1756 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1757 size_t maxlen, /* maximum message length */
1758 char *exist /* if non-null, append to it;
1759 exist is ALWAYS freed */
1762 size_t message_len = 0;
1763 size_t buffer_len = 0;
1767 if (exist == NULL) {
1771 m = reallok(exist, strlen(exist) + 4096);
1772 if (m == NULL) phree(exist);
1775 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1782 /* read in the lines of message text one by one */
1784 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1786 /* augment the buffer if we have to */
1787 if ((message_len + strlen(buf) + 2) > buffer_len) {
1788 lprintf(9, "realloking\n");
1789 ptr = reallok(m, (buffer_len * 2) );
1790 if (ptr == NULL) { /* flush if can't allocate */
1791 while ( (client_gets(buf)>0) &&
1792 strcmp(buf, terminator)) ;;
1795 buffer_len = (buffer_len * 2);
1798 lprintf(9, "buffer_len is %d\n", buffer_len);
1802 if (append == NULL) append = m;
1803 while (strlen(append) > 0) ++append;
1804 strcpy(append, buf);
1805 strcat(append, "\n");
1806 message_len = message_len + strlen(buf) + 1;
1808 /* if we've hit the max msg length, flush the rest */
1809 if (message_len >= maxlen) {
1810 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1821 * Build a binary message to be saved on disk.
1824 struct CtdlMessage *make_message(
1825 struct usersupp *author, /* author's usersupp structure */
1826 char *recipient, /* NULL if it's not mail */
1827 char *room, /* room where it's going */
1828 int type, /* see MES_ types in header file */
1829 int net_type, /* see MES_ types in header file */
1830 int format_type, /* local or remote (see citadel.h) */
1831 char *fake_name) /* who we're masquerading as */
1837 struct CtdlMessage *msg;
1839 msg = mallok(sizeof(struct CtdlMessage));
1840 memset(msg, 0, sizeof(struct CtdlMessage));
1841 msg->cm_magic = CTDLMESSAGE_MAGIC;
1842 msg->cm_anon_type = type;
1843 msg->cm_format_type = format_type;
1845 /* Don't confuse the poor folks if it's not routed mail. */
1846 strcpy(dest_node, "");
1848 /* If net_type is MES_BINARY, split out the destination node. */
1849 if (net_type == MES_BINARY) {
1850 strcpy(dest_node, NODENAME);
1851 for (a = 0; a < strlen(recipient); ++a) {
1852 if (recipient[a] == '@') {
1854 strcpy(dest_node, &recipient[a + 1]);
1859 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1860 if (net_type == MES_INTERNET) {
1861 strcpy(dest_node, "internet");
1864 while (isspace(recipient[strlen(recipient) - 1]))
1865 recipient[strlen(recipient) - 1] = 0;
1867 sprintf(buf, "cit%ld", author->usernum); /* Path */
1868 msg->cm_fields['P'] = strdoop(buf);
1870 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1871 msg->cm_fields['T'] = strdoop(buf);
1873 if (fake_name[0]) /* author */
1874 msg->cm_fields['A'] = strdoop(fake_name);
1876 msg->cm_fields['A'] = strdoop(author->fullname);
1878 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1879 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1881 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1883 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1884 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1886 if (recipient[0] != 0)
1887 msg->cm_fields['R'] = strdoop(recipient);
1888 if (dest_node[0] != 0)
1889 msg->cm_fields['D'] = strdoop(dest_node);
1892 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1893 config.c_maxmsglen, NULL);
1904 * message entry - mode 0 (normal)
1906 void cmd_ent0(char *entargs)
1909 char recipient[256];
1911 int format_type = 0;
1912 char newusername[256];
1913 struct CtdlMessage *msg;
1917 struct usersupp tempUS;
1920 post = extract_int(entargs, 0);
1921 extract(recipient, entargs, 1);
1922 anon_flag = extract_int(entargs, 2);
1923 format_type = extract_int(entargs, 3);
1925 /* first check to make sure the request is valid. */
1927 if (!(CC->logged_in)) {
1928 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1931 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1932 cprintf("%d Need to be validated to enter ",
1933 ERROR + HIGHER_ACCESS_REQUIRED);
1934 cprintf("(except in %s> to sysop)\n", MAILROOM);
1937 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1938 cprintf("%d Need net privileges to enter here.\n",
1939 ERROR + HIGHER_ACCESS_REQUIRED);
1942 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1943 cprintf("%d Sorry, this is a read-only room.\n",
1944 ERROR + HIGHER_ACCESS_REQUIRED);
1951 if (CC->usersupp.axlevel < 6) {
1952 cprintf("%d You don't have permission to masquerade.\n",
1953 ERROR + HIGHER_ACCESS_REQUIRED);
1956 extract(newusername, entargs, 4);
1957 memset(CC->fake_postname, 0, 32);
1958 strcpy(CC->fake_postname, newusername);
1959 cprintf("%d Ok\n", OK);
1962 CC->cs_flags |= CS_POSTING;
1965 if (CC->quickroom.QRflags & QR_MAILBOX) {
1966 if (CC->usersupp.axlevel >= 2) {
1967 strcpy(buf, recipient);
1969 strcpy(buf, "sysop");
1970 e = alias(buf); /* alias and mail type */
1971 if ((buf[0] == 0) || (e == MES_ERROR)) {
1972 cprintf("%d Unknown address - cannot send message.\n",
1973 ERROR + NO_SUCH_USER);
1976 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1977 cprintf("%d Net privileges required for network mail.\n",
1978 ERROR + HIGHER_ACCESS_REQUIRED);
1981 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1982 && ((CC->usersupp.flags & US_INTERNET) == 0)
1983 && (!CC->internal_pgm)) {
1984 cprintf("%d You don't have access to Internet mail.\n",
1985 ERROR + HIGHER_ACCESS_REQUIRED);
1988 if (!strcasecmp(buf, "sysop")) {
1993 goto SKFALL; /* don't search local file */
1994 if (!strcasecmp(buf, CC->usersupp.fullname)) {
1995 cprintf("%d Can't send mail to yourself!\n",
1996 ERROR + NO_SUCH_USER);
1999 /* Check to make sure the user exists; also get the correct
2000 * upper/lower casing of the name.
2002 a = getuser(&tempUS, buf);
2004 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2007 strcpy(buf, tempUS.fullname);
2010 SKFALL: b = MES_NORMAL;
2011 if (CC->quickroom.QRflags & QR_ANONONLY)
2013 if (CC->quickroom.QRflags & QR_ANONOPT) {
2017 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2020 /* If we're only checking the validity of the request, return
2021 * success without creating the message.
2024 cprintf("%d %s\n", OK, buf);
2028 cprintf("%d send message\n", SEND_LISTING);
2030 /* Read in the message from the client. */
2031 if (CC->fake_postname[0])
2032 msg = make_message(&CC->usersupp, buf,
2033 CC->quickroom.QRname, b, e, format_type,
2035 else if (CC->fake_username[0])
2036 msg = make_message(&CC->usersupp, buf,
2037 CC->quickroom.QRname, b, e, format_type,
2040 msg = make_message(&CC->usersupp, buf,
2041 CC->quickroom.QRname, b, e, format_type, "");
2044 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2045 CtdlFreeMessage(msg);
2046 CC->fake_postname[0] = '\0';
2053 * message entry - mode 3 (raw)
2055 void cmd_ent3(char *entargs)
2061 unsigned char ch, which_field;
2062 struct usersupp tempUS;
2064 struct CtdlMessage *msg;
2067 if (CC->internal_pgm == 0) {
2068 cprintf("%d This command is for internal programs only.\n",
2073 /* See if there's a recipient, but make sure it's a real one */
2074 extract(recp, entargs, 1);
2075 for (a = 0; a < strlen(recp); ++a)
2076 if (!isprint(recp[a]))
2077 strcpy(&recp[a], &recp[a + 1]);
2078 while (isspace(recp[0]))
2079 strcpy(recp, &recp[1]);
2080 while (isspace(recp[strlen(recp) - 1]))
2081 recp[strlen(recp) - 1] = 0;
2083 /* If we're in Mail, check the recipient */
2084 if (strlen(recp) > 0) {
2085 e = alias(recp); /* alias and mail type */
2086 if ((recp[0] == 0) || (e == MES_ERROR)) {
2087 cprintf("%d Unknown address - cannot send message.\n",
2088 ERROR + NO_SUCH_USER);
2091 if (e == MES_LOCAL) {
2092 a = getuser(&tempUS, recp);
2094 cprintf("%d No such user.\n",
2095 ERROR + NO_SUCH_USER);
2101 /* At this point, message has been approved. */
2102 if (extract_int(entargs, 0) == 0) {
2103 cprintf("%d OK to send\n", OK);
2107 msglen = extract_long(entargs, 2);
2108 msg = mallok(sizeof(struct CtdlMessage));
2110 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2114 memset(msg, 0, sizeof(struct CtdlMessage));
2115 tempbuf = mallok(msglen);
2116 if (tempbuf == NULL) {
2117 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2122 cprintf("%d %ld\n", SEND_BINARY, msglen);
2124 client_read(&ch, 1); /* 0xFF magic number */
2125 msg->cm_magic = CTDLMESSAGE_MAGIC;
2126 client_read(&ch, 1); /* anon type */
2127 msg->cm_anon_type = ch;
2128 client_read(&ch, 1); /* format type */
2129 msg->cm_format_type = ch;
2130 msglen = msglen - 3;
2132 while (msglen > 0) {
2133 client_read(&which_field, 1);
2134 if (!isalpha(which_field)) valid_msg = 0;
2138 client_read(&ch, 1);
2140 a = strlen(tempbuf);
2143 } while ( (ch != 0) && (msglen > 0) );
2145 msg->cm_fields[which_field] = strdoop(tempbuf);
2148 msg->cm_flags = CM_SKIP_HOOKS;
2149 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2150 CtdlFreeMessage(msg);
2156 * API function to delete messages which match a set of criteria
2157 * (returns the actual number of messages deleted)
2159 int CtdlDeleteMessages(char *room_name, /* which room */
2160 long dmsgnum, /* or "0" for any */
2161 char *content_type /* or NULL for any */
2165 struct quickroom qrbuf;
2166 struct cdbdata *cdbfr;
2167 long *msglist = NULL;
2170 int num_deleted = 0;
2172 struct SuppMsgInfo smi;
2174 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2175 room_name, dmsgnum, content_type);
2177 /* get room record, obtaining a lock... */
2178 if (lgetroom(&qrbuf, room_name) != 0) {
2179 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2181 return (0); /* room not found */
2183 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2185 if (cdbfr != NULL) {
2186 msglist = mallok(cdbfr->len);
2187 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2188 num_msgs = cdbfr->len / sizeof(long);
2192 for (i = 0; i < num_msgs; ++i) {
2195 /* Set/clear a bit for each criterion */
2197 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2198 delete_this |= 0x01;
2200 if (content_type == NULL) {
2201 delete_this |= 0x02;
2203 GetSuppMsgInfo(&smi, msglist[i]);
2204 if (!strcasecmp(smi.smi_content_type,
2206 delete_this |= 0x02;
2210 /* Delete message only if all bits are set */
2211 if (delete_this == 0x03) {
2212 AdjRefCount(msglist[i], -1);
2218 num_msgs = sort_msglist(msglist, num_msgs);
2219 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2220 msglist, (num_msgs * sizeof(long)));
2222 qrbuf.QRhighest = msglist[num_msgs - 1];
2226 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2227 return (num_deleted);
2233 * Delete message from current room
2235 void cmd_dele(char *delstr)
2240 getuser(&CC->usersupp, CC->curr_user);
2241 if ((CC->usersupp.axlevel < 6)
2242 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2243 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2244 && (!(CC->internal_pgm))) {
2245 cprintf("%d Higher access required.\n",
2246 ERROR + HIGHER_ACCESS_REQUIRED);
2249 delnum = extract_long(delstr, 0);
2251 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2254 cprintf("%d %d message%s deleted.\n", OK,
2255 num_deleted, ((num_deleted != 1) ? "s" : ""));
2257 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2263 * move or copy a message to another room
2265 void cmd_move(char *args)
2269 struct quickroom qtemp;
2273 num = extract_long(args, 0);
2274 extract(targ, args, 1);
2275 targ[ROOMNAMELEN - 1] = 0;
2276 is_copy = extract_int(args, 2);
2278 getuser(&CC->usersupp, CC->curr_user);
2279 if ((CC->usersupp.axlevel < 6)
2280 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2281 cprintf("%d Higher access required.\n",
2282 ERROR + HIGHER_ACCESS_REQUIRED);
2286 if (getroom(&qtemp, targ) != 0) {
2287 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2291 err = CtdlSaveMsgPointerInRoom(targ, num,
2292 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2294 cprintf("%d Cannot store message in %s: error %d\n",
2299 /* Now delete the message from the source room,
2300 * if this is a 'move' rather than a 'copy' operation.
2302 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2304 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2310 * GetSuppMsgInfo() - Get the supplementary record for a message
2312 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2315 struct cdbdata *cdbsmi;
2318 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2319 smibuf->smi_msgnum = msgnum;
2320 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2322 /* Use the negative of the message number for its supp record index */
2323 TheIndex = (0L - msgnum);
2325 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2326 if (cdbsmi == NULL) {
2327 return; /* record not found; go with defaults */
2329 memcpy(smibuf, cdbsmi->ptr,
2330 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2331 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2338 * PutSuppMsgInfo() - (re)write supplementary record for a message
2340 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2344 /* Use the negative of the message number for its supp record index */
2345 TheIndex = (0L - smibuf->smi_msgnum);
2347 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2348 smibuf->smi_msgnum, smibuf->smi_refcount);
2350 cdb_store(CDB_MSGMAIN,
2351 &TheIndex, sizeof(long),
2352 smibuf, sizeof(struct SuppMsgInfo));
2357 * AdjRefCount - change the reference count for a message;
2358 * delete the message if it reaches zero
2360 void AdjRefCount(long msgnum, int incr)
2363 struct SuppMsgInfo smi;
2366 /* This is a *tight* critical section; please keep it that way, as
2367 * it may get called while nested in other critical sections.
2368 * Complicating this any further will surely cause deadlock!
2370 begin_critical_section(S_SUPPMSGMAIN);
2371 GetSuppMsgInfo(&smi, msgnum);
2372 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2373 msgnum, smi.smi_refcount);
2374 smi.smi_refcount += incr;
2375 PutSuppMsgInfo(&smi);
2376 end_critical_section(S_SUPPMSGMAIN);
2377 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2378 msgnum, smi.smi_refcount);
2380 /* If the reference count is now zero, delete the message
2381 * (and its supplementary record as well).
2383 if (smi.smi_refcount == 0) {
2384 lprintf(9, "Deleting message <%ld>\n", msgnum);
2386 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2387 delnum = (0L - msgnum);
2388 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2393 * Write a generic object to this room
2395 * Note: this could be much more efficient. Right now we use two temporary
2396 * files, and still pull the message into memory as with all others.
2398 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2399 char *content_type, /* MIME type of this object */
2400 char *tempfilename, /* Where to fetch it from */
2401 struct usersupp *is_mailbox, /* Mailbox room? */
2402 int is_binary, /* Is encoding necessary? */
2403 int is_unique, /* Del others of this type? */
2404 unsigned int flags /* Internal save flags */
2409 char filename[PATH_MAX];
2412 struct quickroom qrbuf;
2413 char roomname[ROOMNAMELEN];
2414 struct CtdlMessage *msg;
2417 if (is_mailbox != NULL)
2418 MailboxName(roomname, is_mailbox, req_room);
2420 safestrncpy(roomname, req_room, sizeof(roomname));
2421 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2423 strcpy(filename, tmpnam(NULL));
2424 fp = fopen(filename, "w");
2428 tempfp = fopen(tempfilename, "r");
2429 if (tempfp == NULL) {
2435 fprintf(fp, "Content-type: %s\n", content_type);
2436 lprintf(9, "Content-type: %s\n", content_type);
2438 if (is_binary == 0) {
2439 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2440 while (ch = getc(tempfp), ch > 0)
2446 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2449 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2450 tempfilename, filename);
2454 lprintf(9, "Allocating\n");
2455 msg = mallok(sizeof(struct CtdlMessage));
2456 memset(msg, 0, sizeof(struct CtdlMessage));
2457 msg->cm_magic = CTDLMESSAGE_MAGIC;
2458 msg->cm_anon_type = MES_NORMAL;
2459 msg->cm_format_type = 4;
2460 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2461 msg->cm_fields['O'] = strdoop(req_room);
2462 msg->cm_fields['N'] = strdoop(config.c_nodename);
2463 msg->cm_fields['H'] = strdoop(config.c_humannode);
2464 msg->cm_flags = flags;
2466 lprintf(9, "Loading\n");
2467 fp = fopen(filename, "rb");
2468 fseek(fp, 0L, SEEK_END);
2471 msg->cm_fields['M'] = mallok(len);
2472 fread(msg->cm_fields['M'], len, 1, fp);
2476 /* Create the requested room if we have to. */
2477 if (getroom(&qrbuf, roomname) != 0) {
2478 create_room(roomname,
2479 ( (is_mailbox != NULL) ? 4 : 3 ),
2482 /* If the caller specified this object as unique, delete all
2483 * other objects of this type that are currently in the room.
2486 lprintf(9, "Deleted %d other msgs of this type\n",
2487 CtdlDeleteMessages(roomname, 0L, content_type));
2489 /* Now write the data */
2490 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2491 CtdlFreeMessage(msg);
2499 void CtdlGetSysConfigBackend(long msgnum) {
2500 config_msgnum = msgnum;
2504 char *CtdlGetSysConfig(char *sysconfname) {
2505 char hold_rm[ROOMNAMELEN];
2508 struct CtdlMessage *msg;
2511 strcpy(hold_rm, CC->quickroom.QRname);
2512 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2513 getroom(&CC->quickroom, hold_rm);
2518 /* We want the last (and probably only) config in this room */
2519 begin_critical_section(S_CONFIG);
2520 config_msgnum = (-1L);
2521 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2522 CtdlGetSysConfigBackend);
2523 msgnum = config_msgnum;
2524 end_critical_section(S_CONFIG);
2530 msg = CtdlFetchMessage(msgnum);
2532 conf = strdoop(msg->cm_fields['M']);
2533 CtdlFreeMessage(msg);
2540 getroom(&CC->quickroom, hold_rm);
2542 lprintf(9, "eggstracting...\n");
2543 if (conf != NULL) do {
2544 extract_token(buf, conf, 0, '\n');
2545 lprintf(9, "eggstracted <%s>\n", buf);
2546 strcpy(conf, &conf[strlen(buf)+1]);
2547 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2552 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2553 char temp[PATH_MAX];
2556 strcpy(temp, tmpnam(NULL));
2558 fp = fopen(temp, "w");
2559 if (fp == NULL) return;
2560 fprintf(fp, "%s", sysconfdata);
2563 /* this handy API function does all the work for us */
2564 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);