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];
108 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
111 fp = fopen("network/mail.aliases", "r");
113 fp = fopen("/dev/null", "r");
119 while (fgets(aaa, sizeof aaa, fp) != NULL) {
120 while (isspace(name[0]))
121 strcpy(name, &name[1]);
122 aaa[strlen(aaa) - 1] = 0;
124 for (a = 0; a < strlen(aaa); ++a) {
126 strcpy(bbb, &aaa[a + 1]);
130 if (!strcasecmp(name, aaa))
135 lprintf(7, "Mail is being forwarded to %s\n", name);
137 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
138 for (a=0; a<strlen(name); ++a) {
139 if (name[a] == '@') {
140 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
142 lprintf(7, "Changed to <%s>\n", name);
147 /* determine local or remote type, see citadel.h */
149 for (a = 0; a < strlen(name); ++a)
151 return (MES_INTERNET);
153 for (a = 0; a < strlen(name); ++a)
155 for (b = a; b < strlen(name); ++b)
157 return (MES_INTERNET);
160 for (a = 0; a < strlen(name); ++a)
164 lprintf(7, "Too many @'s in address\n");
168 for (a = 0; a < strlen(name); ++a)
170 strcpy(bbb, &name[a + 1]);
172 strcpy(bbb, &bbb[1]);
173 fp = fopen("network/mail.sysinfo", "r");
177 a = getstring(fp, aaa);
178 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
180 a = getstring(fp, aaa);
181 if (!strncmp(aaa, "use ", 4)) {
182 strcpy(bbb, &aaa[4]);
188 if (!strncmp(aaa, "uum", 3)) {
190 for (a = 0; a < strlen(bbb); ++a) {
196 while (bbb[strlen(bbb) - 1] == '_')
197 bbb[strlen(bbb) - 1] = 0;
198 sprintf(name, &aaa[4], bbb);
199 lprintf(9, "returning MES_INTERNET\n");
200 return (MES_INTERNET);
202 if (!strncmp(aaa, "bin", 3)) {
205 while (aaa[strlen(aaa) - 1] != '@')
206 aaa[strlen(aaa) - 1] = 0;
207 aaa[strlen(aaa) - 1] = 0;
208 while (aaa[strlen(aaa) - 1] == ' ')
209 aaa[strlen(aaa) - 1] = 0;
210 while (bbb[0] != '@')
211 strcpy(bbb, &bbb[1]);
212 strcpy(bbb, &bbb[1]);
213 while (bbb[0] == ' ')
214 strcpy(bbb, &bbb[1]);
215 sprintf(name, "%s @%s", aaa, bbb);
216 lprintf(9, "returning MES_BINARY\n");
222 lprintf(9, "returning MES_LOCAL\n");
231 fp = fopen("citadel.control", "r");
232 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
238 void simple_listing(long msgnum)
240 cprintf("%ld\n", msgnum);
245 /* Determine if a given message matches the fields in a message template.
246 * Return 0 for a successful match.
248 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
251 /* If there aren't any fields in the template, all messages will
254 if (template == NULL) return(0);
256 /* Null messages are bogus. */
257 if (msg == NULL) return(1);
259 for (i='A'; i<='Z'; ++i) {
260 if (template->cm_fields[i] != NULL) {
261 if (msg->cm_fields[i] == NULL) {
264 if (strcasecmp(msg->cm_fields[i],
265 template->cm_fields[i])) return 1;
269 /* All compares succeeded: we have a match! */
277 * API function to perform an operation for each qualifying message in the
278 * current room. (Returns the number of messages processed.)
280 int CtdlForEachMessage(int mode, long ref,
281 int moderation_level,
283 struct CtdlMessage *compare,
284 void (*CallBack) (long msgnum))
289 struct cdbdata *cdbfr;
290 long *msglist = NULL;
292 int num_processed = 0;
294 struct SuppMsgInfo smi;
295 struct CtdlMessage *msg;
297 /* Learn about the user and room in question */
299 getuser(&CC->usersupp, CC->curr_user);
300 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
302 /* Load the message list */
303 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
305 msglist = mallok(cdbfr->len);
306 memcpy(msglist, cdbfr->ptr, cdbfr->len);
307 num_msgs = cdbfr->len / sizeof(long);
310 return 0; /* No messages at all? No further action. */
314 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
315 GetSuppMsgInfo(&smi, msglist[a]);
317 /* Filter out messages that are moderated below the level
318 * currently being viewed at.
320 if (smi.smi_mod < moderation_level) {
324 /* If the caller is looking for a specific MIME type, filter
325 * out all messages which are not of the type requested.
327 if (content_type != NULL) if (strlen(content_type) > 0) {
328 if (strcasecmp(smi.smi_content_type, content_type)) {
334 num_msgs = sort_msglist(msglist, num_msgs);
336 /* If a template was supplied, filter out the messages which
337 * don't match. (This could induce some delays!)
340 if (compare != NULL) {
341 for (a = 0; a < num_msgs; ++a) {
342 msg = CtdlFetchMessage(msglist[a]);
344 if (CtdlMsgCmp(msg, compare)) {
347 CtdlFreeMessage(msg);
355 * Now iterate through the message list, according to the
356 * criteria supplied by the caller.
359 for (a = 0; a < num_msgs; ++a) {
360 thismsg = msglist[a];
365 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
366 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
367 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
368 && (CC->usersupp.flags & US_LASTOLD))
369 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
370 || ((mode == MSGS_FIRST) && (a < ref))
371 || ((mode == MSGS_GT) && (thismsg > ref))
372 || ((mode == MSGS_EQ) && (thismsg == ref))
375 if (CallBack) CallBack(thismsg);
379 phree(msglist); /* Clean up */
380 return num_processed;
386 * cmd_msgs() - get list of message #'s in this room
387 * implements the MSGS server command using CtdlForEachMessage()
389 void cmd_msgs(char *cmdbuf)
398 int with_template = 0;
399 struct CtdlMessage *template = NULL;
401 extract(which, cmdbuf, 0);
402 cm_ref = extract_int(cmdbuf, 1);
403 with_template = extract_int(cmdbuf, 2);
407 if (!strncasecmp(which, "OLD", 3))
409 else if (!strncasecmp(which, "NEW", 3))
411 else if (!strncasecmp(which, "FIRST", 5))
413 else if (!strncasecmp(which, "LAST", 4))
415 else if (!strncasecmp(which, "GT", 2))
418 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
419 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
424 cprintf("%d Send template then receive message list\n",
426 template = (struct CtdlMessage *)
427 mallok(sizeof(struct CtdlMessage));
428 memset(template, 0, sizeof(struct CtdlMessage));
429 while(client_gets(buf), strcmp(buf,"000")) {
430 extract(tfield, buf, 0);
431 extract(tvalue, buf, 1);
432 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
433 if (!strcasecmp(tfield, msgkeys[i])) {
434 template->cm_fields[i] =
441 cprintf("%d Message list...\n", LISTING_FOLLOWS);
444 CtdlForEachMessage(mode, cm_ref,
445 CC->usersupp.moderation_filter,
446 NULL, template, simple_listing);
447 if (template != NULL) CtdlFreeMessage(template);
455 * help_subst() - support routine for help file viewer
457 void help_subst(char *strbuf, char *source, char *dest)
462 while (p = pattern2(strbuf, source), (p >= 0)) {
463 strcpy(workbuf, &strbuf[p + strlen(source)]);
464 strcpy(&strbuf[p], dest);
465 strcat(strbuf, workbuf);
470 void do_help_subst(char *buffer)
474 help_subst(buffer, "^nodename", config.c_nodename);
475 help_subst(buffer, "^humannode", config.c_humannode);
476 help_subst(buffer, "^fqdn", config.c_fqdn);
477 help_subst(buffer, "^username", CC->usersupp.fullname);
478 sprintf(buf2, "%ld", CC->usersupp.usernum);
479 help_subst(buffer, "^usernum", buf2);
480 help_subst(buffer, "^sysadm", config.c_sysadm);
481 help_subst(buffer, "^variantname", CITADEL);
482 sprintf(buf2, "%d", config.c_maxsessions);
483 help_subst(buffer, "^maxsessions", buf2);
489 * memfmout() - Citadel text formatter and paginator.
490 * Although the original purpose of this routine was to format
491 * text to the reader's screen width, all we're really using it
492 * for here is to format text out to 80 columns before sending it
493 * to the client. The client software may reformat it again.
496 int width, /* screen width to use */
497 char *mptr, /* where are we going to get our text from? */
498 char subst, /* nonzero if we should do substitutions */
499 char *nl) /* string to terminate lines with */
511 c = 1; /* c is the current pos */
514 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
516 buffer[strlen(buffer) + 1] = 0;
517 buffer[strlen(buffer)] = ch;
520 if (buffer[0] == '^')
521 do_help_subst(buffer);
523 buffer[strlen(buffer) + 1] = 0;
525 strcpy(buffer, &buffer[1]);
535 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
537 if (((old == 13) || (old == 10)) && (isspace(real))) {
545 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
546 cprintf("%s%s", nl, aaa);
555 if ((strlen(aaa) + c) > (width - 5)) {
565 if ((ch == 13) || (ch == 10)) {
566 cprintf("%s%s", aaa, nl);
573 FMTEND: cprintf("%s%s", aaa, nl);
579 * Callback function for mime parser that simply lists the part
581 void list_this_part(char *name, char *filename, char *partnum, char *disp,
582 void *content, char *cbtype, size_t length)
585 cprintf("part=%s|%s|%s|%s|%s|%d\n",
586 name, filename, partnum, disp, cbtype, length);
591 * Callback function for mime parser that opens a section for downloading
593 void mime_download(char *name, char *filename, char *partnum, char *disp,
594 void *content, char *cbtype, size_t length)
597 /* Silently go away if there's already a download open... */
598 if (CC->download_fp != NULL)
601 /* ...or if this is not the desired section */
602 if (strcasecmp(desired_section, partnum))
605 CC->download_fp = tmpfile();
606 if (CC->download_fp == NULL)
609 fwrite(content, length, 1, CC->download_fp);
610 fflush(CC->download_fp);
611 rewind(CC->download_fp);
613 OpenCmdResult(filename, cbtype);
619 * Load a message from disk into memory.
620 * This is used by CtdlOutputMsg() and other fetch functions.
622 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
623 * using the CtdlMessageFree() function.
625 struct CtdlMessage *CtdlFetchMessage(long msgnum)
627 struct cdbdata *dmsgtext;
628 struct CtdlMessage *ret = NULL;
631 CIT_UBYTE field_header;
634 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
635 if (dmsgtext == NULL) {
638 mptr = dmsgtext->ptr;
640 /* Parse the three bytes that begin EVERY message on disk.
641 * The first is always 0xFF, the on-disk magic number.
642 * The second is the anonymous/public type byte.
643 * The third is the format type byte (vari, fixed, or MIME).
647 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
651 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
652 memset(ret, 0, sizeof(struct CtdlMessage));
654 ret->cm_magic = CTDLMESSAGE_MAGIC;
655 ret->cm_anon_type = *mptr++; /* Anon type byte */
656 ret->cm_format_type = *mptr++; /* Format type byte */
659 * The rest is zero or more arbitrary fields. Load them in.
660 * We're done when we encounter either a zero-length field or
661 * have just processed the 'M' (message text) field.
664 field_length = strlen(mptr);
665 if (field_length == 0)
667 field_header = *mptr++;
668 ret->cm_fields[field_header] = mallok(field_length);
669 strcpy(ret->cm_fields[field_header], mptr);
671 while (*mptr++ != 0); /* advance to next field */
673 } while ((field_length > 0) && (field_header != 'M'));
677 /* Always make sure there's something in the msg text field */
678 if (ret->cm_fields['M'] == NULL)
679 ret->cm_fields['M'] = strdoop("<no text>\n");
681 /* Perform "before read" hooks (aborting if any return nonzero) */
682 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
683 CtdlFreeMessage(ret);
692 * Returns 1 if the supplied pointer points to a valid Citadel message.
693 * If the pointer is NULL or the magic number check fails, returns 0.
695 int is_valid_message(struct CtdlMessage *msg) {
698 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
699 lprintf(3, "is_valid_message() -- self-check failed\n");
707 * 'Destructor' for struct CtdlMessage
709 void CtdlFreeMessage(struct CtdlMessage *msg)
713 if (is_valid_message(msg) == 0) return;
715 for (i = 0; i < 256; ++i)
716 if (msg->cm_fields[i] != NULL) {
717 phree(msg->cm_fields[i]);
720 msg->cm_magic = 0; /* just in case */
726 * Callback function for mime parser that wants to display text
728 void fixed_output(char *name, char *filename, char *partnum, char *disp,
729 void *content, char *cbtype, size_t length)
736 if (!strcasecmp(cbtype, "multipart/alternative")) {
737 strcpy(ma->prefix, partnum);
738 strcat(ma->prefix, ".");
744 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
746 && (ma->did_print == 1) ) {
747 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
753 if ( (!strcasecmp(cbtype, "text/plain"))
754 || (strlen(cbtype)==0) ) {
759 if (ch==10) cprintf("\r\n");
760 else cprintf("%c", ch);
763 else if (!strcasecmp(cbtype, "text/html")) {
764 ptr = html_to_ascii(content, 80, 0);
769 if (ch==10) cprintf("\r\n");
770 else cprintf("%c", ch);
774 else if (strncasecmp(cbtype, "multipart/", 10)) {
775 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
776 partnum, filename, cbtype, length);
782 * Get a message off disk. (returns om_* values found in msgbase.h)
785 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
786 int mode, /* how would you like that message? */
787 int headers_only, /* eschew the message body? */
788 int do_proto, /* do Citadel protocol responses? */
789 int crlf /* Use CRLF newlines instead of LF? */
795 char display_name[256];
796 struct CtdlMessage *TheMessage;
798 char *nl; /* newline string */
800 /* buffers needed for RFC822 translation */
810 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
814 sprintf(mid, "%ld", msg_num);
815 nl = (crlf ? "\r\n" : "\n");
817 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
818 if (do_proto) cprintf("%d Not logged in.\n",
819 ERROR + NOT_LOGGED_IN);
820 return(om_not_logged_in);
823 /* FIXME ... small security issue
824 * We need to check to make sure the requested message is actually
825 * in the current room, and set msg_ok to 1 only if it is. This
826 * functionality is currently missing because I'm in a hurry to replace
827 * broken production code with nonbroken pre-beta code. :( -- ajc
830 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
832 return(om_no_such_msg);
837 * Fetch the message from disk
839 TheMessage = CtdlFetchMessage(msg_num);
840 if (TheMessage == NULL) {
841 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
843 return(om_no_such_msg);
846 /* Are we downloading a MIME component? */
847 if (mode == MT_DOWNLOAD) {
848 if (TheMessage->cm_format_type != FMT_RFC822) {
850 cprintf("%d This is not a MIME message.\n",
852 } else if (CC->download_fp != NULL) {
853 if (do_proto) cprintf(
854 "%d You already have a download open.\n",
857 /* Parse the message text component */
858 mptr = TheMessage->cm_fields['M'];
859 mime_parser(mptr, NULL, *mime_download);
860 /* If there's no file open by this time, the requested
861 * section wasn't found, so print an error
863 if (CC->download_fp == NULL) {
864 if (do_proto) cprintf(
865 "%d Section %s not found.\n",
866 ERROR + FILE_NOT_FOUND,
870 CtdlFreeMessage(TheMessage);
871 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
874 /* now for the user-mode message reading loops */
875 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
877 /* Tell the client which format type we're using. If this is a
878 * MIME message, *lie* about it and tell the user it's fixed-format.
880 if (mode == MT_CITADEL) {
881 if (TheMessage->cm_format_type == FMT_RFC822) {
882 if (do_proto) cprintf("type=1\n");
885 if (do_proto) cprintf("type=%d\n",
886 TheMessage->cm_format_type);
890 /* nhdr=yes means that we're only displaying headers, no body */
891 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
892 if (do_proto) cprintf("nhdr=yes\n");
895 /* begin header processing loop for Citadel message format */
897 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
899 strcpy(display_name, "<unknown>");
900 if (TheMessage->cm_fields['A']) {
901 strcpy(buf, TheMessage->cm_fields['A']);
902 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
903 if (TheMessage->cm_anon_type == MES_ANON)
904 strcpy(display_name, "****");
905 else if (TheMessage->cm_anon_type == MES_AN2)
906 strcpy(display_name, "anonymous");
908 strcpy(display_name, buf);
910 && ((TheMessage->cm_anon_type == MES_ANON)
911 || (TheMessage->cm_anon_type == MES_AN2))) {
912 sprintf(&display_name[strlen(display_name)],
917 strcpy(allkeys, FORDER);
918 for (i=0; i<strlen(allkeys); ++i) {
919 k = (int) allkeys[i];
921 if (TheMessage->cm_fields[k] != NULL) {
923 if (do_proto) cprintf("%s=%s\n",
928 if (do_proto) cprintf("%s=%s\n",
930 TheMessage->cm_fields[k]
939 /* begin header processing loop for RFC822 transfer format */
944 strcpy(snode, NODENAME);
945 strcpy(lnode, HUMANNODE);
946 if (mode == MT_RFC822) {
947 cprintf("X-UIDL: %ld%s", msg_num, nl);
948 for (i = 0; i < 256; ++i) {
949 if (TheMessage->cm_fields[i]) {
950 mptr = TheMessage->cm_fields[i];
957 cprintf("Path: %s%s", mptr, nl);
960 cprintf("Subject: %s%s", mptr, nl);
966 cprintf("X-Citadel-Room: %s%s",
971 cprintf("To: %s%s", mptr, nl);
973 generate_rfc822_datestamp(datestamp,
975 cprintf("Date: %s%s", datestamp, nl);
981 for (i=0; i<strlen(suser); ++i) {
982 suser[i] = tolower(suser[i]);
983 if (!isalnum(suser[i])) suser[i]='_';
986 if (mode == MT_RFC822) {
987 if (!strcasecmp(snode, NODENAME)) {
991 /* Construct a fun message id */
992 cprintf("Message-ID: <%s", mid);
993 if (strchr(mid, '@')==NULL) {
994 cprintf("@%s", snode);
998 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1000 if (strlen(fuser) > 0) {
1001 cprintf("From: %s (%s)%s", fuser, luser, nl);
1004 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1007 cprintf("Organization: %s%s", lnode, nl);
1010 /* end header processing loop ... at this point, we're in the text */
1012 mptr = TheMessage->cm_fields['M'];
1014 /* Tell the client about the MIME parts in this message */
1015 if (TheMessage->cm_format_type == FMT_RFC822) {
1016 if (mode == MT_CITADEL) {
1017 mime_parser(mptr, NULL, *list_this_part);
1019 else if (mode == MT_MIME) { /* list parts only */
1020 mime_parser(mptr, NULL, *list_this_part);
1021 if (do_proto) cprintf("000\n");
1022 CtdlFreeMessage(TheMessage);
1025 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1026 /* FIXME ... we have to put some code in here to avoid
1027 * printing duplicate header information when both
1028 * Citadel and RFC822 headers exist. Preference should
1029 * probably be given to the RFC822 headers.
1031 while (ch=*(mptr++), ch!=0) {
1033 else if (ch==10) cprintf("%s", nl);
1034 else cprintf("%c", ch);
1036 if (do_proto) cprintf("000\n");
1037 CtdlFreeMessage(TheMessage);
1043 if (do_proto) cprintf("000\n");
1044 CtdlFreeMessage(TheMessage);
1048 /* signify start of msg text */
1049 if (mode == MT_CITADEL)
1050 if (do_proto) cprintf("text\n");
1051 if (mode == MT_RFC822) {
1052 if (TheMessage->cm_fields['U'] == NULL) {
1053 cprintf("Subject: (no subject)%s", nl);
1058 /* If the format type on disk is 1 (fixed-format), then we want
1059 * everything to be output completely literally ... regardless of
1060 * what message transfer format is in use.
1062 if (TheMessage->cm_format_type == FMT_FIXED) {
1064 while (ch = *mptr++, ch > 0) {
1067 if ((ch == 10) || (strlen(buf) > 250)) {
1068 cprintf("%s%s", buf, nl);
1071 buf[strlen(buf) + 1] = 0;
1072 buf[strlen(buf)] = ch;
1075 if (strlen(buf) > 0)
1076 cprintf("%s%s", buf, nl);
1079 /* If the message on disk is format 0 (Citadel vari-format), we
1080 * output using the formatter at 80 columns. This is the final output
1081 * form if the transfer format is RFC822, but if the transfer format
1082 * is Citadel proprietary, it'll still work, because the indentation
1083 * for new paragraphs is correct and the client will reformat the
1084 * message to the reader's screen width.
1086 if (TheMessage->cm_format_type == FMT_CITADEL) {
1087 memfmout(80, mptr, 0, nl);
1090 /* If the message on disk is format 4 (MIME), we've gotta hand it
1091 * off to the MIME parser. The client has already been told that
1092 * this message is format 1 (fixed format), so the callback function
1093 * we use will display those parts as-is.
1095 if (TheMessage->cm_format_type == FMT_RFC822) {
1096 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1097 memset(ma, 0, sizeof(struct ma_info));
1098 mime_parser(mptr, NULL, *fixed_output);
1101 /* now we're done */
1102 if (do_proto) cprintf("000\n");
1103 CtdlFreeMessage(TheMessage);
1110 * display a message (mode 0 - Citadel proprietary)
1112 void cmd_msg0(char *cmdbuf)
1115 int headers_only = 0;
1117 msgid = extract_long(cmdbuf, 0);
1118 headers_only = extract_int(cmdbuf, 1);
1120 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1126 * display a message (mode 2 - RFC822)
1128 void cmd_msg2(char *cmdbuf)
1131 int headers_only = 0;
1133 msgid = extract_long(cmdbuf, 0);
1134 headers_only = extract_int(cmdbuf, 1);
1136 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1142 * display a message (mode 3 - IGnet raw format - internal programs only)
1144 void cmd_msg3(char *cmdbuf)
1147 struct CtdlMessage *msg;
1150 if (CC->internal_pgm == 0) {
1151 cprintf("%d This command is for internal programs only.\n",
1156 msgnum = extract_long(cmdbuf, 0);
1157 msg = CtdlFetchMessage(msgnum);
1159 cprintf("%d Message %ld not found.\n",
1164 serialize_message(&smr, msg);
1165 CtdlFreeMessage(msg);
1168 cprintf("%d Unable to serialize message\n",
1169 ERROR+INTERNAL_ERROR);
1173 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1174 client_write(smr.ser, smr.len);
1181 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1183 void cmd_msg4(char *cmdbuf)
1187 msgid = extract_long(cmdbuf, 0);
1188 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1192 * Open a component of a MIME message as a download file
1194 void cmd_opna(char *cmdbuf)
1198 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1200 msgid = extract_long(cmdbuf, 0);
1201 extract(desired_section, cmdbuf, 1);
1203 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1208 * Save a message pointer into a specified room
1209 * (Returns 0 for success, nonzero for failure)
1210 * roomname may be NULL to use the current room
1212 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1214 char hold_rm[ROOMNAMELEN];
1215 struct cdbdata *cdbfr;
1218 long highest_msg = 0L;
1219 struct CtdlMessage *msg = NULL;
1221 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1222 roomname, msgid, flags);
1224 strcpy(hold_rm, CC->quickroom.QRname);
1226 /* We may need to check to see if this message is real */
1227 if ( (flags & SM_VERIFY_GOODNESS)
1228 || (flags & SM_DO_REPL_CHECK)
1230 msg = CtdlFetchMessage(msgid);
1231 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1234 /* Perform replication checks if necessary */
1235 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1237 if (getroom(&CC->quickroom,
1238 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1240 lprintf(9, "No such room <%s>\n", roomname);
1241 if (msg != NULL) CtdlFreeMessage(msg);
1242 return(ERROR + ROOM_NOT_FOUND);
1245 if (ReplicationChecks(msg) != 0) {
1246 getroom(&CC->quickroom, hold_rm);
1247 if (msg != NULL) CtdlFreeMessage(msg);
1248 lprintf(9, "Did replication, and newer exists\n");
1253 /* Now the regular stuff */
1254 if (lgetroom(&CC->quickroom,
1255 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1257 lprintf(9, "No such room <%s>\n", roomname);
1258 if (msg != NULL) CtdlFreeMessage(msg);
1259 return(ERROR + ROOM_NOT_FOUND);
1262 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1263 if (cdbfr == NULL) {
1267 msglist = mallok(cdbfr->len);
1268 if (msglist == NULL)
1269 lprintf(3, "ERROR malloc msglist!\n");
1270 num_msgs = cdbfr->len / sizeof(long);
1271 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1276 /* Make sure the message doesn't already exist in this room. It
1277 * is absolutely taboo to have more than one reference to the same
1278 * message in a room.
1280 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1281 if (msglist[i] == msgid) {
1282 lputroom(&CC->quickroom); /* unlock the room */
1283 getroom(&CC->quickroom, hold_rm);
1284 if (msg != NULL) CtdlFreeMessage(msg);
1285 return(ERROR + ALREADY_EXISTS);
1289 /* Now add the new message */
1291 msglist = reallok(msglist,
1292 (num_msgs * sizeof(long)));
1294 if (msglist == NULL) {
1295 lprintf(3, "ERROR: can't realloc message list!\n");
1297 msglist[num_msgs - 1] = msgid;
1299 /* Sort the message list, so all the msgid's are in order */
1300 num_msgs = sort_msglist(msglist, num_msgs);
1302 /* Determine the highest message number */
1303 highest_msg = msglist[num_msgs - 1];
1305 /* Write it back to disk. */
1306 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1307 msglist, num_msgs * sizeof(long));
1309 /* Free up the memory we used. */
1312 /* Update the highest-message pointer and unlock the room. */
1313 CC->quickroom.QRhighest = highest_msg;
1314 lputroom(&CC->quickroom);
1315 getroom(&CC->quickroom, hold_rm);
1317 /* Bump the reference count for this message. */
1318 if ((flags & SM_DONT_BUMP_REF)==0) {
1319 AdjRefCount(msgid, +1);
1322 /* Return success. */
1323 if (msg != NULL) CtdlFreeMessage(msg);
1330 * Message base operation to send a message to the master file
1331 * (returns new message number)
1333 * This is the back end for CtdlSaveMsg() and should not be directly
1334 * called by server-side modules.
1337 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1338 FILE *save_a_copy) /* save a copy to disk? */
1345 /* Get a new message number */
1346 newmsgid = get_new_message_number();
1347 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1349 /* Generate an ID if we don't have one already */
1350 if (msg->cm_fields['I']==NULL) {
1351 msg->cm_fields['I'] = strdoop(msgidbuf);
1354 serialize_message(&smr, msg);
1357 cprintf("%d Unable to serialize message\n",
1358 ERROR+INTERNAL_ERROR);
1362 /* Write our little bundle of joy into the message base */
1363 begin_critical_section(S_MSGMAIN);
1364 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1365 smr.ser, smr.len) < 0) {
1366 lprintf(2, "Can't store message\n");
1371 end_critical_section(S_MSGMAIN);
1373 /* If the caller specified that a copy should be saved to a particular
1374 * file handle, do that now too.
1376 if (save_a_copy != NULL) {
1377 fwrite(smr.ser, smr.len, 1, save_a_copy);
1380 /* Free the memory we used for the serialized message */
1383 /* Return the *local* message ID to the caller
1384 * (even if we're storing an incoming network message)
1392 * Serialize a struct CtdlMessage into the format used on disk and network.
1394 * This function loads up a "struct ser_ret" (defined in server.h) which
1395 * contains the length of the serialized message and a pointer to the
1396 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1398 void serialize_message(struct ser_ret *ret, /* return values */
1399 struct CtdlMessage *msg) /* unserialized msg */
1403 static char *forder = FORDER;
1405 if (is_valid_message(msg) == 0) return; /* self check */
1408 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1409 ret->len = ret->len +
1410 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1412 lprintf(9, "calling malloc(%d)\n", ret->len);
1413 ret->ser = mallok(ret->len);
1414 if (ret->ser == NULL) {
1420 ret->ser[1] = msg->cm_anon_type;
1421 ret->ser[2] = msg->cm_format_type;
1424 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1425 ret->ser[wlen++] = (char)forder[i];
1426 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1427 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1429 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1438 * Back end for the ReplicationChecks() function
1440 void check_repl(long msgnum) {
1441 struct CtdlMessage *msg;
1442 time_t timestamp = (-1L);
1444 lprintf(9, "check_repl() found message %ld\n", msgnum);
1445 msg = CtdlFetchMessage(msgnum);
1446 if (msg == NULL) return;
1447 if (msg->cm_fields['T'] != NULL) {
1448 timestamp = atol(msg->cm_fields['T']);
1450 CtdlFreeMessage(msg);
1452 if (timestamp > msg_repl->highest) {
1453 msg_repl->highest = timestamp; /* newer! */
1454 lprintf(9, "newer!\n");
1457 lprintf(9, "older!\n");
1459 /* Existing isn't newer? Then delete the old one(s). */
1460 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1465 * Check to see if any messages already exist which carry the same Extended ID
1469 * -> With older timestamps: delete them and return 0. Message will be saved.
1470 * -> With newer timestamps: return 1. Message save will be aborted.
1472 int ReplicationChecks(struct CtdlMessage *msg) {
1473 struct CtdlMessage *template;
1476 lprintf(9, "ReplicationChecks() started\n");
1477 /* No extended id? Don't do anything. */
1478 if (msg->cm_fields['E'] == NULL) return 0;
1479 if (strlen(msg->cm_fields['E']) == 0) return 0;
1480 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1482 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1483 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1484 msg_repl->highest = atol(msg->cm_fields['T']);
1486 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1487 memset(template, 0, sizeof(struct CtdlMessage));
1488 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1490 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template, check_repl);
1492 /* If a newer message exists with the same Extended ID, abort
1495 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1499 CtdlFreeMessage(template);
1500 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1508 * Save a message to disk
1510 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1511 char *rec, /* Recipient (mail) */
1512 char *force, /* force a particular room? */
1513 int supplied_mailtype) /* local or remote type */
1516 char hold_rm[ROOMNAMELEN];
1517 char actual_rm[ROOMNAMELEN];
1518 char force_room[ROOMNAMELEN];
1519 char content_type[256]; /* We have to learn this */
1520 char recipient[256];
1523 struct usersupp userbuf;
1525 struct SuppMsgInfo smi;
1526 FILE *network_fp = NULL;
1527 static int seqnum = 1;
1528 struct CtdlMessage *imsg;
1532 lprintf(9, "CtdlSaveMsg() called\n");
1533 if (is_valid_message(msg) == 0) return(-1); /* self check */
1534 mailtype = supplied_mailtype;
1536 /* If this message has no timestamp, we take the liberty of
1537 * giving it one, right now.
1539 if (msg->cm_fields['T'] == NULL) {
1540 lprintf(9, "Generating timestamp\n");
1541 sprintf(aaa, "%ld", time(NULL));
1542 msg->cm_fields['T'] = strdoop(aaa);
1545 /* If this message has no path, we generate one.
1547 if (msg->cm_fields['P'] == NULL) {
1548 lprintf(9, "Generating path\n");
1549 if (msg->cm_fields['A'] != NULL) {
1550 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1551 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1552 if (isspace(msg->cm_fields['P'][a])) {
1553 msg->cm_fields['P'][a] = ' ';
1558 msg->cm_fields['P'] = strdoop("unknown");
1562 strcpy(force_room, force);
1564 /* Strip non-printable characters out of the recipient name */
1565 lprintf(9, "Checking recipient (if present)\n");
1566 strcpy(recipient, rec);
1567 for (a = 0; a < strlen(recipient); ++a)
1568 if (!isprint(recipient[a]))
1569 strcpy(&recipient[a], &recipient[a + 1]);
1571 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1572 for (a=0; a<strlen(recipient); ++a) {
1573 if (recipient[a] == '@') {
1574 if (CtdlHostAlias(&recipient[a+1])
1575 == hostalias_localhost) {
1577 lprintf(7, "Changed to <%s>\n", recipient);
1578 mailtype = MES_LOCAL;
1583 lprintf(9, "Recipient is <%s>\n", recipient);
1585 /* Learn about what's inside, because it's what's inside that counts */
1586 lprintf(9, "Learning what's inside\n");
1587 if (msg->cm_fields['M'] == NULL) {
1588 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1591 switch (msg->cm_format_type) {
1593 strcpy(content_type, "text/x-citadel-variformat");
1596 strcpy(content_type, "text/plain");
1599 strcpy(content_type, "text/plain");
1600 /* advance past header fields */
1601 mptr = msg->cm_fields['M'];
1604 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1605 safestrncpy(content_type, mptr,
1606 sizeof(content_type));
1607 strcpy(content_type, &content_type[14]);
1608 for (a = 0; a < strlen(content_type); ++a)
1609 if ((content_type[a] == ';')
1610 || (content_type[a] == ' ')
1611 || (content_type[a] == 13)
1612 || (content_type[a] == 10))
1613 content_type[a] = 0;
1620 /* Goto the correct room */
1621 lprintf(9, "Switching rooms\n");
1622 strcpy(hold_rm, CC->quickroom.QRname);
1623 strcpy(actual_rm, CC->quickroom.QRname);
1625 /* If the user is a twit, move to the twit room for posting */
1626 lprintf(9, "Handling twit stuff\n");
1628 if (CC->usersupp.axlevel == 2) {
1629 strcpy(hold_rm, actual_rm);
1630 strcpy(actual_rm, config.c_twitroom);
1634 /* ...or if this message is destined for Aide> then go there. */
1635 if (strlen(force_room) > 0) {
1636 strcpy(actual_rm, force_room);
1639 lprintf(9, "Possibly relocating\n");
1640 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1641 getroom(&CC->quickroom, actual_rm);
1645 * If this message has no O (room) field, generate one.
1647 if (msg->cm_fields['O'] == NULL) {
1648 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1651 /* Perform "before save" hooks (aborting if any return nonzero) */
1652 lprintf(9, "Performing before-save hooks\n");
1653 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1655 /* If this message has an Extended ID, perform replication checks */
1656 lprintf(9, "Performing replication checks\n");
1657 if (ReplicationChecks(msg) > 0) return(-1);
1659 /* Network mail - send a copy to the network program. */
1660 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1661 lprintf(9, "Sending network spool\n");
1662 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1663 (long) getpid(), CC->cs_pid, ++seqnum);
1664 lprintf(9, "Saving a copy to %s\n", aaa);
1665 network_fp = fopen(aaa, "ab+");
1666 if (network_fp == NULL)
1667 lprintf(2, "ERROR: %s\n", strerror(errno));
1670 /* Save it to disk */
1671 lprintf(9, "Saving to disk\n");
1672 newmsgid = send_message(msg, network_fp);
1673 if (network_fp != NULL) {
1675 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1678 if (newmsgid <= 0L) return(-1);
1680 /* Write a supplemental message info record. This doesn't have to
1681 * be a critical section because nobody else knows about this message
1684 lprintf(9, "Creating SuppMsgInfo record\n");
1685 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1686 smi.smi_msgnum = newmsgid;
1687 smi.smi_refcount = 0;
1688 safestrncpy(smi.smi_content_type, content_type, 64);
1689 PutSuppMsgInfo(&smi);
1691 /* Now figure out where to store the pointers */
1692 lprintf(9, "Storing pointers\n");
1694 /* If this is being done by the networker delivering a private
1695 * message, we want to BYPASS saving the sender's copy (because there
1696 * is no local sender; it would otherwise go to the Trashcan).
1698 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1699 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1700 lprintf(3, "ERROR saving message pointer!\n");
1701 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1705 /* For internet mail, drop a copy in the outbound queue room */
1706 if (mailtype == MES_INTERNET) {
1707 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1710 /* Bump this user's messages posted counter. */
1711 lprintf(9, "Updating user\n");
1712 lgetuser(&CC->usersupp, CC->curr_user);
1713 CC->usersupp.posted = CC->usersupp.posted + 1;
1714 lputuser(&CC->usersupp);
1716 /* If this is private, local mail, make a copy in the
1717 * recipient's mailbox and bump the reference count.
1719 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1720 if (getuser(&userbuf, recipient) == 0) {
1721 lprintf(9, "Delivering private mail\n");
1722 MailboxName(actual_rm, &userbuf, MAILROOM);
1723 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1726 lprintf(9, "No user <%s>, saving in %s> instead\n",
1727 recipient, AIDEROOM);
1728 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1732 /* Perform "after save" hooks */
1733 lprintf(9, "Performing after-save hooks\n");
1734 PerformMessageHooks(msg, EVT_AFTERSAVE);
1737 lprintf(9, "Returning to original room\n");
1738 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1739 getroom(&CC->quickroom, hold_rm);
1741 /* For internet mail, generate delivery instructions
1742 * (Yes, this is recursive! Deal with it!)
1744 if (mailtype == MES_INTERNET) {
1745 lprintf(9, "Generating delivery instructions\n");
1746 instr = mallok(2048);
1748 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1751 SPOOLMIME, newmsgid, time(NULL),
1752 msg->cm_fields['A'], msg->cm_fields['N'],
1755 imsg = mallok(sizeof(struct CtdlMessage));
1756 memset(imsg, 0, sizeof(struct CtdlMessage));
1757 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1758 imsg->cm_anon_type = MES_NORMAL;
1759 imsg->cm_format_type = FMT_RFC822;
1760 imsg->cm_fields['A'] = strdoop("Citadel");
1761 imsg->cm_fields['M'] = instr;
1762 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1763 CtdlFreeMessage(imsg);
1772 * Convenience function for generating small administrative messages.
1774 void quickie_message(char *from, char *to, char *room, char *text)
1776 struct CtdlMessage *msg;
1778 msg = mallok(sizeof(struct CtdlMessage));
1779 memset(msg, 0, sizeof(struct CtdlMessage));
1780 msg->cm_magic = CTDLMESSAGE_MAGIC;
1781 msg->cm_anon_type = MES_NORMAL;
1782 msg->cm_format_type = 0;
1783 msg->cm_fields['A'] = strdoop(from);
1784 msg->cm_fields['O'] = strdoop(room);
1785 msg->cm_fields['N'] = strdoop(NODENAME);
1787 msg->cm_fields['R'] = strdoop(to);
1788 msg->cm_fields['M'] = strdoop(text);
1790 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1791 CtdlFreeMessage(msg);
1792 syslog(LOG_NOTICE, text);
1798 * Back end function used by make_message() and similar functions
1800 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1801 size_t maxlen, /* maximum message length */
1802 char *exist /* if non-null, append to it;
1803 exist is ALWAYS freed */
1806 size_t message_len = 0;
1807 size_t buffer_len = 0;
1811 if (exist == NULL) {
1815 m = reallok(exist, strlen(exist) + 4096);
1816 if (m == NULL) phree(exist);
1819 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1826 /* read in the lines of message text one by one */
1828 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1830 /* augment the buffer if we have to */
1831 if ((message_len + strlen(buf) + 2) > buffer_len) {
1832 lprintf(9, "realloking\n");
1833 ptr = reallok(m, (buffer_len * 2) );
1834 if (ptr == NULL) { /* flush if can't allocate */
1835 while ( (client_gets(buf)>0) &&
1836 strcmp(buf, terminator)) ;;
1839 buffer_len = (buffer_len * 2);
1842 lprintf(9, "buffer_len is %d\n", buffer_len);
1846 if (append == NULL) append = m;
1847 while (strlen(append) > 0) ++append;
1848 strcpy(append, buf);
1849 strcat(append, "\n");
1850 message_len = message_len + strlen(buf) + 1;
1852 /* if we've hit the max msg length, flush the rest */
1853 if (message_len >= maxlen) {
1854 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1865 * Build a binary message to be saved on disk.
1868 struct CtdlMessage *make_message(
1869 struct usersupp *author, /* author's usersupp structure */
1870 char *recipient, /* NULL if it's not mail */
1871 char *room, /* room where it's going */
1872 int type, /* see MES_ types in header file */
1873 int net_type, /* see MES_ types in header file */
1874 int format_type, /* local or remote (see citadel.h) */
1875 char *fake_name) /* who we're masquerading as */
1881 struct CtdlMessage *msg;
1883 msg = mallok(sizeof(struct CtdlMessage));
1884 memset(msg, 0, sizeof(struct CtdlMessage));
1885 msg->cm_magic = CTDLMESSAGE_MAGIC;
1886 msg->cm_anon_type = type;
1887 msg->cm_format_type = format_type;
1889 /* Don't confuse the poor folks if it's not routed mail. */
1890 strcpy(dest_node, "");
1892 /* If net_type is MES_BINARY, split out the destination node. */
1893 if (net_type == MES_BINARY) {
1894 strcpy(dest_node, NODENAME);
1895 for (a = 0; a < strlen(recipient); ++a) {
1896 if (recipient[a] == '@') {
1898 strcpy(dest_node, &recipient[a + 1]);
1903 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1904 if (net_type == MES_INTERNET) {
1905 strcpy(dest_node, "internet");
1908 while (isspace(recipient[strlen(recipient) - 1]))
1909 recipient[strlen(recipient) - 1] = 0;
1911 sprintf(buf, "cit%ld", author->usernum); /* Path */
1912 msg->cm_fields['P'] = strdoop(buf);
1914 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1915 msg->cm_fields['T'] = strdoop(buf);
1917 if (fake_name[0]) /* author */
1918 msg->cm_fields['A'] = strdoop(fake_name);
1920 msg->cm_fields['A'] = strdoop(author->fullname);
1922 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1923 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1925 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1927 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1928 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1930 if (recipient[0] != 0)
1931 msg->cm_fields['R'] = strdoop(recipient);
1932 if (dest_node[0] != 0)
1933 msg->cm_fields['D'] = strdoop(dest_node);
1936 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1937 config.c_maxmsglen, NULL);
1948 * message entry - mode 0 (normal)
1950 void cmd_ent0(char *entargs)
1953 char recipient[256];
1955 int format_type = 0;
1956 char newusername[256];
1957 struct CtdlMessage *msg;
1961 struct usersupp tempUS;
1964 post = extract_int(entargs, 0);
1965 extract(recipient, entargs, 1);
1966 anon_flag = extract_int(entargs, 2);
1967 format_type = extract_int(entargs, 3);
1969 /* first check to make sure the request is valid. */
1971 if (!(CC->logged_in)) {
1972 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1975 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1976 cprintf("%d Need to be validated to enter ",
1977 ERROR + HIGHER_ACCESS_REQUIRED);
1978 cprintf("(except in %s> to sysop)\n", MAILROOM);
1981 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1982 cprintf("%d Need net privileges to enter here.\n",
1983 ERROR + HIGHER_ACCESS_REQUIRED);
1986 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1987 cprintf("%d Sorry, this is a read-only room.\n",
1988 ERROR + HIGHER_ACCESS_REQUIRED);
1995 if (CC->usersupp.axlevel < 6) {
1996 cprintf("%d You don't have permission to masquerade.\n",
1997 ERROR + HIGHER_ACCESS_REQUIRED);
2000 extract(newusername, entargs, 4);
2001 memset(CC->fake_postname, 0, 32);
2002 strcpy(CC->fake_postname, newusername);
2003 cprintf("%d Ok\n", OK);
2006 CC->cs_flags |= CS_POSTING;
2009 if (CC->quickroom.QRflags & QR_MAILBOX) {
2010 if (CC->usersupp.axlevel >= 2) {
2011 strcpy(buf, recipient);
2013 strcpy(buf, "sysop");
2014 e = alias(buf); /* alias and mail type */
2015 if ((buf[0] == 0) || (e == MES_ERROR)) {
2016 cprintf("%d Unknown address - cannot send message.\n",
2017 ERROR + NO_SUCH_USER);
2020 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2021 cprintf("%d Net privileges required for network mail.\n",
2022 ERROR + HIGHER_ACCESS_REQUIRED);
2025 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2026 && ((CC->usersupp.flags & US_INTERNET) == 0)
2027 && (!CC->internal_pgm)) {
2028 cprintf("%d You don't have access to Internet mail.\n",
2029 ERROR + HIGHER_ACCESS_REQUIRED);
2032 if (!strcasecmp(buf, "sysop")) {
2037 goto SKFALL; /* don't search local file */
2038 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2039 cprintf("%d Can't send mail to yourself!\n",
2040 ERROR + NO_SUCH_USER);
2043 /* Check to make sure the user exists; also get the correct
2044 * upper/lower casing of the name.
2046 a = getuser(&tempUS, buf);
2048 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2051 strcpy(buf, tempUS.fullname);
2054 SKFALL: b = MES_NORMAL;
2055 if (CC->quickroom.QRflags & QR_ANONONLY)
2057 if (CC->quickroom.QRflags & QR_ANONOPT) {
2061 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2064 /* If we're only checking the validity of the request, return
2065 * success without creating the message.
2068 cprintf("%d %s\n", OK, buf);
2072 cprintf("%d send message\n", SEND_LISTING);
2074 /* Read in the message from the client. */
2075 if (CC->fake_postname[0])
2076 msg = make_message(&CC->usersupp, buf,
2077 CC->quickroom.QRname, b, e, format_type,
2079 else if (CC->fake_username[0])
2080 msg = make_message(&CC->usersupp, buf,
2081 CC->quickroom.QRname, b, e, format_type,
2084 msg = make_message(&CC->usersupp, buf,
2085 CC->quickroom.QRname, b, e, format_type, "");
2088 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2089 CtdlFreeMessage(msg);
2090 CC->fake_postname[0] = '\0';
2097 * message entry - mode 3 (raw)
2099 void cmd_ent3(char *entargs)
2105 unsigned char ch, which_field;
2106 struct usersupp tempUS;
2108 struct CtdlMessage *msg;
2111 if (CC->internal_pgm == 0) {
2112 cprintf("%d This command is for internal programs only.\n",
2117 /* See if there's a recipient, but make sure it's a real one */
2118 extract(recp, entargs, 1);
2119 for (a = 0; a < strlen(recp); ++a)
2120 if (!isprint(recp[a]))
2121 strcpy(&recp[a], &recp[a + 1]);
2122 while (isspace(recp[0]))
2123 strcpy(recp, &recp[1]);
2124 while (isspace(recp[strlen(recp) - 1]))
2125 recp[strlen(recp) - 1] = 0;
2127 /* If we're in Mail, check the recipient */
2128 if (strlen(recp) > 0) {
2129 e = alias(recp); /* alias and mail type */
2130 if ((recp[0] == 0) || (e == MES_ERROR)) {
2131 cprintf("%d Unknown address - cannot send message.\n",
2132 ERROR + NO_SUCH_USER);
2135 if (e == MES_LOCAL) {
2136 a = getuser(&tempUS, recp);
2138 cprintf("%d No such user.\n",
2139 ERROR + NO_SUCH_USER);
2145 /* At this point, message has been approved. */
2146 if (extract_int(entargs, 0) == 0) {
2147 cprintf("%d OK to send\n", OK);
2151 msglen = extract_long(entargs, 2);
2152 msg = mallok(sizeof(struct CtdlMessage));
2154 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2158 memset(msg, 0, sizeof(struct CtdlMessage));
2159 tempbuf = mallok(msglen);
2160 if (tempbuf == NULL) {
2161 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2166 cprintf("%d %ld\n", SEND_BINARY, msglen);
2168 client_read(&ch, 1); /* 0xFF magic number */
2169 msg->cm_magic = CTDLMESSAGE_MAGIC;
2170 client_read(&ch, 1); /* anon type */
2171 msg->cm_anon_type = ch;
2172 client_read(&ch, 1); /* format type */
2173 msg->cm_format_type = ch;
2174 msglen = msglen - 3;
2176 while (msglen > 0) {
2177 client_read(&which_field, 1);
2178 if (!isalpha(which_field)) valid_msg = 0;
2182 client_read(&ch, 1);
2184 a = strlen(tempbuf);
2187 } while ( (ch != 0) && (msglen > 0) );
2189 msg->cm_fields[which_field] = strdoop(tempbuf);
2192 msg->cm_flags = CM_SKIP_HOOKS;
2193 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2194 CtdlFreeMessage(msg);
2200 * API function to delete messages which match a set of criteria
2201 * (returns the actual number of messages deleted)
2203 int CtdlDeleteMessages(char *room_name, /* which room */
2204 long dmsgnum, /* or "0" for any */
2205 char *content_type /* or "" for any */
2209 struct quickroom qrbuf;
2210 struct cdbdata *cdbfr;
2211 long *msglist = NULL;
2214 int num_deleted = 0;
2216 struct SuppMsgInfo smi;
2218 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2219 room_name, dmsgnum, content_type);
2221 /* get room record, obtaining a lock... */
2222 if (lgetroom(&qrbuf, room_name) != 0) {
2223 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2225 return (0); /* room not found */
2227 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2229 if (cdbfr != NULL) {
2230 msglist = mallok(cdbfr->len);
2231 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2232 num_msgs = cdbfr->len / sizeof(long);
2236 for (i = 0; i < num_msgs; ++i) {
2239 /* Set/clear a bit for each criterion */
2241 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2242 delete_this |= 0x01;
2244 if (strlen(content_type) == 0) {
2245 delete_this |= 0x02;
2247 GetSuppMsgInfo(&smi, msglist[i]);
2248 if (!strcasecmp(smi.smi_content_type,
2250 delete_this |= 0x02;
2254 /* Delete message only if all bits are set */
2255 if (delete_this == 0x03) {
2256 AdjRefCount(msglist[i], -1);
2262 num_msgs = sort_msglist(msglist, num_msgs);
2263 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2264 msglist, (num_msgs * sizeof(long)));
2266 qrbuf.QRhighest = msglist[num_msgs - 1];
2270 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2271 return (num_deleted);
2277 * Delete message from current room
2279 void cmd_dele(char *delstr)
2284 getuser(&CC->usersupp, CC->curr_user);
2285 if ((CC->usersupp.axlevel < 6)
2286 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2287 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2288 && (!(CC->internal_pgm))) {
2289 cprintf("%d Higher access required.\n",
2290 ERROR + HIGHER_ACCESS_REQUIRED);
2293 delnum = extract_long(delstr, 0);
2295 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2298 cprintf("%d %d message%s deleted.\n", OK,
2299 num_deleted, ((num_deleted != 1) ? "s" : ""));
2301 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2307 * move or copy a message to another room
2309 void cmd_move(char *args)
2313 struct quickroom qtemp;
2317 num = extract_long(args, 0);
2318 extract(targ, args, 1);
2319 targ[ROOMNAMELEN - 1] = 0;
2320 is_copy = extract_int(args, 2);
2322 getuser(&CC->usersupp, CC->curr_user);
2323 if ((CC->usersupp.axlevel < 6)
2324 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2325 cprintf("%d Higher access required.\n",
2326 ERROR + HIGHER_ACCESS_REQUIRED);
2330 if (getroom(&qtemp, targ) != 0) {
2331 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2335 err = CtdlSaveMsgPointerInRoom(targ, num,
2336 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2338 cprintf("%d Cannot store message in %s: error %d\n",
2343 /* Now delete the message from the source room,
2344 * if this is a 'move' rather than a 'copy' operation.
2346 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2348 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2354 * GetSuppMsgInfo() - Get the supplementary record for a message
2356 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2359 struct cdbdata *cdbsmi;
2362 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2363 smibuf->smi_msgnum = msgnum;
2364 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2366 /* Use the negative of the message number for its supp record index */
2367 TheIndex = (0L - msgnum);
2369 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2370 if (cdbsmi == NULL) {
2371 return; /* record not found; go with defaults */
2373 memcpy(smibuf, cdbsmi->ptr,
2374 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2375 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2382 * PutSuppMsgInfo() - (re)write supplementary record for a message
2384 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2388 /* Use the negative of the message number for its supp record index */
2389 TheIndex = (0L - smibuf->smi_msgnum);
2391 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2392 smibuf->smi_msgnum, smibuf->smi_refcount);
2394 cdb_store(CDB_MSGMAIN,
2395 &TheIndex, sizeof(long),
2396 smibuf, sizeof(struct SuppMsgInfo));
2401 * AdjRefCount - change the reference count for a message;
2402 * delete the message if it reaches zero
2404 void AdjRefCount(long msgnum, int incr)
2407 struct SuppMsgInfo smi;
2410 /* This is a *tight* critical section; please keep it that way, as
2411 * it may get called while nested in other critical sections.
2412 * Complicating this any further will surely cause deadlock!
2414 begin_critical_section(S_SUPPMSGMAIN);
2415 GetSuppMsgInfo(&smi, msgnum);
2416 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2417 msgnum, smi.smi_refcount);
2418 smi.smi_refcount += incr;
2419 PutSuppMsgInfo(&smi);
2420 end_critical_section(S_SUPPMSGMAIN);
2421 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2422 msgnum, smi.smi_refcount);
2424 /* If the reference count is now zero, delete the message
2425 * (and its supplementary record as well).
2427 if (smi.smi_refcount == 0) {
2428 lprintf(9, "Deleting message <%ld>\n", msgnum);
2430 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2431 delnum = (0L - msgnum);
2432 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2437 * Write a generic object to this room
2439 * Note: this could be much more efficient. Right now we use two temporary
2440 * files, and still pull the message into memory as with all others.
2442 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2443 char *content_type, /* MIME type of this object */
2444 char *tempfilename, /* Where to fetch it from */
2445 struct usersupp *is_mailbox, /* Mailbox room? */
2446 int is_binary, /* Is encoding necessary? */
2447 int is_unique, /* Del others of this type? */
2448 unsigned int flags /* Internal save flags */
2453 char filename[PATH_MAX];
2456 struct quickroom qrbuf;
2457 char roomname[ROOMNAMELEN];
2458 struct CtdlMessage *msg;
2461 if (is_mailbox != NULL)
2462 MailboxName(roomname, is_mailbox, req_room);
2464 safestrncpy(roomname, req_room, sizeof(roomname));
2465 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2467 strcpy(filename, tmpnam(NULL));
2468 fp = fopen(filename, "w");
2472 tempfp = fopen(tempfilename, "r");
2473 if (tempfp == NULL) {
2479 fprintf(fp, "Content-type: %s\n", content_type);
2480 lprintf(9, "Content-type: %s\n", content_type);
2482 if (is_binary == 0) {
2483 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2484 while (ch = getc(tempfp), ch > 0)
2490 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2493 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2494 tempfilename, filename);
2498 lprintf(9, "Allocating\n");
2499 msg = mallok(sizeof(struct CtdlMessage));
2500 memset(msg, 0, sizeof(struct CtdlMessage));
2501 msg->cm_magic = CTDLMESSAGE_MAGIC;
2502 msg->cm_anon_type = MES_NORMAL;
2503 msg->cm_format_type = 4;
2504 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2505 msg->cm_fields['O'] = strdoop(req_room);
2506 msg->cm_fields['N'] = strdoop(config.c_nodename);
2507 msg->cm_fields['H'] = strdoop(config.c_humannode);
2508 msg->cm_flags = flags;
2510 lprintf(9, "Loading\n");
2511 fp = fopen(filename, "rb");
2512 fseek(fp, 0L, SEEK_END);
2515 msg->cm_fields['M'] = mallok(len);
2516 fread(msg->cm_fields['M'], len, 1, fp);
2520 /* Create the requested room if we have to. */
2521 if (getroom(&qrbuf, roomname) != 0) {
2522 create_room(roomname,
2523 ( (is_mailbox != NULL) ? 4 : 3 ),
2526 /* If the caller specified this object as unique, delete all
2527 * other objects of this type that are currently in the room.
2530 lprintf(9, "Deleted %d other msgs of this type\n",
2531 CtdlDeleteMessages(roomname, 0L, content_type));
2533 /* Now write the data */
2534 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2535 CtdlFreeMessage(msg);
2543 void CtdlGetSysConfigBackend(long msgnum) {
2544 config_msgnum = msgnum;
2548 char *CtdlGetSysConfig(char *sysconfname) {
2549 char hold_rm[ROOMNAMELEN];
2552 struct CtdlMessage *msg;
2555 strcpy(hold_rm, CC->quickroom.QRname);
2556 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2557 getroom(&CC->quickroom, hold_rm);
2562 /* We want the last (and probably only) config in this room */
2563 begin_critical_section(S_CONFIG);
2564 config_msgnum = (-1L);
2565 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2566 CtdlGetSysConfigBackend);
2567 msgnum = config_msgnum;
2568 end_critical_section(S_CONFIG);
2574 msg = CtdlFetchMessage(msgnum);
2576 conf = strdoop(msg->cm_fields['M']);
2577 CtdlFreeMessage(msg);
2584 getroom(&CC->quickroom, hold_rm);
2586 lprintf(9, "eggstracting...\n");
2587 if (conf != NULL) do {
2588 extract_token(buf, conf, 0, '\n');
2589 lprintf(9, "eggstracted <%s>\n", buf);
2590 strcpy(conf, &conf[strlen(buf)+1]);
2591 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2596 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2597 char temp[PATH_MAX];
2600 strcpy(temp, tmpnam(NULL));
2602 fp = fopen(temp, "w");
2603 if (fp == NULL) return;
2604 fprintf(fp, "%s", sysconfdata);
2607 /* this handy API function does all the work for us */
2608 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);