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 lprintf(9, "returning MES_INTERNET\n");
191 return (MES_INTERNET);
193 if (!strncmp(aaa, "bin", 3)) {
196 while (aaa[strlen(aaa) - 1] != '@')
197 aaa[strlen(aaa) - 1] = 0;
198 aaa[strlen(aaa) - 1] = 0;
199 while (aaa[strlen(aaa) - 1] == ' ')
200 aaa[strlen(aaa) - 1] = 0;
201 while (bbb[0] != '@')
202 strcpy(bbb, &bbb[1]);
203 strcpy(bbb, &bbb[1]);
204 while (bbb[0] == ' ')
205 strcpy(bbb, &bbb[1]);
206 sprintf(name, "%s @%s", aaa, bbb);
207 lprintf(9, "returning MES_BINARY\n");
212 lprintf(9, "returning MES_LOCAL\n");
221 fp = fopen("citadel.control", "r");
222 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
228 void simple_listing(long msgnum)
230 cprintf("%ld\n", msgnum);
235 /* Determine if a given message matches the fields in a message template.
236 * Return 0 for a successful match.
238 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
241 /* If there aren't any fields in the template, all messages will
244 if (template == NULL) return(0);
246 /* Null messages are bogus. */
247 if (msg == NULL) return(1);
249 for (i='A'; i<='Z'; ++i) {
250 if (template->cm_fields[i] != NULL) {
251 if (msg->cm_fields[i] == NULL) {
254 if (strcasecmp(msg->cm_fields[i],
255 template->cm_fields[i])) return 1;
259 /* All compares succeeded: we have a match! */
267 * API function to perform an operation for each qualifying message in the
270 void CtdlForEachMessage(int mode, long ref,
272 struct CtdlMessage *compare,
273 void (*CallBack) (long msgnum))
278 struct cdbdata *cdbfr;
279 long *msglist = NULL;
282 struct SuppMsgInfo smi;
283 struct CtdlMessage *msg;
285 /* Learn about the user and room in question */
287 getuser(&CC->usersupp, CC->curr_user);
288 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
290 /* Load the message list */
291 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
293 msglist = mallok(cdbfr->len);
294 memcpy(msglist, cdbfr->ptr, cdbfr->len);
295 num_msgs = cdbfr->len / sizeof(long);
298 return; /* No messages at all? No further action. */
302 /* If the caller is looking for a specific MIME type, then filter
303 * out all messages which are not of the type requested.
306 if (content_type != NULL)
307 if (strlen(content_type) > 0)
308 for (a = 0; a < num_msgs; ++a) {
309 GetSuppMsgInfo(&smi, msglist[a]);
310 if (strcasecmp(smi.smi_content_type, content_type)) {
315 num_msgs = sort_msglist(msglist, num_msgs);
317 /* If a template was supplied, filter out the messages which
318 * don't match. (This could induce some delays!)
321 if (compare != NULL) {
322 for (a = 0; a < num_msgs; ++a) {
323 msg = CtdlFetchMessage(msglist[a]);
325 if (CtdlMsgCmp(msg, compare)) {
328 CtdlFreeMessage(msg);
336 * Now iterate through the message list, according to the
337 * criteria supplied by the caller.
340 for (a = 0; a < num_msgs; ++a) {
341 thismsg = msglist[a];
346 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
347 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
348 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
349 && (CC->usersupp.flags & US_LASTOLD))
350 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
351 || ((mode == MSGS_FIRST) && (a < ref))
352 || ((mode == MSGS_GT) && (thismsg > ref))
358 phree(msglist); /* Clean up */
364 * cmd_msgs() - get list of message #'s in this room
365 * implements the MSGS server command using CtdlForEachMessage()
367 void cmd_msgs(char *cmdbuf)
376 int with_template = 0;
377 struct CtdlMessage *template = NULL;
379 extract(which, cmdbuf, 0);
380 cm_ref = extract_int(cmdbuf, 1);
381 with_template = extract_int(cmdbuf, 2);
385 if (!strncasecmp(which, "OLD", 3))
387 else if (!strncasecmp(which, "NEW", 3))
389 else if (!strncasecmp(which, "FIRST", 5))
391 else if (!strncasecmp(which, "LAST", 4))
393 else if (!strncasecmp(which, "GT", 2))
396 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
397 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
402 cprintf("%d Send template then receive message list\n",
404 template = (struct CtdlMessage *)
405 mallok(sizeof(struct CtdlMessage));
406 memset(template, 0, sizeof(struct CtdlMessage));
407 while(client_gets(buf), strcmp(buf,"000")) {
408 extract(tfield, buf, 0);
409 extract(tvalue, buf, 1);
410 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
411 if (!strcasecmp(tfield, msgkeys[i])) {
412 template->cm_fields[i] =
419 cprintf("%d Message list...\n", LISTING_FOLLOWS);
422 CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing);
423 if (template != NULL) CtdlFreeMessage(template);
431 * help_subst() - support routine for help file viewer
433 void help_subst(char *strbuf, char *source, char *dest)
438 while (p = pattern2(strbuf, source), (p >= 0)) {
439 strcpy(workbuf, &strbuf[p + strlen(source)]);
440 strcpy(&strbuf[p], dest);
441 strcat(strbuf, workbuf);
446 void do_help_subst(char *buffer)
450 help_subst(buffer, "^nodename", config.c_nodename);
451 help_subst(buffer, "^humannode", config.c_humannode);
452 help_subst(buffer, "^fqdn", config.c_fqdn);
453 help_subst(buffer, "^username", CC->usersupp.fullname);
454 sprintf(buf2, "%ld", CC->usersupp.usernum);
455 help_subst(buffer, "^usernum", buf2);
456 help_subst(buffer, "^sysadm", config.c_sysadm);
457 help_subst(buffer, "^variantname", CITADEL);
458 sprintf(buf2, "%d", config.c_maxsessions);
459 help_subst(buffer, "^maxsessions", buf2);
465 * memfmout() - Citadel text formatter and paginator.
466 * Although the original purpose of this routine was to format
467 * text to the reader's screen width, all we're really using it
468 * for here is to format text out to 80 columns before sending it
469 * to the client. The client software may reformat it again.
472 int width, /* screen width to use */
473 char *mptr, /* where are we going to get our text from? */
474 char subst, /* nonzero if we should do substitutions */
475 char *nl) /* string to terminate lines with */
487 c = 1; /* c is the current pos */
490 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
492 buffer[strlen(buffer) + 1] = 0;
493 buffer[strlen(buffer)] = ch;
496 if (buffer[0] == '^')
497 do_help_subst(buffer);
499 buffer[strlen(buffer) + 1] = 0;
501 strcpy(buffer, &buffer[1]);
511 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
513 if (((old == 13) || (old == 10)) && (isspace(real))) {
521 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
522 cprintf("%s%s", nl, aaa);
531 if ((strlen(aaa) + c) > (width - 5)) {
541 if ((ch == 13) || (ch == 10)) {
542 cprintf("%s%s", aaa, nl);
549 FMTEND: cprintf("%s%s", aaa, nl);
555 * Callback function for mime parser that simply lists the part
557 void list_this_part(char *name, char *filename, char *partnum, char *disp,
558 void *content, char *cbtype, size_t length)
561 cprintf("part=%s|%s|%s|%s|%s|%d\n",
562 name, filename, partnum, disp, cbtype, length);
567 * Callback function for mime parser that opens a section for downloading
569 void mime_download(char *name, char *filename, char *partnum, char *disp,
570 void *content, char *cbtype, size_t length)
573 /* Silently go away if there's already a download open... */
574 if (CC->download_fp != NULL)
577 /* ...or if this is not the desired section */
578 if (strcasecmp(desired_section, partnum))
581 CC->download_fp = tmpfile();
582 if (CC->download_fp == NULL)
585 fwrite(content, length, 1, CC->download_fp);
586 fflush(CC->download_fp);
587 rewind(CC->download_fp);
589 OpenCmdResult(filename, cbtype);
595 * Load a message from disk into memory.
596 * This is used by CtdlOutputMsg() and other fetch functions.
598 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
599 * using the CtdlMessageFree() function.
601 struct CtdlMessage *CtdlFetchMessage(long msgnum)
603 struct cdbdata *dmsgtext;
604 struct CtdlMessage *ret = NULL;
607 CIT_UBYTE field_header;
610 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
611 if (dmsgtext == NULL) {
614 mptr = dmsgtext->ptr;
616 /* Parse the three bytes that begin EVERY message on disk.
617 * The first is always 0xFF, the on-disk magic number.
618 * The second is the anonymous/public type byte.
619 * The third is the format type byte (vari, fixed, or MIME).
623 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
627 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
628 memset(ret, 0, sizeof(struct CtdlMessage));
630 ret->cm_magic = CTDLMESSAGE_MAGIC;
631 ret->cm_anon_type = *mptr++; /* Anon type byte */
632 ret->cm_format_type = *mptr++; /* Format type byte */
635 * The rest is zero or more arbitrary fields. Load them in.
636 * We're done when we encounter either a zero-length field or
637 * have just processed the 'M' (message text) field.
640 field_length = strlen(mptr);
641 if (field_length == 0)
643 field_header = *mptr++;
644 ret->cm_fields[field_header] = mallok(field_length);
645 strcpy(ret->cm_fields[field_header], mptr);
647 while (*mptr++ != 0); /* advance to next field */
649 } while ((field_length > 0) && (field_header != 'M'));
653 /* Always make sure there's something in the msg text field */
654 if (ret->cm_fields['M'] == NULL)
655 ret->cm_fields['M'] = strdoop("<no text>\n");
657 /* Perform "before read" hooks (aborting if any return nonzero) */
658 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
659 CtdlFreeMessage(ret);
668 * Returns 1 if the supplied pointer points to a valid Citadel message.
669 * If the pointer is NULL or the magic number check fails, returns 0.
671 int is_valid_message(struct CtdlMessage *msg) {
674 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
675 lprintf(3, "is_valid_message() -- self-check failed\n");
683 * 'Destructor' for struct CtdlMessage
685 void CtdlFreeMessage(struct CtdlMessage *msg)
689 if (is_valid_message(msg) == 0) return;
691 for (i = 0; i < 256; ++i)
692 if (msg->cm_fields[i] != NULL) {
693 phree(msg->cm_fields[i]);
696 msg->cm_magic = 0; /* just in case */
702 * Callback function for mime parser that wants to display text
704 void fixed_output(char *name, char *filename, char *partnum, char *disp,
705 void *content, char *cbtype, size_t length)
712 if (!strcasecmp(cbtype, "multipart/alternative")) {
713 strcpy(ma->prefix, partnum);
714 strcat(ma->prefix, ".");
720 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
722 && (ma->did_print == 1) ) {
723 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
729 if ( (!strcasecmp(cbtype, "text/plain"))
730 || (strlen(cbtype)==0) ) {
735 if (ch==10) cprintf("\r\n");
736 else cprintf("%c", ch);
739 else if (!strcasecmp(cbtype, "text/html")) {
740 ptr = html_to_ascii(content, 80, 0);
745 if (ch==10) cprintf("\r\n");
746 else cprintf("%c", ch);
750 else if (strncasecmp(cbtype, "multipart/", 10)) {
751 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
752 partnum, filename, cbtype, length);
758 * Get a message off disk. (returns om_* values found in msgbase.h)
761 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
762 int mode, /* how would you like that message? */
763 int headers_only, /* eschew the message body? */
764 int do_proto, /* do Citadel protocol responses? */
765 int crlf /* Use CRLF newlines instead of LF? */
771 char display_name[256];
772 struct CtdlMessage *TheMessage;
774 char *nl; /* newline string */
776 /* buffers needed for RFC822 translation */
786 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
790 sprintf(mid, "%ld", msg_num);
791 nl = (crlf ? "\r\n" : "\n");
793 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
794 if (do_proto) cprintf("%d Not logged in.\n",
795 ERROR + NOT_LOGGED_IN);
796 return(om_not_logged_in);
799 /* FIXME ... small security issue
800 * We need to check to make sure the requested message is actually
801 * in the current room, and set msg_ok to 1 only if it is. This
802 * functionality is currently missing because I'm in a hurry to replace
803 * broken production code with nonbroken pre-beta code. :( -- ajc
806 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
808 return(om_no_such_msg);
813 * Fetch the message from disk
815 TheMessage = CtdlFetchMessage(msg_num);
816 if (TheMessage == NULL) {
817 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
819 return(om_no_such_msg);
822 /* Are we downloading a MIME component? */
823 if (mode == MT_DOWNLOAD) {
824 if (TheMessage->cm_format_type != FMT_RFC822) {
826 cprintf("%d This is not a MIME message.\n",
828 } else if (CC->download_fp != NULL) {
829 if (do_proto) cprintf(
830 "%d You already have a download open.\n",
833 /* Parse the message text component */
834 mptr = TheMessage->cm_fields['M'];
835 mime_parser(mptr, NULL, *mime_download);
836 /* If there's no file open by this time, the requested
837 * section wasn't found, so print an error
839 if (CC->download_fp == NULL) {
840 if (do_proto) cprintf(
841 "%d Section %s not found.\n",
842 ERROR + FILE_NOT_FOUND,
846 CtdlFreeMessage(TheMessage);
847 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
850 /* now for the user-mode message reading loops */
851 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
853 /* Tell the client which format type we're using. If this is a
854 * MIME message, *lie* about it and tell the user it's fixed-format.
856 if (mode == MT_CITADEL) {
857 if (TheMessage->cm_format_type == FMT_RFC822) {
858 if (do_proto) cprintf("type=1\n");
861 if (do_proto) cprintf("type=%d\n",
862 TheMessage->cm_format_type);
866 /* nhdr=yes means that we're only displaying headers, no body */
867 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
868 if (do_proto) cprintf("nhdr=yes\n");
871 /* begin header processing loop for Citadel message format */
873 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
875 strcpy(display_name, "<unknown>");
876 if (TheMessage->cm_fields['A']) {
877 strcpy(buf, TheMessage->cm_fields['A']);
878 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
879 if (TheMessage->cm_anon_type == MES_ANON)
880 strcpy(display_name, "****");
881 else if (TheMessage->cm_anon_type == MES_AN2)
882 strcpy(display_name, "anonymous");
884 strcpy(display_name, buf);
886 && ((TheMessage->cm_anon_type == MES_ANON)
887 || (TheMessage->cm_anon_type == MES_AN2))) {
888 sprintf(&display_name[strlen(display_name)],
893 strcpy(allkeys, FORDER);
894 for (i=0; i<strlen(allkeys); ++i) {
895 k = (int) allkeys[i];
897 if (TheMessage->cm_fields[k] != NULL) {
899 if (do_proto) cprintf("%s=%s\n",
904 if (do_proto) cprintf("%s=%s\n",
906 TheMessage->cm_fields[k]
915 /* begin header processing loop for RFC822 transfer format */
920 strcpy(snode, NODENAME);
921 strcpy(lnode, HUMANNODE);
922 if (mode == MT_RFC822) {
923 cprintf("X-UIDL: %ld%s", msg_num, nl);
924 for (i = 0; i < 256; ++i) {
925 if (TheMessage->cm_fields[i]) {
926 mptr = TheMessage->cm_fields[i];
933 cprintf("Path: %s%s", mptr, nl);
936 cprintf("Subject: %s%s", mptr, nl);
942 cprintf("X-Citadel-Room: %s%s",
947 cprintf("To: %s%s", mptr, nl);
949 generate_rfc822_datestamp(datestamp,
951 cprintf("Date: %s%s", datestamp, nl);
957 for (i=0; i<strlen(suser); ++i) {
958 suser[i] = tolower(suser[i]);
959 if (!isalnum(suser[i])) suser[i]='_';
962 if (mode == MT_RFC822) {
963 if (!strcasecmp(snode, NODENAME)) {
967 /* Construct a fun message id */
968 cprintf("Message-ID: <%s", mid);
969 if (strchr(mid, '@')==NULL) {
970 cprintf("@%s", snode);
974 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
976 if (strlen(fuser) > 0) {
977 cprintf("From: %s (%s)%s", fuser, luser, nl);
980 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
983 cprintf("Organization: %s%s", lnode, nl);
986 /* end header processing loop ... at this point, we're in the text */
988 mptr = TheMessage->cm_fields['M'];
990 /* Tell the client about the MIME parts in this message */
991 if (TheMessage->cm_format_type == FMT_RFC822) {
992 if (mode == MT_CITADEL) {
993 mime_parser(mptr, NULL, *list_this_part);
995 else if (mode == MT_MIME) { /* list parts only */
996 mime_parser(mptr, NULL, *list_this_part);
997 if (do_proto) cprintf("000\n");
998 CtdlFreeMessage(TheMessage);
1001 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1002 /* FIXME ... we have to put some code in here to avoid
1003 * printing duplicate header information when both
1004 * Citadel and RFC822 headers exist. Preference should
1005 * probably be given to the RFC822 headers.
1007 while (ch=*(mptr++), ch!=0) {
1009 else if (ch==10) cprintf("%s", nl);
1010 else cprintf("%c", ch);
1012 if (do_proto) cprintf("000\n");
1013 CtdlFreeMessage(TheMessage);
1019 if (do_proto) cprintf("000\n");
1020 CtdlFreeMessage(TheMessage);
1024 /* signify start of msg text */
1025 if (mode == MT_CITADEL)
1026 if (do_proto) cprintf("text\n");
1027 if (mode == MT_RFC822) {
1028 if (TheMessage->cm_fields['U'] == NULL) {
1029 cprintf("Subject: (no subject)%s", nl);
1034 /* If the format type on disk is 1 (fixed-format), then we want
1035 * everything to be output completely literally ... regardless of
1036 * what message transfer format is in use.
1038 if (TheMessage->cm_format_type == FMT_FIXED) {
1040 while (ch = *mptr++, ch > 0) {
1043 if ((ch == 10) || (strlen(buf) > 250)) {
1044 cprintf("%s%s", buf, nl);
1047 buf[strlen(buf) + 1] = 0;
1048 buf[strlen(buf)] = ch;
1051 if (strlen(buf) > 0)
1052 cprintf("%s%s", buf, nl);
1055 /* If the message on disk is format 0 (Citadel vari-format), we
1056 * output using the formatter at 80 columns. This is the final output
1057 * form if the transfer format is RFC822, but if the transfer format
1058 * is Citadel proprietary, it'll still work, because the indentation
1059 * for new paragraphs is correct and the client will reformat the
1060 * message to the reader's screen width.
1062 if (TheMessage->cm_format_type == FMT_CITADEL) {
1063 memfmout(80, mptr, 0, nl);
1066 /* If the message on disk is format 4 (MIME), we've gotta hand it
1067 * off to the MIME parser. The client has already been told that
1068 * this message is format 1 (fixed format), so the callback function
1069 * we use will display those parts as-is.
1071 if (TheMessage->cm_format_type == FMT_RFC822) {
1072 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1073 memset(ma, 0, sizeof(struct ma_info));
1074 mime_parser(mptr, NULL, *fixed_output);
1077 /* now we're done */
1078 if (do_proto) cprintf("000\n");
1079 CtdlFreeMessage(TheMessage);
1086 * display a message (mode 0 - Citadel proprietary)
1088 void cmd_msg0(char *cmdbuf)
1091 int headers_only = 0;
1093 msgid = extract_long(cmdbuf, 0);
1094 headers_only = extract_int(cmdbuf, 1);
1096 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1102 * display a message (mode 2 - RFC822)
1104 void cmd_msg2(char *cmdbuf)
1107 int headers_only = 0;
1109 msgid = extract_long(cmdbuf, 0);
1110 headers_only = extract_int(cmdbuf, 1);
1112 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1118 * display a message (mode 3 - IGnet raw format - internal programs only)
1120 void cmd_msg3(char *cmdbuf)
1123 struct CtdlMessage *msg;
1126 if (CC->internal_pgm == 0) {
1127 cprintf("%d This command is for internal programs only.\n",
1132 msgnum = extract_long(cmdbuf, 0);
1133 msg = CtdlFetchMessage(msgnum);
1135 cprintf("%d Message %ld not found.\n",
1140 serialize_message(&smr, msg);
1141 CtdlFreeMessage(msg);
1144 cprintf("%d Unable to serialize message\n",
1145 ERROR+INTERNAL_ERROR);
1149 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1150 client_write(smr.ser, smr.len);
1157 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1159 void cmd_msg4(char *cmdbuf)
1163 msgid = extract_long(cmdbuf, 0);
1164 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1168 * Open a component of a MIME message as a download file
1170 void cmd_opna(char *cmdbuf)
1174 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1176 msgid = extract_long(cmdbuf, 0);
1177 extract(desired_section, cmdbuf, 1);
1179 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1184 * Save a message pointer into a specified room
1185 * (Returns 0 for success, nonzero for failure)
1186 * roomname may be NULL to use the current room
1188 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1190 char hold_rm[ROOMNAMELEN];
1191 struct cdbdata *cdbfr;
1194 long highest_msg = 0L;
1195 struct CtdlMessage *msg = NULL;
1197 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1198 roomname, msgid, flags);
1200 strcpy(hold_rm, CC->quickroom.QRname);
1202 /* We may need to check to see if this message is real */
1203 if ( (flags & SM_VERIFY_GOODNESS)
1204 || (flags & SM_DO_REPL_CHECK)
1206 msg = CtdlFetchMessage(msgid);
1207 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1210 /* Perform replication checks if necessary */
1211 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1213 if (getroom(&CC->quickroom,
1214 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1216 lprintf(9, "No such room <%s>\n", roomname);
1217 if (msg != NULL) CtdlFreeMessage(msg);
1218 return(ERROR + ROOM_NOT_FOUND);
1221 if (ReplicationChecks(msg) != 0) {
1222 getroom(&CC->quickroom, hold_rm);
1223 if (msg != NULL) CtdlFreeMessage(msg);
1224 lprintf(9, "Did replication, and newer exists\n");
1229 /* Now the regular stuff */
1230 if (lgetroom(&CC->quickroom,
1231 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1233 lprintf(9, "No such room <%s>\n", roomname);
1234 if (msg != NULL) CtdlFreeMessage(msg);
1235 return(ERROR + ROOM_NOT_FOUND);
1238 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1239 if (cdbfr == NULL) {
1243 msglist = mallok(cdbfr->len);
1244 if (msglist == NULL)
1245 lprintf(3, "ERROR malloc msglist!\n");
1246 num_msgs = cdbfr->len / sizeof(long);
1247 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1252 /* Make sure the message doesn't already exist in this room. It
1253 * is absolutely taboo to have more than one reference to the same
1254 * message in a room.
1256 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1257 if (msglist[i] == msgid) {
1258 lputroom(&CC->quickroom); /* unlock the room */
1259 getroom(&CC->quickroom, hold_rm);
1260 if (msg != NULL) CtdlFreeMessage(msg);
1261 return(ERROR + ALREADY_EXISTS);
1265 /* Now add the new message */
1267 msglist = reallok(msglist,
1268 (num_msgs * sizeof(long)));
1270 if (msglist == NULL) {
1271 lprintf(3, "ERROR: can't realloc message list!\n");
1273 msglist[num_msgs - 1] = msgid;
1275 /* Sort the message list, so all the msgid's are in order */
1276 num_msgs = sort_msglist(msglist, num_msgs);
1278 /* Determine the highest message number */
1279 highest_msg = msglist[num_msgs - 1];
1281 /* Write it back to disk. */
1282 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1283 msglist, num_msgs * sizeof(long));
1285 /* Free up the memory we used. */
1288 /* Update the highest-message pointer and unlock the room. */
1289 CC->quickroom.QRhighest = highest_msg;
1290 lputroom(&CC->quickroom);
1291 getroom(&CC->quickroom, hold_rm);
1293 /* Bump the reference count for this message. */
1294 if ((flags & SM_DONT_BUMP_REF)==0) {
1295 AdjRefCount(msgid, +1);
1298 /* Return success. */
1299 if (msg != NULL) CtdlFreeMessage(msg);
1306 * Message base operation to send a message to the master file
1307 * (returns new message number)
1309 * This is the back end for CtdlSaveMsg() and should not be directly
1310 * called by server-side modules.
1313 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1314 FILE *save_a_copy) /* save a copy to disk? */
1321 /* Get a new message number */
1322 newmsgid = get_new_message_number();
1323 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1325 /* Generate an ID if we don't have one already */
1326 if (msg->cm_fields['I']==NULL) {
1327 msg->cm_fields['I'] = strdoop(msgidbuf);
1330 serialize_message(&smr, msg);
1333 cprintf("%d Unable to serialize message\n",
1334 ERROR+INTERNAL_ERROR);
1338 /* Write our little bundle of joy into the message base */
1339 begin_critical_section(S_MSGMAIN);
1340 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1341 smr.ser, smr.len) < 0) {
1342 lprintf(2, "Can't store message\n");
1347 end_critical_section(S_MSGMAIN);
1349 /* If the caller specified that a copy should be saved to a particular
1350 * file handle, do that now too.
1352 if (save_a_copy != NULL) {
1353 fwrite(smr.ser, smr.len, 1, save_a_copy);
1356 /* Free the memory we used for the serialized message */
1359 /* Return the *local* message ID to the caller
1360 * (even if we're storing an incoming network message)
1368 * Serialize a struct CtdlMessage into the format used on disk and network.
1370 * This function loads up a "struct ser_ret" (defined in server.h) which
1371 * contains the length of the serialized message and a pointer to the
1372 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1374 void serialize_message(struct ser_ret *ret, /* return values */
1375 struct CtdlMessage *msg) /* unserialized msg */
1379 static char *forder = FORDER;
1381 if (is_valid_message(msg) == 0) return; /* self check */
1384 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1385 ret->len = ret->len +
1386 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1388 lprintf(9, "calling malloc(%d)\n", ret->len);
1389 ret->ser = mallok(ret->len);
1390 if (ret->ser == NULL) {
1396 ret->ser[1] = msg->cm_anon_type;
1397 ret->ser[2] = msg->cm_format_type;
1400 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1401 ret->ser[wlen++] = (char)forder[i];
1402 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1403 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1405 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1414 * Back end for the ReplicationChecks() function
1416 void check_repl(long msgnum) {
1417 struct CtdlMessage *msg;
1418 time_t timestamp = (-1L);
1420 lprintf(9, "check_repl() found message %ld\n", msgnum);
1421 msg = CtdlFetchMessage(msgnum);
1422 if (msg == NULL) return;
1423 if (msg->cm_fields['T'] != NULL) {
1424 timestamp = atol(msg->cm_fields['T']);
1426 CtdlFreeMessage(msg);
1428 if (timestamp > msg_repl->highest) {
1429 msg_repl->highest = timestamp; /* newer! */
1430 lprintf(9, "newer!\n");
1433 lprintf(9, "older!\n");
1435 /* Existing isn't newer? Then delete the old one(s). */
1436 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1441 * Check to see if any messages already exist which carry the same Extended ID
1445 * -> With older timestamps: delete them and return 0. Message will be saved.
1446 * -> With newer timestamps: return 1. Message save will be aborted.
1448 int ReplicationChecks(struct CtdlMessage *msg) {
1449 struct CtdlMessage *template;
1452 lprintf(9, "ReplicationChecks() started\n");
1453 /* No extended id? Don't do anything. */
1454 if (msg->cm_fields['E'] == NULL) return 0;
1455 if (strlen(msg->cm_fields['E']) == 0) return 0;
1456 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1458 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1459 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1460 msg_repl->highest = atol(msg->cm_fields['T']);
1462 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1463 memset(template, 0, sizeof(struct CtdlMessage));
1464 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1466 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1468 /* If a newer message exists with the same Extended ID, abort
1471 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1475 CtdlFreeMessage(template);
1476 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1484 * Save a message to disk
1486 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1487 char *rec, /* Recipient (mail) */
1488 char *force, /* force a particular room? */
1489 int supplied_mailtype) /* local or remote type */
1492 char hold_rm[ROOMNAMELEN];
1493 char actual_rm[ROOMNAMELEN];
1494 char force_room[ROOMNAMELEN];
1495 char content_type[256]; /* We have to learn this */
1496 char recipient[256];
1499 struct usersupp userbuf;
1501 struct SuppMsgInfo smi;
1502 FILE *network_fp = NULL;
1503 static int seqnum = 1;
1504 struct CtdlMessage *imsg;
1508 lprintf(9, "CtdlSaveMsg() called\n");
1509 if (is_valid_message(msg) == 0) return(-1); /* self check */
1510 mailtype = supplied_mailtype;
1512 /* If this message has no timestamp, we take the liberty of
1513 * giving it one, right now.
1515 if (msg->cm_fields['T'] == NULL) {
1516 lprintf(9, "Generating timestamp\n");
1517 sprintf(aaa, "%ld", time(NULL));
1518 msg->cm_fields['T'] = strdoop(aaa);
1521 /* If this message has no path, we generate one.
1523 if (msg->cm_fields['P'] == NULL) {
1524 lprintf(9, "Generating path\n");
1525 if (msg->cm_fields['A'] != NULL) {
1526 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1527 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1528 if (isspace(msg->cm_fields['P'][a])) {
1529 msg->cm_fields['P'][a] = ' ';
1534 msg->cm_fields['P'] = strdoop("unknown");
1538 strcpy(force_room, force);
1540 /* Strip non-printable characters out of the recipient name */
1541 lprintf(9, "Checking recipient (if present)\n");
1542 strcpy(recipient, rec);
1543 for (a = 0; a < strlen(recipient); ++a)
1544 if (!isprint(recipient[a]))
1545 strcpy(&recipient[a], &recipient[a + 1]);
1547 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1548 for (a=0; a<strlen(recipient); ++a) {
1549 if (recipient[a] == '@') {
1550 if (CtdlHostAlias(&recipient[a+1])
1551 == hostalias_localhost) {
1553 lprintf(7, "Changed to <%s>\n", recipient);
1554 mailtype = MES_LOCAL;
1559 lprintf(9, "Recipient is <%s>\n", recipient);
1561 /* Learn about what's inside, because it's what's inside that counts */
1562 lprintf(9, "Learning what's inside\n");
1563 if (msg->cm_fields['M'] == NULL) {
1564 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1567 switch (msg->cm_format_type) {
1569 strcpy(content_type, "text/x-citadel-variformat");
1572 strcpy(content_type, "text/plain");
1575 strcpy(content_type, "text/plain");
1576 /* advance past header fields */
1577 mptr = msg->cm_fields['M'];
1580 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1581 safestrncpy(content_type, mptr,
1582 sizeof(content_type));
1583 strcpy(content_type, &content_type[14]);
1584 for (a = 0; a < strlen(content_type); ++a)
1585 if ((content_type[a] == ';')
1586 || (content_type[a] == ' ')
1587 || (content_type[a] == 13)
1588 || (content_type[a] == 10))
1589 content_type[a] = 0;
1596 /* Goto the correct room */
1597 lprintf(9, "Switching rooms\n");
1598 strcpy(hold_rm, CC->quickroom.QRname);
1599 strcpy(actual_rm, CC->quickroom.QRname);
1601 /* If the user is a twit, move to the twit room for posting */
1602 lprintf(9, "Handling twit stuff\n");
1604 if (CC->usersupp.axlevel == 2) {
1605 strcpy(hold_rm, actual_rm);
1606 strcpy(actual_rm, config.c_twitroom);
1610 /* ...or if this message is destined for Aide> then go there. */
1611 if (strlen(force_room) > 0) {
1612 strcpy(actual_rm, force_room);
1615 lprintf(9, "Possibly relocating\n");
1616 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1617 getroom(&CC->quickroom, actual_rm);
1621 * If this message has no O (room) field, generate one.
1623 if (msg->cm_fields['O'] == NULL) {
1624 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1627 /* Perform "before save" hooks (aborting if any return nonzero) */
1628 lprintf(9, "Performing before-save hooks\n");
1629 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1631 /* If this message has an Extended ID, perform replication checks */
1632 lprintf(9, "Performing replication checks\n");
1633 if (ReplicationChecks(msg) > 0) return(-1);
1635 /* Network mail - send a copy to the network program. */
1636 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1637 lprintf(9, "Sending network spool\n");
1638 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1639 (long) getpid(), CC->cs_pid, ++seqnum);
1640 lprintf(9, "Saving a copy to %s\n", aaa);
1641 network_fp = fopen(aaa, "ab+");
1642 if (network_fp == NULL)
1643 lprintf(2, "ERROR: %s\n", strerror(errno));
1646 /* Save it to disk */
1647 lprintf(9, "Saving to disk\n");
1648 newmsgid = send_message(msg, network_fp);
1649 if (network_fp != NULL) {
1651 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1654 if (newmsgid <= 0L) return(-1);
1656 /* Write a supplemental message info record. This doesn't have to
1657 * be a critical section because nobody else knows about this message
1660 lprintf(9, "Creating SuppMsgInfo record\n");
1661 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1662 smi.smi_msgnum = newmsgid;
1663 smi.smi_refcount = 0;
1664 safestrncpy(smi.smi_content_type, content_type, 64);
1665 PutSuppMsgInfo(&smi);
1667 /* Now figure out where to store the pointers */
1668 lprintf(9, "Storing pointers\n");
1670 /* If this is being done by the networker delivering a private
1671 * message, we want to BYPASS saving the sender's copy (because there
1672 * is no local sender; it would otherwise go to the Trashcan).
1674 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1675 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1676 lprintf(3, "ERROR saving message pointer!\n");
1677 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1681 /* For internet mail, drop a copy in the outbound queue room */
1682 if (mailtype == MES_INTERNET) {
1683 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1686 /* Bump this user's messages posted counter. */
1687 lprintf(9, "Updating user\n");
1688 lgetuser(&CC->usersupp, CC->curr_user);
1689 CC->usersupp.posted = CC->usersupp.posted + 1;
1690 lputuser(&CC->usersupp);
1692 /* If this is private, local mail, make a copy in the
1693 * recipient's mailbox and bump the reference count.
1695 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1696 if (getuser(&userbuf, recipient) == 0) {
1697 lprintf(9, "Delivering private mail\n");
1698 MailboxName(actual_rm, &userbuf, MAILROOM);
1699 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1702 lprintf(9, "No user <%s>, saving in %s> instead\n",
1703 recipient, AIDEROOM);
1704 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1708 /* Perform "after save" hooks */
1709 lprintf(9, "Performing after-save hooks\n");
1710 PerformMessageHooks(msg, EVT_AFTERSAVE);
1713 lprintf(9, "Returning to original room\n");
1714 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1715 getroom(&CC->quickroom, hold_rm);
1717 /* For internet mail, generate delivery instructions
1718 * (Yes, this is recursive! Deal with it!)
1720 if (mailtype == MES_INTERNET) {
1721 lprintf(9, "Generating delivery instructions\n");
1722 instr = mallok(2048);
1724 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1727 SPOOLMIME, newmsgid, time(NULL),
1728 msg->cm_fields['A'], msg->cm_fields['N'],
1731 imsg = mallok(sizeof(struct CtdlMessage));
1732 memset(imsg, 0, sizeof(struct CtdlMessage));
1733 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1734 imsg->cm_anon_type = MES_NORMAL;
1735 imsg->cm_format_type = FMT_RFC822;
1736 imsg->cm_fields['A'] = strdoop("Citadel");
1737 imsg->cm_fields['M'] = instr;
1738 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1739 CtdlFreeMessage(imsg);
1748 * Convenience function for generating small administrative messages.
1750 void quickie_message(char *from, char *to, char *room, char *text)
1752 struct CtdlMessage *msg;
1754 msg = mallok(sizeof(struct CtdlMessage));
1755 memset(msg, 0, sizeof(struct CtdlMessage));
1756 msg->cm_magic = CTDLMESSAGE_MAGIC;
1757 msg->cm_anon_type = MES_NORMAL;
1758 msg->cm_format_type = 0;
1759 msg->cm_fields['A'] = strdoop(from);
1760 msg->cm_fields['O'] = strdoop(room);
1761 msg->cm_fields['N'] = strdoop(NODENAME);
1763 msg->cm_fields['R'] = strdoop(to);
1764 msg->cm_fields['M'] = strdoop(text);
1766 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1767 CtdlFreeMessage(msg);
1768 syslog(LOG_NOTICE, text);
1774 * Back end function used by make_message() and similar functions
1776 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1777 size_t maxlen, /* maximum message length */
1778 char *exist /* if non-null, append to it;
1779 exist is ALWAYS freed */
1782 size_t message_len = 0;
1783 size_t buffer_len = 0;
1787 if (exist == NULL) {
1791 m = reallok(exist, strlen(exist) + 4096);
1792 if (m == NULL) phree(exist);
1795 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1802 /* read in the lines of message text one by one */
1804 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1806 /* augment the buffer if we have to */
1807 if ((message_len + strlen(buf) + 2) > buffer_len) {
1808 lprintf(9, "realloking\n");
1809 ptr = reallok(m, (buffer_len * 2) );
1810 if (ptr == NULL) { /* flush if can't allocate */
1811 while ( (client_gets(buf)>0) &&
1812 strcmp(buf, terminator)) ;;
1815 buffer_len = (buffer_len * 2);
1818 lprintf(9, "buffer_len is %d\n", buffer_len);
1822 if (append == NULL) append = m;
1823 while (strlen(append) > 0) ++append;
1824 strcpy(append, buf);
1825 strcat(append, "\n");
1826 message_len = message_len + strlen(buf) + 1;
1828 /* if we've hit the max msg length, flush the rest */
1829 if (message_len >= maxlen) {
1830 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1841 * Build a binary message to be saved on disk.
1844 struct CtdlMessage *make_message(
1845 struct usersupp *author, /* author's usersupp structure */
1846 char *recipient, /* NULL if it's not mail */
1847 char *room, /* room where it's going */
1848 int type, /* see MES_ types in header file */
1849 int net_type, /* see MES_ types in header file */
1850 int format_type, /* local or remote (see citadel.h) */
1851 char *fake_name) /* who we're masquerading as */
1857 struct CtdlMessage *msg;
1859 msg = mallok(sizeof(struct CtdlMessage));
1860 memset(msg, 0, sizeof(struct CtdlMessage));
1861 msg->cm_magic = CTDLMESSAGE_MAGIC;
1862 msg->cm_anon_type = type;
1863 msg->cm_format_type = format_type;
1865 /* Don't confuse the poor folks if it's not routed mail. */
1866 strcpy(dest_node, "");
1868 /* If net_type is MES_BINARY, split out the destination node. */
1869 if (net_type == MES_BINARY) {
1870 strcpy(dest_node, NODENAME);
1871 for (a = 0; a < strlen(recipient); ++a) {
1872 if (recipient[a] == '@') {
1874 strcpy(dest_node, &recipient[a + 1]);
1879 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1880 if (net_type == MES_INTERNET) {
1881 strcpy(dest_node, "internet");
1884 while (isspace(recipient[strlen(recipient) - 1]))
1885 recipient[strlen(recipient) - 1] = 0;
1887 sprintf(buf, "cit%ld", author->usernum); /* Path */
1888 msg->cm_fields['P'] = strdoop(buf);
1890 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1891 msg->cm_fields['T'] = strdoop(buf);
1893 if (fake_name[0]) /* author */
1894 msg->cm_fields['A'] = strdoop(fake_name);
1896 msg->cm_fields['A'] = strdoop(author->fullname);
1898 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1899 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1901 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1903 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1904 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1906 if (recipient[0] != 0)
1907 msg->cm_fields['R'] = strdoop(recipient);
1908 if (dest_node[0] != 0)
1909 msg->cm_fields['D'] = strdoop(dest_node);
1912 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1913 config.c_maxmsglen, NULL);
1924 * message entry - mode 0 (normal)
1926 void cmd_ent0(char *entargs)
1929 char recipient[256];
1931 int format_type = 0;
1932 char newusername[256];
1933 struct CtdlMessage *msg;
1937 struct usersupp tempUS;
1940 post = extract_int(entargs, 0);
1941 extract(recipient, entargs, 1);
1942 anon_flag = extract_int(entargs, 2);
1943 format_type = extract_int(entargs, 3);
1945 /* first check to make sure the request is valid. */
1947 if (!(CC->logged_in)) {
1948 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1951 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1952 cprintf("%d Need to be validated to enter ",
1953 ERROR + HIGHER_ACCESS_REQUIRED);
1954 cprintf("(except in %s> to sysop)\n", MAILROOM);
1957 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1958 cprintf("%d Need net privileges to enter here.\n",
1959 ERROR + HIGHER_ACCESS_REQUIRED);
1962 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1963 cprintf("%d Sorry, this is a read-only room.\n",
1964 ERROR + HIGHER_ACCESS_REQUIRED);
1971 if (CC->usersupp.axlevel < 6) {
1972 cprintf("%d You don't have permission to masquerade.\n",
1973 ERROR + HIGHER_ACCESS_REQUIRED);
1976 extract(newusername, entargs, 4);
1977 memset(CC->fake_postname, 0, 32);
1978 strcpy(CC->fake_postname, newusername);
1979 cprintf("%d Ok\n", OK);
1982 CC->cs_flags |= CS_POSTING;
1985 if (CC->quickroom.QRflags & QR_MAILBOX) {
1986 if (CC->usersupp.axlevel >= 2) {
1987 strcpy(buf, recipient);
1989 strcpy(buf, "sysop");
1990 e = alias(buf); /* alias and mail type */
1991 if ((buf[0] == 0) || (e == MES_ERROR)) {
1992 cprintf("%d Unknown address - cannot send message.\n",
1993 ERROR + NO_SUCH_USER);
1996 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1997 cprintf("%d Net privileges required for network mail.\n",
1998 ERROR + HIGHER_ACCESS_REQUIRED);
2001 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2002 && ((CC->usersupp.flags & US_INTERNET) == 0)
2003 && (!CC->internal_pgm)) {
2004 cprintf("%d You don't have access to Internet mail.\n",
2005 ERROR + HIGHER_ACCESS_REQUIRED);
2008 if (!strcasecmp(buf, "sysop")) {
2013 goto SKFALL; /* don't search local file */
2014 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2015 cprintf("%d Can't send mail to yourself!\n",
2016 ERROR + NO_SUCH_USER);
2019 /* Check to make sure the user exists; also get the correct
2020 * upper/lower casing of the name.
2022 a = getuser(&tempUS, buf);
2024 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2027 strcpy(buf, tempUS.fullname);
2030 SKFALL: b = MES_NORMAL;
2031 if (CC->quickroom.QRflags & QR_ANONONLY)
2033 if (CC->quickroom.QRflags & QR_ANONOPT) {
2037 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2040 /* If we're only checking the validity of the request, return
2041 * success without creating the message.
2044 cprintf("%d %s\n", OK, buf);
2048 cprintf("%d send message\n", SEND_LISTING);
2050 /* Read in the message from the client. */
2051 if (CC->fake_postname[0])
2052 msg = make_message(&CC->usersupp, buf,
2053 CC->quickroom.QRname, b, e, format_type,
2055 else if (CC->fake_username[0])
2056 msg = make_message(&CC->usersupp, buf,
2057 CC->quickroom.QRname, b, e, format_type,
2060 msg = make_message(&CC->usersupp, buf,
2061 CC->quickroom.QRname, b, e, format_type, "");
2064 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2065 CtdlFreeMessage(msg);
2066 CC->fake_postname[0] = '\0';
2073 * message entry - mode 3 (raw)
2075 void cmd_ent3(char *entargs)
2081 unsigned char ch, which_field;
2082 struct usersupp tempUS;
2084 struct CtdlMessage *msg;
2087 if (CC->internal_pgm == 0) {
2088 cprintf("%d This command is for internal programs only.\n",
2093 /* See if there's a recipient, but make sure it's a real one */
2094 extract(recp, entargs, 1);
2095 for (a = 0; a < strlen(recp); ++a)
2096 if (!isprint(recp[a]))
2097 strcpy(&recp[a], &recp[a + 1]);
2098 while (isspace(recp[0]))
2099 strcpy(recp, &recp[1]);
2100 while (isspace(recp[strlen(recp) - 1]))
2101 recp[strlen(recp) - 1] = 0;
2103 /* If we're in Mail, check the recipient */
2104 if (strlen(recp) > 0) {
2105 e = alias(recp); /* alias and mail type */
2106 if ((recp[0] == 0) || (e == MES_ERROR)) {
2107 cprintf("%d Unknown address - cannot send message.\n",
2108 ERROR + NO_SUCH_USER);
2111 if (e == MES_LOCAL) {
2112 a = getuser(&tempUS, recp);
2114 cprintf("%d No such user.\n",
2115 ERROR + NO_SUCH_USER);
2121 /* At this point, message has been approved. */
2122 if (extract_int(entargs, 0) == 0) {
2123 cprintf("%d OK to send\n", OK);
2127 msglen = extract_long(entargs, 2);
2128 msg = mallok(sizeof(struct CtdlMessage));
2130 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2134 memset(msg, 0, sizeof(struct CtdlMessage));
2135 tempbuf = mallok(msglen);
2136 if (tempbuf == NULL) {
2137 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2142 cprintf("%d %ld\n", SEND_BINARY, msglen);
2144 client_read(&ch, 1); /* 0xFF magic number */
2145 msg->cm_magic = CTDLMESSAGE_MAGIC;
2146 client_read(&ch, 1); /* anon type */
2147 msg->cm_anon_type = ch;
2148 client_read(&ch, 1); /* format type */
2149 msg->cm_format_type = ch;
2150 msglen = msglen - 3;
2152 while (msglen > 0) {
2153 client_read(&which_field, 1);
2154 if (!isalpha(which_field)) valid_msg = 0;
2158 client_read(&ch, 1);
2160 a = strlen(tempbuf);
2163 } while ( (ch != 0) && (msglen > 0) );
2165 msg->cm_fields[which_field] = strdoop(tempbuf);
2168 msg->cm_flags = CM_SKIP_HOOKS;
2169 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2170 CtdlFreeMessage(msg);
2176 * API function to delete messages which match a set of criteria
2177 * (returns the actual number of messages deleted)
2179 int CtdlDeleteMessages(char *room_name, /* which room */
2180 long dmsgnum, /* or "0" for any */
2181 char *content_type /* or NULL for any */
2185 struct quickroom qrbuf;
2186 struct cdbdata *cdbfr;
2187 long *msglist = NULL;
2190 int num_deleted = 0;
2192 struct SuppMsgInfo smi;
2194 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2195 room_name, dmsgnum, content_type);
2197 /* get room record, obtaining a lock... */
2198 if (lgetroom(&qrbuf, room_name) != 0) {
2199 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2201 return (0); /* room not found */
2203 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2205 if (cdbfr != NULL) {
2206 msglist = mallok(cdbfr->len);
2207 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2208 num_msgs = cdbfr->len / sizeof(long);
2212 for (i = 0; i < num_msgs; ++i) {
2215 /* Set/clear a bit for each criterion */
2217 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2218 delete_this |= 0x01;
2220 if (content_type == NULL) {
2221 delete_this |= 0x02;
2223 GetSuppMsgInfo(&smi, msglist[i]);
2224 if (!strcasecmp(smi.smi_content_type,
2226 delete_this |= 0x02;
2230 /* Delete message only if all bits are set */
2231 if (delete_this == 0x03) {
2232 AdjRefCount(msglist[i], -1);
2238 num_msgs = sort_msglist(msglist, num_msgs);
2239 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2240 msglist, (num_msgs * sizeof(long)));
2242 qrbuf.QRhighest = msglist[num_msgs - 1];
2246 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2247 return (num_deleted);
2253 * Delete message from current room
2255 void cmd_dele(char *delstr)
2260 getuser(&CC->usersupp, CC->curr_user);
2261 if ((CC->usersupp.axlevel < 6)
2262 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2263 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2264 && (!(CC->internal_pgm))) {
2265 cprintf("%d Higher access required.\n",
2266 ERROR + HIGHER_ACCESS_REQUIRED);
2269 delnum = extract_long(delstr, 0);
2271 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2274 cprintf("%d %d message%s deleted.\n", OK,
2275 num_deleted, ((num_deleted != 1) ? "s" : ""));
2277 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2283 * move or copy a message to another room
2285 void cmd_move(char *args)
2289 struct quickroom qtemp;
2293 num = extract_long(args, 0);
2294 extract(targ, args, 1);
2295 targ[ROOMNAMELEN - 1] = 0;
2296 is_copy = extract_int(args, 2);
2298 getuser(&CC->usersupp, CC->curr_user);
2299 if ((CC->usersupp.axlevel < 6)
2300 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2301 cprintf("%d Higher access required.\n",
2302 ERROR + HIGHER_ACCESS_REQUIRED);
2306 if (getroom(&qtemp, targ) != 0) {
2307 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2311 err = CtdlSaveMsgPointerInRoom(targ, num,
2312 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2314 cprintf("%d Cannot store message in %s: error %d\n",
2319 /* Now delete the message from the source room,
2320 * if this is a 'move' rather than a 'copy' operation.
2322 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2324 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2330 * GetSuppMsgInfo() - Get the supplementary record for a message
2332 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2335 struct cdbdata *cdbsmi;
2338 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2339 smibuf->smi_msgnum = msgnum;
2340 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2342 /* Use the negative of the message number for its supp record index */
2343 TheIndex = (0L - msgnum);
2345 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2346 if (cdbsmi == NULL) {
2347 return; /* record not found; go with defaults */
2349 memcpy(smibuf, cdbsmi->ptr,
2350 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2351 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2358 * PutSuppMsgInfo() - (re)write supplementary record for a message
2360 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2364 /* Use the negative of the message number for its supp record index */
2365 TheIndex = (0L - smibuf->smi_msgnum);
2367 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2368 smibuf->smi_msgnum, smibuf->smi_refcount);
2370 cdb_store(CDB_MSGMAIN,
2371 &TheIndex, sizeof(long),
2372 smibuf, sizeof(struct SuppMsgInfo));
2377 * AdjRefCount - change the reference count for a message;
2378 * delete the message if it reaches zero
2380 void AdjRefCount(long msgnum, int incr)
2383 struct SuppMsgInfo smi;
2386 /* This is a *tight* critical section; please keep it that way, as
2387 * it may get called while nested in other critical sections.
2388 * Complicating this any further will surely cause deadlock!
2390 begin_critical_section(S_SUPPMSGMAIN);
2391 GetSuppMsgInfo(&smi, msgnum);
2392 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2393 msgnum, smi.smi_refcount);
2394 smi.smi_refcount += incr;
2395 PutSuppMsgInfo(&smi);
2396 end_critical_section(S_SUPPMSGMAIN);
2397 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2398 msgnum, smi.smi_refcount);
2400 /* If the reference count is now zero, delete the message
2401 * (and its supplementary record as well).
2403 if (smi.smi_refcount == 0) {
2404 lprintf(9, "Deleting message <%ld>\n", msgnum);
2406 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2407 delnum = (0L - msgnum);
2408 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2413 * Write a generic object to this room
2415 * Note: this could be much more efficient. Right now we use two temporary
2416 * files, and still pull the message into memory as with all others.
2418 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2419 char *content_type, /* MIME type of this object */
2420 char *tempfilename, /* Where to fetch it from */
2421 struct usersupp *is_mailbox, /* Mailbox room? */
2422 int is_binary, /* Is encoding necessary? */
2423 int is_unique, /* Del others of this type? */
2424 unsigned int flags /* Internal save flags */
2429 char filename[PATH_MAX];
2432 struct quickroom qrbuf;
2433 char roomname[ROOMNAMELEN];
2434 struct CtdlMessage *msg;
2437 if (is_mailbox != NULL)
2438 MailboxName(roomname, is_mailbox, req_room);
2440 safestrncpy(roomname, req_room, sizeof(roomname));
2441 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2443 strcpy(filename, tmpnam(NULL));
2444 fp = fopen(filename, "w");
2448 tempfp = fopen(tempfilename, "r");
2449 if (tempfp == NULL) {
2455 fprintf(fp, "Content-type: %s\n", content_type);
2456 lprintf(9, "Content-type: %s\n", content_type);
2458 if (is_binary == 0) {
2459 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2460 while (ch = getc(tempfp), ch > 0)
2466 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2469 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2470 tempfilename, filename);
2474 lprintf(9, "Allocating\n");
2475 msg = mallok(sizeof(struct CtdlMessage));
2476 memset(msg, 0, sizeof(struct CtdlMessage));
2477 msg->cm_magic = CTDLMESSAGE_MAGIC;
2478 msg->cm_anon_type = MES_NORMAL;
2479 msg->cm_format_type = 4;
2480 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2481 msg->cm_fields['O'] = strdoop(req_room);
2482 msg->cm_fields['N'] = strdoop(config.c_nodename);
2483 msg->cm_fields['H'] = strdoop(config.c_humannode);
2484 msg->cm_flags = flags;
2486 lprintf(9, "Loading\n");
2487 fp = fopen(filename, "rb");
2488 fseek(fp, 0L, SEEK_END);
2491 msg->cm_fields['M'] = mallok(len);
2492 fread(msg->cm_fields['M'], len, 1, fp);
2496 /* Create the requested room if we have to. */
2497 if (getroom(&qrbuf, roomname) != 0) {
2498 create_room(roomname,
2499 ( (is_mailbox != NULL) ? 4 : 3 ),
2502 /* If the caller specified this object as unique, delete all
2503 * other objects of this type that are currently in the room.
2506 lprintf(9, "Deleted %d other msgs of this type\n",
2507 CtdlDeleteMessages(roomname, 0L, content_type));
2509 /* Now write the data */
2510 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2511 CtdlFreeMessage(msg);
2519 void CtdlGetSysConfigBackend(long msgnum) {
2520 config_msgnum = msgnum;
2524 char *CtdlGetSysConfig(char *sysconfname) {
2525 char hold_rm[ROOMNAMELEN];
2528 struct CtdlMessage *msg;
2531 strcpy(hold_rm, CC->quickroom.QRname);
2532 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2533 getroom(&CC->quickroom, hold_rm);
2538 /* We want the last (and probably only) config in this room */
2539 begin_critical_section(S_CONFIG);
2540 config_msgnum = (-1L);
2541 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2542 CtdlGetSysConfigBackend);
2543 msgnum = config_msgnum;
2544 end_critical_section(S_CONFIG);
2550 msg = CtdlFetchMessage(msgnum);
2552 conf = strdoop(msg->cm_fields['M']);
2553 CtdlFreeMessage(msg);
2560 getroom(&CC->quickroom, hold_rm);
2562 lprintf(9, "eggstracting...\n");
2563 if (conf != NULL) do {
2564 extract_token(buf, conf, 0, '\n');
2565 lprintf(9, "eggstracted <%s>\n", buf);
2566 strcpy(conf, &conf[strlen(buf)+1]);
2567 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2572 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2573 char temp[PATH_MAX];
2576 strcpy(temp, tmpnam(NULL));
2578 fp = fopen(temp, "w");
2579 if (fp == NULL) return;
2580 fprintf(fp, "%s", sysconfdata);
2583 /* this handy API function does all the work for us */
2584 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);