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)) {
966 cprintf("Message-ID: <%s@%s>%s", mid, snode, nl);
967 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
969 if (strlen(fuser) > 0) {
970 cprintf("From: %s (%s)%s", fuser, luser, nl);
973 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
976 cprintf("Organization: %s%s", lnode, nl);
979 /* end header processing loop ... at this point, we're in the text */
981 mptr = TheMessage->cm_fields['M'];
983 /* Tell the client about the MIME parts in this message */
984 if (TheMessage->cm_format_type == FMT_RFC822) {
985 if (mode == MT_CITADEL) {
986 mime_parser(mptr, NULL, *list_this_part);
988 else if (mode == MT_MIME) { /* list parts only */
989 mime_parser(mptr, NULL, *list_this_part);
990 if (do_proto) cprintf("000\n");
991 CtdlFreeMessage(TheMessage);
994 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
995 /* FIXME ... we have to put some code in here to avoid
996 * printing duplicate header information when both
997 * Citadel and RFC822 headers exist. Preference should
998 * probably be given to the RFC822 headers.
1000 while (ch=*(mptr++), ch!=0) {
1002 else if (ch==10) cprintf("%s", nl);
1003 else cprintf("%c", ch);
1005 if (do_proto) cprintf("000\n");
1006 CtdlFreeMessage(TheMessage);
1012 if (do_proto) cprintf("000\n");
1013 CtdlFreeMessage(TheMessage);
1017 /* signify start of msg text */
1018 if (mode == MT_CITADEL)
1019 if (do_proto) cprintf("text\n");
1020 if (mode == MT_RFC822) {
1021 if (TheMessage->cm_fields['U'] == NULL) {
1022 cprintf("Subject: (no subject)%s", nl);
1027 /* If the format type on disk is 1 (fixed-format), then we want
1028 * everything to be output completely literally ... regardless of
1029 * what message transfer format is in use.
1031 if (TheMessage->cm_format_type == FMT_FIXED) {
1033 while (ch = *mptr++, ch > 0) {
1036 if ((ch == 10) || (strlen(buf) > 250)) {
1037 cprintf("%s%s", buf, nl);
1040 buf[strlen(buf) + 1] = 0;
1041 buf[strlen(buf)] = ch;
1044 if (strlen(buf) > 0)
1045 cprintf("%s%s", buf, nl);
1048 /* If the message on disk is format 0 (Citadel vari-format), we
1049 * output using the formatter at 80 columns. This is the final output
1050 * form if the transfer format is RFC822, but if the transfer format
1051 * is Citadel proprietary, it'll still work, because the indentation
1052 * for new paragraphs is correct and the client will reformat the
1053 * message to the reader's screen width.
1055 if (TheMessage->cm_format_type == FMT_CITADEL) {
1056 memfmout(80, mptr, 0, nl);
1059 /* If the message on disk is format 4 (MIME), we've gotta hand it
1060 * off to the MIME parser. The client has already been told that
1061 * this message is format 1 (fixed format), so the callback function
1062 * we use will display those parts as-is.
1064 if (TheMessage->cm_format_type == FMT_RFC822) {
1065 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1066 memset(ma, 0, sizeof(struct ma_info));
1067 mime_parser(mptr, NULL, *fixed_output);
1070 /* now we're done */
1071 if (do_proto) cprintf("000\n");
1072 CtdlFreeMessage(TheMessage);
1079 * display a message (mode 0 - Citadel proprietary)
1081 void cmd_msg0(char *cmdbuf)
1084 int headers_only = 0;
1086 msgid = extract_long(cmdbuf, 0);
1087 headers_only = extract_int(cmdbuf, 1);
1089 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1095 * display a message (mode 2 - RFC822)
1097 void cmd_msg2(char *cmdbuf)
1100 int headers_only = 0;
1102 msgid = extract_long(cmdbuf, 0);
1103 headers_only = extract_int(cmdbuf, 1);
1105 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1111 * display a message (mode 3 - IGnet raw format - internal programs only)
1113 void cmd_msg3(char *cmdbuf)
1116 struct CtdlMessage *msg;
1119 if (CC->internal_pgm == 0) {
1120 cprintf("%d This command is for internal programs only.\n",
1125 msgnum = extract_long(cmdbuf, 0);
1126 msg = CtdlFetchMessage(msgnum);
1128 cprintf("%d Message %ld not found.\n",
1133 serialize_message(&smr, msg);
1134 CtdlFreeMessage(msg);
1137 cprintf("%d Unable to serialize message\n",
1138 ERROR+INTERNAL_ERROR);
1142 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1143 client_write(smr.ser, smr.len);
1150 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1152 void cmd_msg4(char *cmdbuf)
1156 msgid = extract_long(cmdbuf, 0);
1157 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1161 * Open a component of a MIME message as a download file
1163 void cmd_opna(char *cmdbuf)
1167 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1169 msgid = extract_long(cmdbuf, 0);
1170 extract(desired_section, cmdbuf, 1);
1172 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1177 * Save a message pointer into a specified room
1178 * (Returns 0 for success, nonzero for failure)
1179 * roomname may be NULL to use the current room
1181 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1183 char hold_rm[ROOMNAMELEN];
1184 struct cdbdata *cdbfr;
1187 long highest_msg = 0L;
1188 struct CtdlMessage *msg = NULL;
1190 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1191 roomname, msgid, flags);
1193 strcpy(hold_rm, CC->quickroom.QRname);
1195 /* We may need to check to see if this message is real */
1196 if ( (flags & SM_VERIFY_GOODNESS)
1197 || (flags & SM_DO_REPL_CHECK)
1199 msg = CtdlFetchMessage(msgid);
1200 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1203 /* Perform replication checks if necessary */
1204 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1206 if (getroom(&CC->quickroom,
1207 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1209 lprintf(9, "No such room <%s>\n", roomname);
1210 if (msg != NULL) CtdlFreeMessage(msg);
1211 return(ERROR + ROOM_NOT_FOUND);
1214 if (ReplicationChecks(msg) != 0) {
1215 getroom(&CC->quickroom, hold_rm);
1216 if (msg != NULL) CtdlFreeMessage(msg);
1217 lprintf(9, "Did replication, and newer exists\n");
1222 /* Now the regular stuff */
1223 if (lgetroom(&CC->quickroom,
1224 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1226 lprintf(9, "No such room <%s>\n", roomname);
1227 if (msg != NULL) CtdlFreeMessage(msg);
1228 return(ERROR + ROOM_NOT_FOUND);
1231 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1232 if (cdbfr == NULL) {
1236 msglist = mallok(cdbfr->len);
1237 if (msglist == NULL)
1238 lprintf(3, "ERROR malloc msglist!\n");
1239 num_msgs = cdbfr->len / sizeof(long);
1240 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1245 /* Make sure the message doesn't already exist in this room. It
1246 * is absolutely taboo to have more than one reference to the same
1247 * message in a room.
1249 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1250 if (msglist[i] == msgid) {
1251 lputroom(&CC->quickroom); /* unlock the room */
1252 getroom(&CC->quickroom, hold_rm);
1253 if (msg != NULL) CtdlFreeMessage(msg);
1254 return(ERROR + ALREADY_EXISTS);
1258 /* Now add the new message */
1260 msglist = reallok(msglist,
1261 (num_msgs * sizeof(long)));
1263 if (msglist == NULL) {
1264 lprintf(3, "ERROR: can't realloc message list!\n");
1266 msglist[num_msgs - 1] = msgid;
1268 /* Sort the message list, so all the msgid's are in order */
1269 num_msgs = sort_msglist(msglist, num_msgs);
1271 /* Determine the highest message number */
1272 highest_msg = msglist[num_msgs - 1];
1274 /* Write it back to disk. */
1275 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1276 msglist, num_msgs * sizeof(long));
1278 /* Free up the memory we used. */
1281 /* Update the highest-message pointer and unlock the room. */
1282 CC->quickroom.QRhighest = highest_msg;
1283 lputroom(&CC->quickroom);
1284 getroom(&CC->quickroom, hold_rm);
1286 /* Bump the reference count for this message. */
1287 if ((flags & SM_DONT_BUMP_REF)==0) {
1288 AdjRefCount(msgid, +1);
1291 /* Return success. */
1292 if (msg != NULL) CtdlFreeMessage(msg);
1299 * Message base operation to send a message to the master file
1300 * (returns new message number)
1302 * This is the back end for CtdlSaveMsg() and should not be directly
1303 * called by server-side modules.
1306 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1307 int generate_id, /* generate 'I' field? */
1308 FILE *save_a_copy) /* save a copy to disk? */
1315 /* Get a new message number */
1316 newmsgid = get_new_message_number();
1317 sprintf(msgidbuf, "%ld", newmsgid);
1320 msg->cm_fields['I'] = strdoop(msgidbuf);
1323 serialize_message(&smr, msg);
1326 cprintf("%d Unable to serialize message\n",
1327 ERROR+INTERNAL_ERROR);
1331 /* Write our little bundle of joy into the message base */
1332 begin_critical_section(S_MSGMAIN);
1333 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1334 smr.ser, smr.len) < 0) {
1335 lprintf(2, "Can't store message\n");
1340 end_critical_section(S_MSGMAIN);
1342 /* If the caller specified that a copy should be saved to a particular
1343 * file handle, do that now too.
1345 if (save_a_copy != NULL) {
1346 fwrite(smr.ser, smr.len, 1, save_a_copy);
1349 /* Free the memory we used for the serialized message */
1352 /* Return the *local* message ID to the caller
1353 * (even if we're storing an incoming network message)
1361 * Serialize a struct CtdlMessage into the format used on disk and network.
1363 * This function loads up a "struct ser_ret" (defined in server.h) which
1364 * contains the length of the serialized message and a pointer to the
1365 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1367 void serialize_message(struct ser_ret *ret, /* return values */
1368 struct CtdlMessage *msg) /* unserialized msg */
1372 static char *forder = FORDER;
1374 if (is_valid_message(msg) == 0) return; /* self check */
1377 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1378 ret->len = ret->len +
1379 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1381 lprintf(9, "calling malloc\n");
1382 ret->ser = mallok(ret->len);
1383 if (ret->ser == NULL) {
1389 ret->ser[1] = msg->cm_anon_type;
1390 ret->ser[2] = msg->cm_format_type;
1393 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1394 ret->ser[wlen++] = (char)forder[i];
1395 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1396 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1398 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1407 * Back end for the ReplicationChecks() function
1409 void check_repl(long msgnum) {
1410 struct CtdlMessage *msg;
1411 time_t timestamp = (-1L);
1413 lprintf(9, "check_repl() found message %ld\n", msgnum);
1414 msg = CtdlFetchMessage(msgnum);
1415 if (msg == NULL) return;
1416 if (msg->cm_fields['T'] != NULL) {
1417 timestamp = atol(msg->cm_fields['T']);
1419 CtdlFreeMessage(msg);
1421 if (timestamp > msg_repl->highest) {
1422 msg_repl->highest = timestamp; /* newer! */
1423 lprintf(9, "newer!\n");
1426 lprintf(9, "older!\n");
1428 /* Existing isn't newer? Then delete the old one(s). */
1429 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL);
1434 * Check to see if any messages already exist which carry the same Extended ID
1438 * -> With older timestamps: delete them and return 0. Message will be saved.
1439 * -> With newer timestamps: return 1. Message save will be aborted.
1441 int ReplicationChecks(struct CtdlMessage *msg) {
1442 struct CtdlMessage *template;
1445 lprintf(9, "ReplicationChecks() started\n");
1446 /* No extended id? Don't do anything. */
1447 if (msg->cm_fields['E'] == NULL) return 0;
1448 if (strlen(msg->cm_fields['E']) == 0) return 0;
1449 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1451 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1452 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1453 msg_repl->highest = atol(msg->cm_fields['T']);
1455 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1456 memset(template, 0, sizeof(struct CtdlMessage));
1457 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1459 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl);
1461 /* If a newer message exists with the same Extended ID, abort
1464 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1468 CtdlFreeMessage(template);
1469 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1477 * Save a message to disk
1479 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1480 char *rec, /* Recipient (mail) */
1481 char *force, /* force a particular room? */
1482 int supplied_mailtype, /* local or remote type */
1483 int generate_id) /* 1 = generate 'I' field */
1486 char hold_rm[ROOMNAMELEN];
1487 char actual_rm[ROOMNAMELEN];
1488 char force_room[ROOMNAMELEN];
1489 char content_type[256]; /* We have to learn this */
1490 char recipient[256];
1493 struct usersupp userbuf;
1495 struct SuppMsgInfo smi;
1496 FILE *network_fp = NULL;
1497 static int seqnum = 1;
1498 struct CtdlMessage *imsg;
1502 lprintf(9, "CtdlSaveMsg() called\n");
1503 if (is_valid_message(msg) == 0) return(-1); /* self check */
1504 mailtype = supplied_mailtype;
1506 /* If this message has no timestamp, we take the liberty of
1507 * giving it one, right now.
1509 if (msg->cm_fields['T'] == NULL) {
1510 lprintf(9, "Generating timestamp\n");
1511 sprintf(aaa, "%ld", time(NULL));
1512 msg->cm_fields['T'] = strdoop(aaa);
1515 /* If this message has no path, we generate one.
1517 if (msg->cm_fields['P'] == NULL) {
1518 lprintf(9, "Generating path\n");
1519 if (msg->cm_fields['A'] != NULL) {
1520 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1521 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1522 if (isspace(msg->cm_fields['P'][a])) {
1523 msg->cm_fields['P'][a] = ' ';
1528 msg->cm_fields['P'] = strdoop("unknown");
1532 strcpy(force_room, force);
1534 /* Strip non-printable characters out of the recipient name */
1535 lprintf(9, "Checking recipient (if present)\n");
1536 strcpy(recipient, rec);
1537 for (a = 0; a < strlen(recipient); ++a)
1538 if (!isprint(recipient[a]))
1539 strcpy(&recipient[a], &recipient[a + 1]);
1541 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1542 for (a=0; a<strlen(recipient); ++a) {
1543 if (recipient[a] == '@') {
1544 if (CtdlHostAlias(&recipient[a+1])
1545 == hostalias_localhost) {
1547 lprintf(7, "Changed to <%s>\n", recipient);
1548 mailtype = MES_LOCAL;
1553 lprintf(9, "Recipient is <%s>\n", recipient);
1555 /* Learn about what's inside, because it's what's inside that counts */
1556 lprintf(9, "Learning what's inside\n");
1557 if (msg->cm_fields['M'] == NULL) {
1558 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1561 switch (msg->cm_format_type) {
1563 strcpy(content_type, "text/x-citadel-variformat");
1566 strcpy(content_type, "text/plain");
1569 strcpy(content_type, "text/plain");
1570 /* advance past header fields */
1571 mptr = msg->cm_fields['M'];
1574 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1575 safestrncpy(content_type, mptr,
1576 sizeof(content_type));
1577 strcpy(content_type, &content_type[14]);
1578 for (a = 0; a < strlen(content_type); ++a)
1579 if ((content_type[a] == ';')
1580 || (content_type[a] == ' ')
1581 || (content_type[a] == 13)
1582 || (content_type[a] == 10))
1583 content_type[a] = 0;
1590 /* Goto the correct room */
1591 lprintf(9, "Switching rooms\n");
1592 strcpy(hold_rm, CC->quickroom.QRname);
1593 strcpy(actual_rm, CC->quickroom.QRname);
1595 /* If the user is a twit, move to the twit room for posting */
1596 lprintf(9, "Handling twit stuff\n");
1598 if (CC->usersupp.axlevel == 2) {
1599 strcpy(hold_rm, actual_rm);
1600 strcpy(actual_rm, config.c_twitroom);
1604 /* ...or if this message is destined for Aide> then go there. */
1605 if (strlen(force_room) > 0) {
1606 strcpy(actual_rm, force_room);
1609 lprintf(9, "Possibly relocating\n");
1610 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1611 getroom(&CC->quickroom, actual_rm);
1615 * If this message has no O (room) field, generate one.
1617 if (msg->cm_fields['O'] == NULL) {
1618 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1621 /* Perform "before save" hooks (aborting if any return nonzero) */
1622 lprintf(9, "Performing before-save hooks\n");
1623 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1625 /* If this message has an Extended ID, perform replication checks */
1626 lprintf(9, "Performing replication checks\n");
1627 if (ReplicationChecks(msg) > 0) return(-1);
1629 /* Network mail - send a copy to the network program. */
1630 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1631 lprintf(9, "Sending network spool\n");
1632 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1633 (long) getpid(), CC->cs_pid, ++seqnum);
1634 lprintf(9, "Saving a copy to %s\n", aaa);
1635 network_fp = fopen(aaa, "ab+");
1636 if (network_fp == NULL)
1637 lprintf(2, "ERROR: %s\n", strerror(errno));
1640 /* Save it to disk */
1641 lprintf(9, "Saving to disk\n");
1642 newmsgid = send_message(msg, generate_id, network_fp);
1643 if (network_fp != NULL) {
1645 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1648 if (newmsgid <= 0L) return(-1);
1650 /* Write a supplemental message info record. This doesn't have to
1651 * be a critical section because nobody else knows about this message
1654 lprintf(9, "Creating SuppMsgInfo record\n");
1655 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1656 smi.smi_msgnum = newmsgid;
1657 smi.smi_refcount = 0;
1658 safestrncpy(smi.smi_content_type, content_type, 64);
1659 PutSuppMsgInfo(&smi);
1661 /* Now figure out where to store the pointers */
1662 lprintf(9, "Storing pointers\n");
1664 /* If this is being done by the networker delivering a private
1665 * message, we want to BYPASS saving the sender's copy (because there
1666 * is no local sender; it would otherwise go to the Trashcan).
1668 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1669 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1670 lprintf(3, "ERROR saving message pointer!\n");
1671 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1675 /* For internet mail, drop a copy in the outbound queue room */
1676 if (mailtype == MES_INTERNET) {
1677 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1680 /* Bump this user's messages posted counter. */
1681 lprintf(9, "Updating user\n");
1682 lgetuser(&CC->usersupp, CC->curr_user);
1683 CC->usersupp.posted = CC->usersupp.posted + 1;
1684 lputuser(&CC->usersupp);
1686 /* If this is private, local mail, make a copy in the
1687 * recipient's mailbox and bump the reference count.
1689 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1690 if (getuser(&userbuf, recipient) == 0) {
1691 lprintf(9, "Delivering private mail\n");
1692 MailboxName(actual_rm, &userbuf, MAILROOM);
1693 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1696 lprintf(9, "No user <%s>, saving in %s> instead\n",
1697 recipient, AIDEROOM);
1698 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1702 /* Perform "after save" hooks */
1703 lprintf(9, "Performing after-save hooks\n");
1704 PerformMessageHooks(msg, EVT_AFTERSAVE);
1707 lprintf(9, "Returning to original room\n");
1708 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1709 getroom(&CC->quickroom, hold_rm);
1711 /* For internet mail, generate delivery instructions
1712 * (Yes, this is recursive! Deal with it!)
1714 if (mailtype == MES_INTERNET) {
1715 lprintf(9, "Generating delivery instructions\n");
1716 instr = mallok(2048);
1718 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1721 SPOOLMIME, newmsgid, time(NULL),
1722 msg->cm_fields['A'], msg->cm_fields['N'],
1725 imsg = mallok(sizeof(struct CtdlMessage));
1726 memset(imsg, 0, sizeof(struct CtdlMessage));
1727 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1728 imsg->cm_anon_type = MES_NORMAL;
1729 imsg->cm_format_type = FMT_RFC822;
1730 imsg->cm_fields['A'] = strdoop("Citadel");
1731 imsg->cm_fields['M'] = instr;
1732 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1733 CtdlFreeMessage(imsg);
1742 * Convenience function for generating small administrative messages.
1744 void quickie_message(char *from, char *to, char *room, char *text)
1746 struct CtdlMessage *msg;
1748 msg = mallok(sizeof(struct CtdlMessage));
1749 memset(msg, 0, sizeof(struct CtdlMessage));
1750 msg->cm_magic = CTDLMESSAGE_MAGIC;
1751 msg->cm_anon_type = MES_NORMAL;
1752 msg->cm_format_type = 0;
1753 msg->cm_fields['A'] = strdoop(from);
1754 msg->cm_fields['O'] = strdoop(room);
1755 msg->cm_fields['N'] = strdoop(NODENAME);
1757 msg->cm_fields['R'] = strdoop(to);
1758 msg->cm_fields['M'] = strdoop(text);
1760 CtdlSaveMsg(msg, "", room, MES_LOCAL, 1);
1761 CtdlFreeMessage(msg);
1762 syslog(LOG_NOTICE, text);
1768 * Back end function used by make_message() and similar functions
1770 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1771 size_t maxlen, /* maximum message length */
1772 char *exist /* if non-null, append to it;
1773 exist is ALWAYS freed */
1776 size_t message_len = 0;
1777 size_t buffer_len = 0;
1781 if (exist == NULL) {
1785 m = reallok(exist, strlen(exist) + 4096);
1786 if (m == NULL) phree(exist);
1789 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1796 /* read in the lines of message text one by one */
1798 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1800 /* augment the buffer if we have to */
1801 if ((message_len + strlen(buf) + 2) > buffer_len) {
1802 lprintf(9, "realloking\n");
1803 ptr = reallok(m, (buffer_len * 2) );
1804 if (ptr == NULL) { /* flush if can't allocate */
1805 while ( (client_gets(buf)>0) &&
1806 strcmp(buf, terminator)) ;;
1809 buffer_len = (buffer_len * 2);
1812 lprintf(9, "buffer_len is %d\n", buffer_len);
1816 if (append == NULL) append = m;
1817 while (strlen(append) > 0) ++append;
1818 strcpy(append, buf);
1819 strcat(append, "\n");
1820 message_len = message_len + strlen(buf) + 1;
1822 /* if we've hit the max msg length, flush the rest */
1823 if (message_len >= maxlen) {
1824 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1835 * Build a binary message to be saved on disk.
1838 struct CtdlMessage *make_message(
1839 struct usersupp *author, /* author's usersupp structure */
1840 char *recipient, /* NULL if it's not mail */
1841 char *room, /* room where it's going */
1842 int type, /* see MES_ types in header file */
1843 int net_type, /* see MES_ types in header file */
1844 int format_type, /* local or remote (see citadel.h) */
1845 char *fake_name) /* who we're masquerading as */
1851 struct CtdlMessage *msg;
1853 msg = mallok(sizeof(struct CtdlMessage));
1854 memset(msg, 0, sizeof(struct CtdlMessage));
1855 msg->cm_magic = CTDLMESSAGE_MAGIC;
1856 msg->cm_anon_type = type;
1857 msg->cm_format_type = format_type;
1859 /* Don't confuse the poor folks if it's not routed mail. */
1860 strcpy(dest_node, "");
1862 /* If net_type is MES_BINARY, split out the destination node. */
1863 if (net_type == MES_BINARY) {
1864 strcpy(dest_node, NODENAME);
1865 for (a = 0; a < strlen(recipient); ++a) {
1866 if (recipient[a] == '@') {
1868 strcpy(dest_node, &recipient[a + 1]);
1873 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1874 if (net_type == MES_INTERNET) {
1875 strcpy(dest_node, "internet");
1878 while (isspace(recipient[strlen(recipient) - 1]))
1879 recipient[strlen(recipient) - 1] = 0;
1881 sprintf(buf, "cit%ld", author->usernum); /* Path */
1882 msg->cm_fields['P'] = strdoop(buf);
1884 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1885 msg->cm_fields['T'] = strdoop(buf);
1887 if (fake_name[0]) /* author */
1888 msg->cm_fields['A'] = strdoop(fake_name);
1890 msg->cm_fields['A'] = strdoop(author->fullname);
1892 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1893 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1895 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1897 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1898 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1900 if (recipient[0] != 0)
1901 msg->cm_fields['R'] = strdoop(recipient);
1902 if (dest_node[0] != 0)
1903 msg->cm_fields['D'] = strdoop(dest_node);
1906 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1907 config.c_maxmsglen, NULL);
1918 * message entry - mode 0 (normal)
1920 void cmd_ent0(char *entargs)
1923 char recipient[256];
1925 int format_type = 0;
1926 char newusername[256];
1927 struct CtdlMessage *msg;
1931 struct usersupp tempUS;
1934 post = extract_int(entargs, 0);
1935 extract(recipient, entargs, 1);
1936 anon_flag = extract_int(entargs, 2);
1937 format_type = extract_int(entargs, 3);
1939 /* first check to make sure the request is valid. */
1941 if (!(CC->logged_in)) {
1942 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1945 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1946 cprintf("%d Need to be validated to enter ",
1947 ERROR + HIGHER_ACCESS_REQUIRED);
1948 cprintf("(except in %s> to sysop)\n", MAILROOM);
1951 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1952 cprintf("%d Need net privileges to enter here.\n",
1953 ERROR + HIGHER_ACCESS_REQUIRED);
1956 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1957 cprintf("%d Sorry, this is a read-only room.\n",
1958 ERROR + HIGHER_ACCESS_REQUIRED);
1965 if (CC->usersupp.axlevel < 6) {
1966 cprintf("%d You don't have permission to masquerade.\n",
1967 ERROR + HIGHER_ACCESS_REQUIRED);
1970 extract(newusername, entargs, 4);
1971 memset(CC->fake_postname, 0, 32);
1972 strcpy(CC->fake_postname, newusername);
1973 cprintf("%d Ok\n", OK);
1976 CC->cs_flags |= CS_POSTING;
1979 if (CC->quickroom.QRflags & QR_MAILBOX) {
1980 if (CC->usersupp.axlevel >= 2) {
1981 strcpy(buf, recipient);
1983 strcpy(buf, "sysop");
1984 e = alias(buf); /* alias and mail type */
1985 if ((buf[0] == 0) || (e == MES_ERROR)) {
1986 cprintf("%d Unknown address - cannot send message.\n",
1987 ERROR + NO_SUCH_USER);
1990 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
1991 cprintf("%d Net privileges required for network mail.\n",
1992 ERROR + HIGHER_ACCESS_REQUIRED);
1995 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
1996 && ((CC->usersupp.flags & US_INTERNET) == 0)
1997 && (!CC->internal_pgm)) {
1998 cprintf("%d You don't have access to Internet mail.\n",
1999 ERROR + HIGHER_ACCESS_REQUIRED);
2002 if (!strcasecmp(buf, "sysop")) {
2007 goto SKFALL; /* don't search local file */
2008 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2009 cprintf("%d Can't send mail to yourself!\n",
2010 ERROR + NO_SUCH_USER);
2013 /* Check to make sure the user exists; also get the correct
2014 * upper/lower casing of the name.
2016 a = getuser(&tempUS, buf);
2018 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2021 strcpy(buf, tempUS.fullname);
2024 SKFALL: b = MES_NORMAL;
2025 if (CC->quickroom.QRflags & QR_ANONONLY)
2027 if (CC->quickroom.QRflags & QR_ANONOPT) {
2031 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2034 /* If we're only checking the validity of the request, return
2035 * success without creating the message.
2038 cprintf("%d %s\n", OK, buf);
2042 cprintf("%d send message\n", SEND_LISTING);
2044 /* Read in the message from the client. */
2045 if (CC->fake_postname[0])
2046 msg = make_message(&CC->usersupp, buf,
2047 CC->quickroom.QRname, b, e, format_type,
2049 else if (CC->fake_username[0])
2050 msg = make_message(&CC->usersupp, buf,
2051 CC->quickroom.QRname, b, e, format_type,
2054 msg = make_message(&CC->usersupp, buf,
2055 CC->quickroom.QRname, b, e, format_type, "");
2058 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1);
2059 CtdlFreeMessage(msg);
2060 CC->fake_postname[0] = '\0';
2067 * message entry - mode 3 (raw)
2069 void cmd_ent3(char *entargs)
2075 unsigned char ch, which_field;
2076 struct usersupp tempUS;
2078 struct CtdlMessage *msg;
2081 if (CC->internal_pgm == 0) {
2082 cprintf("%d This command is for internal programs only.\n",
2087 /* See if there's a recipient, but make sure it's a real one */
2088 extract(recp, entargs, 1);
2089 for (a = 0; a < strlen(recp); ++a)
2090 if (!isprint(recp[a]))
2091 strcpy(&recp[a], &recp[a + 1]);
2092 while (isspace(recp[0]))
2093 strcpy(recp, &recp[1]);
2094 while (isspace(recp[strlen(recp) - 1]))
2095 recp[strlen(recp) - 1] = 0;
2097 /* If we're in Mail, check the recipient */
2098 if (strlen(recp) > 0) {
2099 e = alias(recp); /* alias and mail type */
2100 if ((recp[0] == 0) || (e == MES_ERROR)) {
2101 cprintf("%d Unknown address - cannot send message.\n",
2102 ERROR + NO_SUCH_USER);
2105 if (e == MES_LOCAL) {
2106 a = getuser(&tempUS, recp);
2108 cprintf("%d No such user.\n",
2109 ERROR + NO_SUCH_USER);
2115 /* At this point, message has been approved. */
2116 if (extract_int(entargs, 0) == 0) {
2117 cprintf("%d OK to send\n", OK);
2121 msglen = extract_long(entargs, 2);
2122 msg = mallok(sizeof(struct CtdlMessage));
2124 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2128 memset(msg, 0, sizeof(struct CtdlMessage));
2129 tempbuf = mallok(msglen);
2130 if (tempbuf == NULL) {
2131 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2136 cprintf("%d %ld\n", SEND_BINARY, msglen);
2138 client_read(&ch, 1); /* 0xFF magic number */
2139 msg->cm_magic = CTDLMESSAGE_MAGIC;
2140 client_read(&ch, 1); /* anon type */
2141 msg->cm_anon_type = ch;
2142 client_read(&ch, 1); /* format type */
2143 msg->cm_format_type = ch;
2144 msglen = msglen - 3;
2146 while (msglen > 0) {
2147 client_read(&which_field, 1);
2148 if (!isalpha(which_field)) valid_msg = 0;
2152 client_read(&ch, 1);
2154 a = strlen(tempbuf);
2157 } while ( (ch != 0) && (msglen > 0) );
2159 msg->cm_fields[which_field] = strdoop(tempbuf);
2162 msg->cm_flags = CM_SKIP_HOOKS;
2163 if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0);
2164 CtdlFreeMessage(msg);
2170 * API function to delete messages which match a set of criteria
2171 * (returns the actual number of messages deleted)
2173 int CtdlDeleteMessages(char *room_name, /* which room */
2174 long dmsgnum, /* or "0" for any */
2175 char *content_type /* or NULL for any */
2179 struct quickroom qrbuf;
2180 struct cdbdata *cdbfr;
2181 long *msglist = NULL;
2184 int num_deleted = 0;
2186 struct SuppMsgInfo smi;
2188 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2189 room_name, dmsgnum, content_type);
2191 /* get room record, obtaining a lock... */
2192 if (lgetroom(&qrbuf, room_name) != 0) {
2193 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2195 return (0); /* room not found */
2197 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2199 if (cdbfr != NULL) {
2200 msglist = mallok(cdbfr->len);
2201 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2202 num_msgs = cdbfr->len / sizeof(long);
2206 for (i = 0; i < num_msgs; ++i) {
2209 /* Set/clear a bit for each criterion */
2211 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2212 delete_this |= 0x01;
2214 if (content_type == NULL) {
2215 delete_this |= 0x02;
2217 GetSuppMsgInfo(&smi, msglist[i]);
2218 if (!strcasecmp(smi.smi_content_type,
2220 delete_this |= 0x02;
2224 /* Delete message only if all bits are set */
2225 if (delete_this == 0x03) {
2226 AdjRefCount(msglist[i], -1);
2232 num_msgs = sort_msglist(msglist, num_msgs);
2233 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2234 msglist, (num_msgs * sizeof(long)));
2236 qrbuf.QRhighest = msglist[num_msgs - 1];
2240 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2241 return (num_deleted);
2247 * Delete message from current room
2249 void cmd_dele(char *delstr)
2254 getuser(&CC->usersupp, CC->curr_user);
2255 if ((CC->usersupp.axlevel < 6)
2256 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2257 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2258 && (!(CC->internal_pgm))) {
2259 cprintf("%d Higher access required.\n",
2260 ERROR + HIGHER_ACCESS_REQUIRED);
2263 delnum = extract_long(delstr, 0);
2265 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL);
2268 cprintf("%d %d message%s deleted.\n", OK,
2269 num_deleted, ((num_deleted != 1) ? "s" : ""));
2271 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2277 * move or copy a message to another room
2279 void cmd_move(char *args)
2283 struct quickroom qtemp;
2287 num = extract_long(args, 0);
2288 extract(targ, args, 1);
2289 targ[ROOMNAMELEN - 1] = 0;
2290 is_copy = extract_int(args, 2);
2292 getuser(&CC->usersupp, CC->curr_user);
2293 if ((CC->usersupp.axlevel < 6)
2294 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2295 cprintf("%d Higher access required.\n",
2296 ERROR + HIGHER_ACCESS_REQUIRED);
2300 if (getroom(&qtemp, targ) != 0) {
2301 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2305 err = CtdlSaveMsgPointerInRoom(targ, num,
2306 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2308 cprintf("%d Cannot store message in %s: error %d\n",
2313 /* Now delete the message from the source room,
2314 * if this is a 'move' rather than a 'copy' operation.
2316 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL);
2318 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2324 * GetSuppMsgInfo() - Get the supplementary record for a message
2326 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2329 struct cdbdata *cdbsmi;
2332 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2333 smibuf->smi_msgnum = msgnum;
2334 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2336 /* Use the negative of the message number for its supp record index */
2337 TheIndex = (0L - msgnum);
2339 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2340 if (cdbsmi == NULL) {
2341 return; /* record not found; go with defaults */
2343 memcpy(smibuf, cdbsmi->ptr,
2344 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2345 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2352 * PutSuppMsgInfo() - (re)write supplementary record for a message
2354 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2358 /* Use the negative of the message number for its supp record index */
2359 TheIndex = (0L - smibuf->smi_msgnum);
2361 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2362 smibuf->smi_msgnum, smibuf->smi_refcount);
2364 cdb_store(CDB_MSGMAIN,
2365 &TheIndex, sizeof(long),
2366 smibuf, sizeof(struct SuppMsgInfo));
2371 * AdjRefCount - change the reference count for a message;
2372 * delete the message if it reaches zero
2374 void AdjRefCount(long msgnum, int incr)
2377 struct SuppMsgInfo smi;
2380 /* This is a *tight* critical section; please keep it that way, as
2381 * it may get called while nested in other critical sections.
2382 * Complicating this any further will surely cause deadlock!
2384 begin_critical_section(S_SUPPMSGMAIN);
2385 GetSuppMsgInfo(&smi, msgnum);
2386 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2387 msgnum, smi.smi_refcount);
2388 smi.smi_refcount += incr;
2389 PutSuppMsgInfo(&smi);
2390 end_critical_section(S_SUPPMSGMAIN);
2391 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2392 msgnum, smi.smi_refcount);
2394 /* If the reference count is now zero, delete the message
2395 * (and its supplementary record as well).
2397 if (smi.smi_refcount == 0) {
2398 lprintf(9, "Deleting message <%ld>\n", msgnum);
2400 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2401 delnum = (0L - msgnum);
2402 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2407 * Write a generic object to this room
2409 * Note: this could be much more efficient. Right now we use two temporary
2410 * files, and still pull the message into memory as with all others.
2412 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2413 char *content_type, /* MIME type of this object */
2414 char *tempfilename, /* Where to fetch it from */
2415 struct usersupp *is_mailbox, /* Mailbox room? */
2416 int is_binary, /* Is encoding necessary? */
2417 int is_unique, /* Del others of this type? */
2418 unsigned int flags /* Internal save flags */
2423 char filename[PATH_MAX];
2426 struct quickroom qrbuf;
2427 char roomname[ROOMNAMELEN];
2428 struct CtdlMessage *msg;
2431 if (is_mailbox != NULL)
2432 MailboxName(roomname, is_mailbox, req_room);
2434 safestrncpy(roomname, req_room, sizeof(roomname));
2435 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2437 strcpy(filename, tmpnam(NULL));
2438 fp = fopen(filename, "w");
2442 tempfp = fopen(tempfilename, "r");
2443 if (tempfp == NULL) {
2449 fprintf(fp, "Content-type: %s\n", content_type);
2450 lprintf(9, "Content-type: %s\n", content_type);
2452 if (is_binary == 0) {
2453 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2454 while (ch = getc(tempfp), ch > 0)
2460 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2463 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2464 tempfilename, filename);
2468 lprintf(9, "Allocating\n");
2469 msg = mallok(sizeof(struct CtdlMessage));
2470 memset(msg, 0, sizeof(struct CtdlMessage));
2471 msg->cm_magic = CTDLMESSAGE_MAGIC;
2472 msg->cm_anon_type = MES_NORMAL;
2473 msg->cm_format_type = 4;
2474 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2475 msg->cm_fields['O'] = strdoop(req_room);
2476 msg->cm_fields['N'] = strdoop(config.c_nodename);
2477 msg->cm_fields['H'] = strdoop(config.c_humannode);
2478 msg->cm_flags = flags;
2480 lprintf(9, "Loading\n");
2481 fp = fopen(filename, "rb");
2482 fseek(fp, 0L, SEEK_END);
2485 msg->cm_fields['M'] = mallok(len);
2486 fread(msg->cm_fields['M'], len, 1, fp);
2490 /* Create the requested room if we have to. */
2491 if (getroom(&qrbuf, roomname) != 0) {
2492 create_room(roomname,
2493 ( (is_mailbox != NULL) ? 4 : 3 ),
2496 /* If the caller specified this object as unique, delete all
2497 * other objects of this type that are currently in the room.
2500 lprintf(9, "Deleted %d other msgs of this type\n",
2501 CtdlDeleteMessages(roomname, 0L, content_type));
2503 /* Now write the data */
2504 CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1);
2505 CtdlFreeMessage(msg);
2513 void CtdlGetSysConfigBackend(long msgnum) {
2514 config_msgnum = msgnum;
2518 char *CtdlGetSysConfig(char *sysconfname) {
2519 char hold_rm[ROOMNAMELEN];
2522 struct CtdlMessage *msg;
2525 strcpy(hold_rm, CC->quickroom.QRname);
2526 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2527 getroom(&CC->quickroom, hold_rm);
2532 /* We want the last (and probably only) config in this room */
2533 begin_critical_section(S_CONFIG);
2534 config_msgnum = (-1L);
2535 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2536 CtdlGetSysConfigBackend);
2537 msgnum = config_msgnum;
2538 end_critical_section(S_CONFIG);
2544 msg = CtdlFetchMessage(msgnum);
2546 conf = strdoop(msg->cm_fields['M']);
2547 CtdlFreeMessage(msg);
2554 getroom(&CC->quickroom, hold_rm);
2556 lprintf(9, "eggstracting...\n");
2557 if (conf != NULL) do {
2558 extract_token(buf, conf, 0, '\n');
2559 lprintf(9, "eggstracted <%s>\n", buf);
2560 strcpy(conf, &conf[strlen(buf)+1]);
2561 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2566 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2567 char temp[PATH_MAX];
2570 strcpy(temp, tmpnam(NULL));
2572 fp = fopen(temp, "w");
2573 if (fp == NULL) return;
2574 fprintf(fp, "%s", sysconfdata);
2577 /* this handy API function does all the work for us */
2578 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);