4 * Implements the message store.
26 #include "sysdep_decls.h"
27 #include "citserver.h"
32 #include "dynloader.h"
34 #include "mime_parser.h"
37 #include "internet_addressing.h"
39 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
40 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
41 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
43 extern struct config config;
47 "", "", "", "", "", "", "", "",
48 "", "", "", "", "", "", "", "",
49 "", "", "", "", "", "", "", "",
50 "", "", "", "", "", "", "", "",
51 "", "", "", "", "", "", "", "",
52 "", "", "", "", "", "", "", "",
53 "", "", "", "", "", "", "", "",
54 "", "", "", "", "", "", "", "",
81 * This function is self explanatory.
82 * (What can I say, I'm in a weird mood today...)
84 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
88 for (i = 0; i < strlen(name); ++i)
91 if (isspace(name[i - 1])) {
92 strcpy(&name[i - 1], &name[i]);
95 while (isspace(name[i + 1])) {
96 strcpy(&name[i + 1], &name[i + 2]);
103 * Aliasing for network mail.
104 * (Error messages have been commented out, because this is a server.)
106 int alias(char *name)
107 { /* process alias and routing info for mail */
110 char aaa[300], bbb[300];
113 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
116 fp = fopen("network/mail.aliases", "r");
118 fp = fopen("/dev/null", "r");
124 while (fgets(aaa, sizeof aaa, fp) != NULL) {
125 while (isspace(name[0]))
126 strcpy(name, &name[1]);
127 aaa[strlen(aaa) - 1] = 0;
129 for (a = 0; a < strlen(aaa); ++a) {
131 strcpy(bbb, &aaa[a + 1]);
135 if (!strcasecmp(name, aaa))
140 lprintf(7, "Mail is being forwarded to %s\n", name);
142 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
143 for (a=0; a<strlen(name); ++a) {
144 if (name[a] == '@') {
145 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
147 lprintf(7, "Changed to <%s>\n", name);
152 /* determine local or remote type, see citadel.h */
154 for (a = 0; a < strlen(name); ++a)
156 return (MES_INTERNET);
158 for (a = 0; a < strlen(name); ++a)
160 for (b = a; b < strlen(name); ++b)
162 return (MES_INTERNET);
165 for (a = 0; a < strlen(name); ++a)
169 lprintf(7, "Too many @'s in address\n");
173 for (a = 0; a < strlen(name); ++a)
175 strcpy(bbb, &name[a + 1]);
177 strcpy(bbb, &bbb[1]);
178 fp = fopen("network/mail.sysinfo", "r");
182 a = getstring(fp, aaa);
183 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
185 a = getstring(fp, aaa);
186 if (!strncmp(aaa, "use ", 4)) {
187 strcpy(bbb, &aaa[4]);
193 if (!strncmp(aaa, "uum", 3)) {
195 for (a = 0; a < strlen(bbb); ++a) {
201 while (bbb[strlen(bbb) - 1] == '_')
202 bbb[strlen(bbb) - 1] = 0;
203 sprintf(name, &aaa[4], bbb);
204 lprintf(9, "returning MES_INTERNET\n");
205 return (MES_INTERNET);
207 if (!strncmp(aaa, "bin", 3)) {
210 while (aaa[strlen(aaa) - 1] != '@')
211 aaa[strlen(aaa) - 1] = 0;
212 aaa[strlen(aaa) - 1] = 0;
213 while (aaa[strlen(aaa) - 1] == ' ')
214 aaa[strlen(aaa) - 1] = 0;
215 while (bbb[0] != '@')
216 strcpy(bbb, &bbb[1]);
217 strcpy(bbb, &bbb[1]);
218 while (bbb[0] == ' ')
219 strcpy(bbb, &bbb[1]);
220 sprintf(name, "%s @%s", aaa, bbb);
221 lprintf(9, "returning MES_BINARY\n");
227 lprintf(9, "returning MES_LOCAL\n");
236 fp = fopen("citadel.control", "r");
237 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
243 void simple_listing(long msgnum, void *userdata)
245 cprintf("%ld\n", msgnum);
250 /* Determine if a given message matches the fields in a message template.
251 * Return 0 for a successful match.
253 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
256 /* If there aren't any fields in the template, all messages will
259 if (template == NULL) return(0);
261 /* Null messages are bogus. */
262 if (msg == NULL) return(1);
264 for (i='A'; i<='Z'; ++i) {
265 if (template->cm_fields[i] != NULL) {
266 if (msg->cm_fields[i] == NULL) {
269 if (strcasecmp(msg->cm_fields[i],
270 template->cm_fields[i])) return 1;
274 /* All compares succeeded: we have a match! */
282 * API function to perform an operation for each qualifying message in the
283 * current room. (Returns the number of messages processed.)
285 int CtdlForEachMessage(int mode, long ref,
286 int moderation_level,
288 struct CtdlMessage *compare,
289 void (*CallBack) (long, void *),
295 struct cdbdata *cdbfr;
296 long *msglist = NULL;
298 int num_processed = 0;
300 struct SuppMsgInfo smi;
301 struct CtdlMessage *msg;
303 /* Learn about the user and room in question */
305 getuser(&CC->usersupp, CC->curr_user);
306 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
308 /* Load the message list */
309 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
311 msglist = mallok(cdbfr->len);
312 memcpy(msglist, cdbfr->ptr, cdbfr->len);
313 num_msgs = cdbfr->len / sizeof(long);
316 return 0; /* No messages at all? No further action. */
320 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
321 GetSuppMsgInfo(&smi, msglist[a]);
323 /* Filter out messages that are moderated below the level
324 * currently being viewed at.
326 if (smi.smi_mod < moderation_level) {
330 /* If the caller is looking for a specific MIME type, filter
331 * out all messages which are not of the type requested.
333 if (content_type != NULL) if (strlen(content_type) > 0) {
334 if (strcasecmp(smi.smi_content_type, content_type)) {
340 num_msgs = sort_msglist(msglist, num_msgs);
342 /* If a template was supplied, filter out the messages which
343 * don't match. (This could induce some delays!)
346 if (compare != NULL) {
347 for (a = 0; a < num_msgs; ++a) {
348 msg = CtdlFetchMessage(msglist[a]);
350 if (CtdlMsgCmp(msg, compare)) {
353 CtdlFreeMessage(msg);
361 * Now iterate through the message list, according to the
362 * criteria supplied by the caller.
365 for (a = 0; a < num_msgs; ++a) {
366 thismsg = msglist[a];
371 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
372 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
373 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
374 && (CC->usersupp.flags & US_LASTOLD))
375 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
376 || ((mode == MSGS_FIRST) && (a < ref))
377 || ((mode == MSGS_GT) && (thismsg > ref))
378 || ((mode == MSGS_EQ) && (thismsg == ref))
381 if (CallBack) CallBack(thismsg, userdata);
385 phree(msglist); /* Clean up */
386 return num_processed;
392 * cmd_msgs() - get list of message #'s in this room
393 * implements the MSGS server command using CtdlForEachMessage()
395 void cmd_msgs(char *cmdbuf)
404 int with_template = 0;
405 struct CtdlMessage *template = NULL;
407 extract(which, cmdbuf, 0);
408 cm_ref = extract_int(cmdbuf, 1);
409 with_template = extract_int(cmdbuf, 2);
413 if (!strncasecmp(which, "OLD", 3))
415 else if (!strncasecmp(which, "NEW", 3))
417 else if (!strncasecmp(which, "FIRST", 5))
419 else if (!strncasecmp(which, "LAST", 4))
421 else if (!strncasecmp(which, "GT", 2))
424 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
425 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
430 cprintf("%d Send template then receive message list\n",
432 template = (struct CtdlMessage *)
433 mallok(sizeof(struct CtdlMessage));
434 memset(template, 0, sizeof(struct CtdlMessage));
435 while(client_gets(buf), strcmp(buf,"000")) {
436 extract(tfield, buf, 0);
437 extract(tvalue, buf, 1);
438 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
439 if (!strcasecmp(tfield, msgkeys[i])) {
440 template->cm_fields[i] =
447 cprintf("%d Message list...\n", LISTING_FOLLOWS);
450 CtdlForEachMessage(mode, cm_ref,
451 CC->usersupp.moderation_filter,
452 NULL, template, simple_listing, NULL);
453 if (template != NULL) CtdlFreeMessage(template);
461 * help_subst() - support routine for help file viewer
463 void help_subst(char *strbuf, char *source, char *dest)
468 while (p = pattern2(strbuf, source), (p >= 0)) {
469 strcpy(workbuf, &strbuf[p + strlen(source)]);
470 strcpy(&strbuf[p], dest);
471 strcat(strbuf, workbuf);
476 void do_help_subst(char *buffer)
480 help_subst(buffer, "^nodename", config.c_nodename);
481 help_subst(buffer, "^humannode", config.c_humannode);
482 help_subst(buffer, "^fqdn", config.c_fqdn);
483 help_subst(buffer, "^username", CC->usersupp.fullname);
484 sprintf(buf2, "%ld", CC->usersupp.usernum);
485 help_subst(buffer, "^usernum", buf2);
486 help_subst(buffer, "^sysadm", config.c_sysadm);
487 help_subst(buffer, "^variantname", CITADEL);
488 sprintf(buf2, "%d", config.c_maxsessions);
489 help_subst(buffer, "^maxsessions", buf2);
495 * memfmout() - Citadel text formatter and paginator.
496 * Although the original purpose of this routine was to format
497 * text to the reader's screen width, all we're really using it
498 * for here is to format text out to 80 columns before sending it
499 * to the client. The client software may reformat it again.
502 int width, /* screen width to use */
503 char *mptr, /* where are we going to get our text from? */
504 char subst, /* nonzero if we should do substitutions */
505 char *nl) /* string to terminate lines with */
517 c = 1; /* c is the current pos */
520 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
522 buffer[strlen(buffer) + 1] = 0;
523 buffer[strlen(buffer)] = ch;
526 if (buffer[0] == '^')
527 do_help_subst(buffer);
529 buffer[strlen(buffer) + 1] = 0;
531 strcpy(buffer, &buffer[1]);
541 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
543 if (((old == 13) || (old == 10)) && (isspace(real))) {
551 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
552 cprintf("%s%s", nl, aaa);
561 if ((strlen(aaa) + c) > (width - 5)) {
571 if ((ch == 13) || (ch == 10)) {
572 cprintf("%s%s", aaa, nl);
579 FMTEND: cprintf("%s%s", aaa, nl);
585 * Callback function for mime parser that simply lists the part
587 void list_this_part(char *name, char *filename, char *partnum, char *disp,
588 void *content, char *cbtype, size_t length)
591 cprintf("part=%s|%s|%s|%s|%s|%d\n",
592 name, filename, partnum, disp, cbtype, length);
597 * Callback function for mime parser that opens a section for downloading
599 void mime_download(char *name, char *filename, char *partnum, char *disp,
600 void *content, char *cbtype, size_t length)
603 /* Silently go away if there's already a download open... */
604 if (CC->download_fp != NULL)
607 /* ...or if this is not the desired section */
608 if (strcasecmp(desired_section, partnum))
611 CC->download_fp = tmpfile();
612 if (CC->download_fp == NULL)
615 fwrite(content, length, 1, CC->download_fp);
616 fflush(CC->download_fp);
617 rewind(CC->download_fp);
619 OpenCmdResult(filename, cbtype);
625 * Load a message from disk into memory.
626 * This is used by CtdlOutputMsg() and other fetch functions.
628 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
629 * using the CtdlMessageFree() function.
631 struct CtdlMessage *CtdlFetchMessage(long msgnum)
633 struct cdbdata *dmsgtext;
634 struct CtdlMessage *ret = NULL;
637 CIT_UBYTE field_header;
640 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
641 if (dmsgtext == NULL) {
644 mptr = dmsgtext->ptr;
646 /* Parse the three bytes that begin EVERY message on disk.
647 * The first is always 0xFF, the on-disk magic number.
648 * The second is the anonymous/public type byte.
649 * The third is the format type byte (vari, fixed, or MIME).
653 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
657 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
658 memset(ret, 0, sizeof(struct CtdlMessage));
660 ret->cm_magic = CTDLMESSAGE_MAGIC;
661 ret->cm_anon_type = *mptr++; /* Anon type byte */
662 ret->cm_format_type = *mptr++; /* Format type byte */
665 * The rest is zero or more arbitrary fields. Load them in.
666 * We're done when we encounter either a zero-length field or
667 * have just processed the 'M' (message text) field.
670 field_length = strlen(mptr);
671 if (field_length == 0)
673 field_header = *mptr++;
674 ret->cm_fields[field_header] = mallok(field_length);
675 strcpy(ret->cm_fields[field_header], mptr);
677 while (*mptr++ != 0); /* advance to next field */
679 } while ((field_length > 0) && (field_header != 'M'));
683 /* Always make sure there's something in the msg text field */
684 if (ret->cm_fields['M'] == NULL)
685 ret->cm_fields['M'] = strdoop("<no text>\n");
687 /* Perform "before read" hooks (aborting if any return nonzero) */
688 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
689 CtdlFreeMessage(ret);
698 * Returns 1 if the supplied pointer points to a valid Citadel message.
699 * If the pointer is NULL or the magic number check fails, returns 0.
701 int is_valid_message(struct CtdlMessage *msg) {
704 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
705 lprintf(3, "is_valid_message() -- self-check failed\n");
713 * 'Destructor' for struct CtdlMessage
715 void CtdlFreeMessage(struct CtdlMessage *msg)
719 if (is_valid_message(msg) == 0) return;
721 for (i = 0; i < 256; ++i)
722 if (msg->cm_fields[i] != NULL) {
723 phree(msg->cm_fields[i]);
726 msg->cm_magic = 0; /* just in case */
732 * Callback function for mime parser that wants to display text
734 void fixed_output(char *name, char *filename, char *partnum, char *disp,
735 void *content, char *cbtype, size_t length)
742 if (!strcasecmp(cbtype, "multipart/alternative")) {
743 strcpy(ma->prefix, partnum);
744 strcat(ma->prefix, ".");
750 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
752 && (ma->did_print == 1) ) {
753 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
759 if ( (!strcasecmp(cbtype, "text/plain"))
760 || (strlen(cbtype)==0) ) {
765 if (ch==10) cprintf("\r\n");
766 else cprintf("%c", ch);
769 else if (!strcasecmp(cbtype, "text/html")) {
770 ptr = html_to_ascii(content, 80, 0);
775 if (ch==10) cprintf("\r\n");
776 else cprintf("%c", ch);
780 else if (strncasecmp(cbtype, "multipart/", 10)) {
781 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
782 partnum, filename, cbtype, length);
788 * Get a message off disk. (returns om_* values found in msgbase.h)
791 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
792 int mode, /* how would you like that message? */
793 int headers_only, /* eschew the message body? */
794 int do_proto, /* do Citadel protocol responses? */
795 int crlf /* Use CRLF newlines instead of LF? */
801 char display_name[256];
802 struct CtdlMessage *TheMessage;
804 char *nl; /* newline string */
806 /* buffers needed for RFC822 translation */
816 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
820 sprintf(mid, "%ld", msg_num);
821 nl = (crlf ? "\r\n" : "\n");
823 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
824 if (do_proto) cprintf("%d Not logged in.\n",
825 ERROR + NOT_LOGGED_IN);
826 return(om_not_logged_in);
829 /* FIXME ... small security issue
830 * We need to check to make sure the requested message is actually
831 * in the current room, and set msg_ok to 1 only if it is. This
832 * functionality is currently missing because I'm in a hurry to replace
833 * broken production code with nonbroken pre-beta code. :( -- ajc
836 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
838 return(om_no_such_msg);
843 * Fetch the message from disk
845 TheMessage = CtdlFetchMessage(msg_num);
846 if (TheMessage == NULL) {
847 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
849 return(om_no_such_msg);
852 /* Are we downloading a MIME component? */
853 if (mode == MT_DOWNLOAD) {
854 if (TheMessage->cm_format_type != FMT_RFC822) {
856 cprintf("%d This is not a MIME message.\n",
858 } else if (CC->download_fp != NULL) {
859 if (do_proto) cprintf(
860 "%d You already have a download open.\n",
863 /* Parse the message text component */
864 mptr = TheMessage->cm_fields['M'];
865 mime_parser(mptr, NULL, *mime_download);
866 /* If there's no file open by this time, the requested
867 * section wasn't found, so print an error
869 if (CC->download_fp == NULL) {
870 if (do_proto) cprintf(
871 "%d Section %s not found.\n",
872 ERROR + FILE_NOT_FOUND,
876 CtdlFreeMessage(TheMessage);
877 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
880 /* now for the user-mode message reading loops */
881 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
883 /* Tell the client which format type we're using. If this is a
884 * MIME message, *lie* about it and tell the user it's fixed-format.
886 if (mode == MT_CITADEL) {
887 if (TheMessage->cm_format_type == FMT_RFC822) {
888 if (do_proto) cprintf("type=1\n");
891 if (do_proto) cprintf("type=%d\n",
892 TheMessage->cm_format_type);
896 /* nhdr=yes means that we're only displaying headers, no body */
897 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
898 if (do_proto) cprintf("nhdr=yes\n");
901 /* begin header processing loop for Citadel message format */
903 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
905 strcpy(display_name, "<unknown>");
906 if (TheMessage->cm_fields['A']) {
907 strcpy(buf, TheMessage->cm_fields['A']);
908 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
909 if (TheMessage->cm_anon_type == MES_ANON)
910 strcpy(display_name, "****");
911 else if (TheMessage->cm_anon_type == MES_AN2)
912 strcpy(display_name, "anonymous");
914 strcpy(display_name, buf);
916 && ((TheMessage->cm_anon_type == MES_ANON)
917 || (TheMessage->cm_anon_type == MES_AN2))) {
918 sprintf(&display_name[strlen(display_name)],
923 strcpy(allkeys, FORDER);
924 for (i=0; i<strlen(allkeys); ++i) {
925 k = (int) allkeys[i];
927 if (TheMessage->cm_fields[k] != NULL) {
929 if (do_proto) cprintf("%s=%s\n",
934 if (do_proto) cprintf("%s=%s\n",
936 TheMessage->cm_fields[k]
945 /* begin header processing loop for RFC822 transfer format */
950 strcpy(snode, NODENAME);
951 strcpy(lnode, HUMANNODE);
952 if (mode == MT_RFC822) {
953 cprintf("X-UIDL: %ld%s", msg_num, nl);
954 for (i = 0; i < 256; ++i) {
955 if (TheMessage->cm_fields[i]) {
956 mptr = TheMessage->cm_fields[i];
963 cprintf("Path: %s%s", mptr, nl);
966 cprintf("Subject: %s%s", mptr, nl);
972 cprintf("X-Citadel-Room: %s%s",
977 cprintf("To: %s%s", mptr, nl);
979 generate_rfc822_datestamp(datestamp,
981 cprintf("Date: %s%s", datestamp, nl);
987 for (i=0; i<strlen(suser); ++i) {
988 suser[i] = tolower(suser[i]);
989 if (!isalnum(suser[i])) suser[i]='_';
992 if (mode == MT_RFC822) {
993 if (!strcasecmp(snode, NODENAME)) {
997 /* Construct a fun message id */
998 cprintf("Message-ID: <%s", mid);
999 if (strchr(mid, '@')==NULL) {
1000 cprintf("@%s", snode);
1004 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1006 if (strlen(fuser) > 0) {
1007 cprintf("From: %s (%s)%s", fuser, luser, nl);
1010 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1013 cprintf("Organization: %s%s", lnode, nl);
1016 /* end header processing loop ... at this point, we're in the text */
1018 mptr = TheMessage->cm_fields['M'];
1020 /* Tell the client about the MIME parts in this message */
1021 if (TheMessage->cm_format_type == FMT_RFC822) {
1022 if (mode == MT_CITADEL) {
1023 mime_parser(mptr, NULL, *list_this_part);
1025 else if (mode == MT_MIME) { /* list parts only */
1026 mime_parser(mptr, NULL, *list_this_part);
1027 if (do_proto) cprintf("000\n");
1028 CtdlFreeMessage(TheMessage);
1031 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1032 /* FIXME ... we have to put some code in here to avoid
1033 * printing duplicate header information when both
1034 * Citadel and RFC822 headers exist. Preference should
1035 * probably be given to the RFC822 headers.
1037 while (ch=*(mptr++), ch!=0) {
1039 else if (ch==10) cprintf("%s", nl);
1040 else cprintf("%c", ch);
1042 if (do_proto) cprintf("000\n");
1043 CtdlFreeMessage(TheMessage);
1049 if (do_proto) cprintf("000\n");
1050 CtdlFreeMessage(TheMessage);
1054 /* signify start of msg text */
1055 if (mode == MT_CITADEL)
1056 if (do_proto) cprintf("text\n");
1057 if (mode == MT_RFC822) {
1058 if (TheMessage->cm_fields['U'] == NULL) {
1059 cprintf("Subject: (no subject)%s", nl);
1064 /* If the format type on disk is 1 (fixed-format), then we want
1065 * everything to be output completely literally ... regardless of
1066 * what message transfer format is in use.
1068 if (TheMessage->cm_format_type == FMT_FIXED) {
1070 while (ch = *mptr++, ch > 0) {
1073 if ((ch == 10) || (strlen(buf) > 250)) {
1074 cprintf("%s%s", buf, nl);
1077 buf[strlen(buf) + 1] = 0;
1078 buf[strlen(buf)] = ch;
1081 if (strlen(buf) > 0)
1082 cprintf("%s%s", buf, nl);
1085 /* If the message on disk is format 0 (Citadel vari-format), we
1086 * output using the formatter at 80 columns. This is the final output
1087 * form if the transfer format is RFC822, but if the transfer format
1088 * is Citadel proprietary, it'll still work, because the indentation
1089 * for new paragraphs is correct and the client will reformat the
1090 * message to the reader's screen width.
1092 if (TheMessage->cm_format_type == FMT_CITADEL) {
1093 memfmout(80, mptr, 0, nl);
1096 /* If the message on disk is format 4 (MIME), we've gotta hand it
1097 * off to the MIME parser. The client has already been told that
1098 * this message is format 1 (fixed format), so the callback function
1099 * we use will display those parts as-is.
1101 if (TheMessage->cm_format_type == FMT_RFC822) {
1102 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1103 memset(ma, 0, sizeof(struct ma_info));
1104 mime_parser(mptr, NULL, *fixed_output);
1107 /* now we're done */
1108 if (do_proto) cprintf("000\n");
1109 CtdlFreeMessage(TheMessage);
1116 * display a message (mode 0 - Citadel proprietary)
1118 void cmd_msg0(char *cmdbuf)
1121 int headers_only = 0;
1123 msgid = extract_long(cmdbuf, 0);
1124 headers_only = extract_int(cmdbuf, 1);
1126 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1132 * display a message (mode 2 - RFC822)
1134 void cmd_msg2(char *cmdbuf)
1137 int headers_only = 0;
1139 msgid = extract_long(cmdbuf, 0);
1140 headers_only = extract_int(cmdbuf, 1);
1142 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1148 * display a message (mode 3 - IGnet raw format - internal programs only)
1150 void cmd_msg3(char *cmdbuf)
1153 struct CtdlMessage *msg;
1156 if (CC->internal_pgm == 0) {
1157 cprintf("%d This command is for internal programs only.\n",
1162 msgnum = extract_long(cmdbuf, 0);
1163 msg = CtdlFetchMessage(msgnum);
1165 cprintf("%d Message %ld not found.\n",
1170 serialize_message(&smr, msg);
1171 CtdlFreeMessage(msg);
1174 cprintf("%d Unable to serialize message\n",
1175 ERROR+INTERNAL_ERROR);
1179 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1180 client_write(smr.ser, smr.len);
1187 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1189 void cmd_msg4(char *cmdbuf)
1193 msgid = extract_long(cmdbuf, 0);
1194 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1198 * Open a component of a MIME message as a download file
1200 void cmd_opna(char *cmdbuf)
1204 CtdlAllocUserData(SYM_DESIRED_SECTION, 64);
1206 msgid = extract_long(cmdbuf, 0);
1207 extract(desired_section, cmdbuf, 1);
1209 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1214 * Save a message pointer into a specified room
1215 * (Returns 0 for success, nonzero for failure)
1216 * roomname may be NULL to use the current room
1218 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1220 char hold_rm[ROOMNAMELEN];
1221 struct cdbdata *cdbfr;
1224 long highest_msg = 0L;
1225 struct CtdlMessage *msg = NULL;
1227 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1228 roomname, msgid, flags);
1230 strcpy(hold_rm, CC->quickroom.QRname);
1232 /* We may need to check to see if this message is real */
1233 if ( (flags & SM_VERIFY_GOODNESS)
1234 || (flags & SM_DO_REPL_CHECK)
1236 msg = CtdlFetchMessage(msgid);
1237 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1240 /* Perform replication checks if necessary */
1241 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1243 if (getroom(&CC->quickroom,
1244 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1246 lprintf(9, "No such room <%s>\n", roomname);
1247 if (msg != NULL) CtdlFreeMessage(msg);
1248 return(ERROR + ROOM_NOT_FOUND);
1251 if (ReplicationChecks(msg) != 0) {
1252 getroom(&CC->quickroom, hold_rm);
1253 if (msg != NULL) CtdlFreeMessage(msg);
1254 lprintf(9, "Did replication, and newer exists\n");
1259 /* Now the regular stuff */
1260 if (lgetroom(&CC->quickroom,
1261 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1263 lprintf(9, "No such room <%s>\n", roomname);
1264 if (msg != NULL) CtdlFreeMessage(msg);
1265 return(ERROR + ROOM_NOT_FOUND);
1268 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1269 if (cdbfr == NULL) {
1273 msglist = mallok(cdbfr->len);
1274 if (msglist == NULL)
1275 lprintf(3, "ERROR malloc msglist!\n");
1276 num_msgs = cdbfr->len / sizeof(long);
1277 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1282 /* Make sure the message doesn't already exist in this room. It
1283 * is absolutely taboo to have more than one reference to the same
1284 * message in a room.
1286 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1287 if (msglist[i] == msgid) {
1288 lputroom(&CC->quickroom); /* unlock the room */
1289 getroom(&CC->quickroom, hold_rm);
1290 if (msg != NULL) CtdlFreeMessage(msg);
1291 return(ERROR + ALREADY_EXISTS);
1295 /* Now add the new message */
1297 msglist = reallok(msglist,
1298 (num_msgs * sizeof(long)));
1300 if (msglist == NULL) {
1301 lprintf(3, "ERROR: can't realloc message list!\n");
1303 msglist[num_msgs - 1] = msgid;
1305 /* Sort the message list, so all the msgid's are in order */
1306 num_msgs = sort_msglist(msglist, num_msgs);
1308 /* Determine the highest message number */
1309 highest_msg = msglist[num_msgs - 1];
1311 /* Write it back to disk. */
1312 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1313 msglist, num_msgs * sizeof(long));
1315 /* Free up the memory we used. */
1318 /* Update the highest-message pointer and unlock the room. */
1319 CC->quickroom.QRhighest = highest_msg;
1320 lputroom(&CC->quickroom);
1321 getroom(&CC->quickroom, hold_rm);
1323 /* Bump the reference count for this message. */
1324 if ((flags & SM_DONT_BUMP_REF)==0) {
1325 AdjRefCount(msgid, +1);
1328 /* Return success. */
1329 if (msg != NULL) CtdlFreeMessage(msg);
1336 * Message base operation to send a message to the master file
1337 * (returns new message number)
1339 * This is the back end for CtdlSaveMsg() and should not be directly
1340 * called by server-side modules.
1343 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1344 FILE *save_a_copy) /* save a copy to disk? */
1351 /* Get a new message number */
1352 newmsgid = get_new_message_number();
1353 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1355 /* Generate an ID if we don't have one already */
1356 if (msg->cm_fields['I']==NULL) {
1357 msg->cm_fields['I'] = strdoop(msgidbuf);
1360 serialize_message(&smr, msg);
1363 cprintf("%d Unable to serialize message\n",
1364 ERROR+INTERNAL_ERROR);
1368 /* Write our little bundle of joy into the message base */
1369 begin_critical_section(S_MSGMAIN);
1370 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1371 smr.ser, smr.len) < 0) {
1372 lprintf(2, "Can't store message\n");
1377 end_critical_section(S_MSGMAIN);
1379 /* If the caller specified that a copy should be saved to a particular
1380 * file handle, do that now too.
1382 if (save_a_copy != NULL) {
1383 fwrite(smr.ser, smr.len, 1, save_a_copy);
1386 /* Free the memory we used for the serialized message */
1389 /* Return the *local* message ID to the caller
1390 * (even if we're storing an incoming network message)
1398 * Serialize a struct CtdlMessage into the format used on disk and network.
1400 * This function loads up a "struct ser_ret" (defined in server.h) which
1401 * contains the length of the serialized message and a pointer to the
1402 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1404 void serialize_message(struct ser_ret *ret, /* return values */
1405 struct CtdlMessage *msg) /* unserialized msg */
1409 static char *forder = FORDER;
1411 if (is_valid_message(msg) == 0) return; /* self check */
1414 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1415 ret->len = ret->len +
1416 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1418 lprintf(9, "calling malloc(%d)\n", ret->len);
1419 ret->ser = mallok(ret->len);
1420 if (ret->ser == NULL) {
1426 ret->ser[1] = msg->cm_anon_type;
1427 ret->ser[2] = msg->cm_format_type;
1430 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1431 ret->ser[wlen++] = (char)forder[i];
1432 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1433 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1435 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1444 * Back end for the ReplicationChecks() function
1446 void check_repl(long msgnum, void *userdata) {
1447 struct CtdlMessage *msg;
1448 time_t timestamp = (-1L);
1450 lprintf(9, "check_repl() found message %ld\n", msgnum);
1451 msg = CtdlFetchMessage(msgnum);
1452 if (msg == NULL) return;
1453 if (msg->cm_fields['T'] != NULL) {
1454 timestamp = atol(msg->cm_fields['T']);
1456 CtdlFreeMessage(msg);
1458 if (timestamp > msg_repl->highest) {
1459 msg_repl->highest = timestamp; /* newer! */
1460 lprintf(9, "newer!\n");
1463 lprintf(9, "older!\n");
1465 /* Existing isn't newer? Then delete the old one(s). */
1466 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1471 * Check to see if any messages already exist which carry the same Extended ID
1475 * -> With older timestamps: delete them and return 0. Message will be saved.
1476 * -> With newer timestamps: return 1. Message save will be aborted.
1478 int ReplicationChecks(struct CtdlMessage *msg) {
1479 struct CtdlMessage *template;
1482 lprintf(9, "ReplicationChecks() started\n");
1483 /* No extended id? Don't do anything. */
1484 if (msg->cm_fields['E'] == NULL) return 0;
1485 if (strlen(msg->cm_fields['E']) == 0) return 0;
1486 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1488 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1489 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1490 msg_repl->highest = atol(msg->cm_fields['T']);
1492 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1493 memset(template, 0, sizeof(struct CtdlMessage));
1494 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1496 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1499 /* If a newer message exists with the same Extended ID, abort
1502 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1506 CtdlFreeMessage(template);
1507 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1515 * Save a message to disk
1517 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1518 char *rec, /* Recipient (mail) */
1519 char *force, /* force a particular room? */
1520 int supplied_mailtype) /* local or remote type */
1523 char hold_rm[ROOMNAMELEN];
1524 char actual_rm[ROOMNAMELEN];
1525 char force_room[ROOMNAMELEN];
1526 char content_type[256]; /* We have to learn this */
1527 char recipient[256];
1530 struct usersupp userbuf;
1532 struct SuppMsgInfo smi;
1533 FILE *network_fp = NULL;
1534 static int seqnum = 1;
1535 struct CtdlMessage *imsg;
1539 lprintf(9, "CtdlSaveMsg() called\n");
1540 if (is_valid_message(msg) == 0) return(-1); /* self check */
1541 mailtype = supplied_mailtype;
1543 /* If this message has no timestamp, we take the liberty of
1544 * giving it one, right now.
1546 if (msg->cm_fields['T'] == NULL) {
1547 lprintf(9, "Generating timestamp\n");
1548 sprintf(aaa, "%ld", time(NULL));
1549 msg->cm_fields['T'] = strdoop(aaa);
1552 /* If this message has no path, we generate one.
1554 if (msg->cm_fields['P'] == NULL) {
1555 lprintf(9, "Generating path\n");
1556 if (msg->cm_fields['A'] != NULL) {
1557 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1558 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1559 if (isspace(msg->cm_fields['P'][a])) {
1560 msg->cm_fields['P'][a] = ' ';
1565 msg->cm_fields['P'] = strdoop("unknown");
1569 strcpy(force_room, force);
1571 /* Strip non-printable characters out of the recipient name */
1572 lprintf(9, "Checking recipient (if present)\n");
1573 strcpy(recipient, rec);
1574 for (a = 0; a < strlen(recipient); ++a)
1575 if (!isprint(recipient[a]))
1576 strcpy(&recipient[a], &recipient[a + 1]);
1578 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1579 for (a=0; a<strlen(recipient); ++a) {
1580 if (recipient[a] == '@') {
1581 if (CtdlHostAlias(&recipient[a+1])
1582 == hostalias_localhost) {
1584 lprintf(7, "Changed to <%s>\n", recipient);
1585 mailtype = MES_LOCAL;
1590 lprintf(9, "Recipient is <%s>\n", recipient);
1592 /* Learn about what's inside, because it's what's inside that counts */
1593 lprintf(9, "Learning what's inside\n");
1594 if (msg->cm_fields['M'] == NULL) {
1595 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1598 switch (msg->cm_format_type) {
1600 strcpy(content_type, "text/x-citadel-variformat");
1603 strcpy(content_type, "text/plain");
1606 strcpy(content_type, "text/plain");
1607 /* advance past header fields */
1608 mptr = msg->cm_fields['M'];
1611 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1612 safestrncpy(content_type, mptr,
1613 sizeof(content_type));
1614 strcpy(content_type, &content_type[14]);
1615 for (a = 0; a < strlen(content_type); ++a)
1616 if ((content_type[a] == ';')
1617 || (content_type[a] == ' ')
1618 || (content_type[a] == 13)
1619 || (content_type[a] == 10))
1620 content_type[a] = 0;
1627 /* Goto the correct room */
1628 lprintf(9, "Switching rooms\n");
1629 strcpy(hold_rm, CC->quickroom.QRname);
1630 strcpy(actual_rm, CC->quickroom.QRname);
1632 /* If the user is a twit, move to the twit room for posting */
1633 lprintf(9, "Handling twit stuff\n");
1635 if (CC->usersupp.axlevel == 2) {
1636 strcpy(hold_rm, actual_rm);
1637 strcpy(actual_rm, config.c_twitroom);
1641 /* ...or if this message is destined for Aide> then go there. */
1642 if (strlen(force_room) > 0) {
1643 strcpy(actual_rm, force_room);
1646 lprintf(9, "Possibly relocating\n");
1647 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1648 getroom(&CC->quickroom, actual_rm);
1652 * If this message has no O (room) field, generate one.
1654 if (msg->cm_fields['O'] == NULL) {
1655 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1658 /* Perform "before save" hooks (aborting if any return nonzero) */
1659 lprintf(9, "Performing before-save hooks\n");
1660 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1662 /* If this message has an Extended ID, perform replication checks */
1663 lprintf(9, "Performing replication checks\n");
1664 if (ReplicationChecks(msg) > 0) return(-1);
1666 /* Network mail - send a copy to the network program. */
1667 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1668 lprintf(9, "Sending network spool\n");
1669 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1670 (long) getpid(), CC->cs_pid, ++seqnum);
1671 lprintf(9, "Saving a copy to %s\n", aaa);
1672 network_fp = fopen(aaa, "ab+");
1673 if (network_fp == NULL)
1674 lprintf(2, "ERROR: %s\n", strerror(errno));
1677 /* Save it to disk */
1678 lprintf(9, "Saving to disk\n");
1679 newmsgid = send_message(msg, network_fp);
1680 if (network_fp != NULL) {
1682 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1685 if (newmsgid <= 0L) return(-1);
1687 /* Write a supplemental message info record. This doesn't have to
1688 * be a critical section because nobody else knows about this message
1691 lprintf(9, "Creating SuppMsgInfo record\n");
1692 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1693 smi.smi_msgnum = newmsgid;
1694 smi.smi_refcount = 0;
1695 safestrncpy(smi.smi_content_type, content_type, 64);
1696 PutSuppMsgInfo(&smi);
1698 /* Now figure out where to store the pointers */
1699 lprintf(9, "Storing pointers\n");
1701 /* If this is being done by the networker delivering a private
1702 * message, we want to BYPASS saving the sender's copy (because there
1703 * is no local sender; it would otherwise go to the Trashcan).
1705 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1706 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1707 lprintf(3, "ERROR saving message pointer!\n");
1708 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1712 /* For internet mail, drop a copy in the outbound queue room */
1713 if (mailtype == MES_INTERNET) {
1714 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1717 /* Bump this user's messages posted counter. */
1718 lprintf(9, "Updating user\n");
1719 lgetuser(&CC->usersupp, CC->curr_user);
1720 CC->usersupp.posted = CC->usersupp.posted + 1;
1721 lputuser(&CC->usersupp);
1723 /* If this is private, local mail, make a copy in the
1724 * recipient's mailbox and bump the reference count.
1726 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1727 if (getuser(&userbuf, recipient) == 0) {
1728 lprintf(9, "Delivering private mail\n");
1729 MailboxName(actual_rm, &userbuf, MAILROOM);
1730 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1733 lprintf(9, "No user <%s>, saving in %s> instead\n",
1734 recipient, AIDEROOM);
1735 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1739 /* Perform "after save" hooks */
1740 lprintf(9, "Performing after-save hooks\n");
1741 PerformMessageHooks(msg, EVT_AFTERSAVE);
1744 lprintf(9, "Returning to original room\n");
1745 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1746 getroom(&CC->quickroom, hold_rm);
1748 /* For internet mail, generate delivery instructions
1749 * (Yes, this is recursive! Deal with it!)
1751 if (mailtype == MES_INTERNET) {
1752 lprintf(9, "Generating delivery instructions\n");
1753 instr = mallok(2048);
1755 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1758 SPOOLMIME, newmsgid, time(NULL),
1759 msg->cm_fields['A'], msg->cm_fields['N'],
1762 imsg = mallok(sizeof(struct CtdlMessage));
1763 memset(imsg, 0, sizeof(struct CtdlMessage));
1764 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1765 imsg->cm_anon_type = MES_NORMAL;
1766 imsg->cm_format_type = FMT_RFC822;
1767 imsg->cm_fields['A'] = strdoop("Citadel");
1768 imsg->cm_fields['M'] = instr;
1769 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1770 CtdlFreeMessage(imsg);
1779 * Convenience function for generating small administrative messages.
1781 void quickie_message(char *from, char *to, char *room, char *text)
1783 struct CtdlMessage *msg;
1785 msg = mallok(sizeof(struct CtdlMessage));
1786 memset(msg, 0, sizeof(struct CtdlMessage));
1787 msg->cm_magic = CTDLMESSAGE_MAGIC;
1788 msg->cm_anon_type = MES_NORMAL;
1789 msg->cm_format_type = 0;
1790 msg->cm_fields['A'] = strdoop(from);
1791 msg->cm_fields['O'] = strdoop(room);
1792 msg->cm_fields['N'] = strdoop(NODENAME);
1794 msg->cm_fields['R'] = strdoop(to);
1795 msg->cm_fields['M'] = strdoop(text);
1797 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1798 CtdlFreeMessage(msg);
1799 syslog(LOG_NOTICE, text);
1805 * Back end function used by make_message() and similar functions
1807 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1808 size_t maxlen, /* maximum message length */
1809 char *exist /* if non-null, append to it;
1810 exist is ALWAYS freed */
1813 size_t message_len = 0;
1814 size_t buffer_len = 0;
1818 if (exist == NULL) {
1822 m = reallok(exist, strlen(exist) + 4096);
1823 if (m == NULL) phree(exist);
1826 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1833 /* read in the lines of message text one by one */
1835 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1837 /* augment the buffer if we have to */
1838 if ((message_len + strlen(buf) + 2) > buffer_len) {
1839 lprintf(9, "realloking\n");
1840 ptr = reallok(m, (buffer_len * 2) );
1841 if (ptr == NULL) { /* flush if can't allocate */
1842 while ( (client_gets(buf)>0) &&
1843 strcmp(buf, terminator)) ;;
1846 buffer_len = (buffer_len * 2);
1849 lprintf(9, "buffer_len is %d\n", buffer_len);
1853 if (append == NULL) append = m;
1854 while (strlen(append) > 0) ++append;
1855 strcpy(append, buf);
1856 strcat(append, "\n");
1857 message_len = message_len + strlen(buf) + 1;
1859 /* if we've hit the max msg length, flush the rest */
1860 if (message_len >= maxlen) {
1861 while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;;
1872 * Build a binary message to be saved on disk.
1875 struct CtdlMessage *make_message(
1876 struct usersupp *author, /* author's usersupp structure */
1877 char *recipient, /* NULL if it's not mail */
1878 char *room, /* room where it's going */
1879 int type, /* see MES_ types in header file */
1880 int net_type, /* see MES_ types in header file */
1881 int format_type, /* local or remote (see citadel.h) */
1882 char *fake_name) /* who we're masquerading as */
1888 struct CtdlMessage *msg;
1890 msg = mallok(sizeof(struct CtdlMessage));
1891 memset(msg, 0, sizeof(struct CtdlMessage));
1892 msg->cm_magic = CTDLMESSAGE_MAGIC;
1893 msg->cm_anon_type = type;
1894 msg->cm_format_type = format_type;
1896 /* Don't confuse the poor folks if it's not routed mail. */
1897 strcpy(dest_node, "");
1899 /* If net_type is MES_BINARY, split out the destination node. */
1900 if (net_type == MES_BINARY) {
1901 strcpy(dest_node, NODENAME);
1902 for (a = 0; a < strlen(recipient); ++a) {
1903 if (recipient[a] == '@') {
1905 strcpy(dest_node, &recipient[a + 1]);
1910 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1911 if (net_type == MES_INTERNET) {
1912 strcpy(dest_node, "internet");
1915 while (isspace(recipient[strlen(recipient) - 1]))
1916 recipient[strlen(recipient) - 1] = 0;
1918 sprintf(buf, "cit%ld", author->usernum); /* Path */
1919 msg->cm_fields['P'] = strdoop(buf);
1921 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1922 msg->cm_fields['T'] = strdoop(buf);
1924 if (fake_name[0]) /* author */
1925 msg->cm_fields['A'] = strdoop(fake_name);
1927 msg->cm_fields['A'] = strdoop(author->fullname);
1929 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1930 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1932 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1934 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1935 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1937 if (recipient[0] != 0)
1938 msg->cm_fields['R'] = strdoop(recipient);
1939 if (dest_node[0] != 0)
1940 msg->cm_fields['D'] = strdoop(dest_node);
1943 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1944 config.c_maxmsglen, NULL);
1955 * message entry - mode 0 (normal)
1957 void cmd_ent0(char *entargs)
1960 char recipient[256];
1962 int format_type = 0;
1963 char newusername[256];
1964 struct CtdlMessage *msg;
1968 struct usersupp tempUS;
1971 post = extract_int(entargs, 0);
1972 extract(recipient, entargs, 1);
1973 anon_flag = extract_int(entargs, 2);
1974 format_type = extract_int(entargs, 3);
1976 /* first check to make sure the request is valid. */
1978 if (!(CC->logged_in)) {
1979 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1982 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1983 cprintf("%d Need to be validated to enter ",
1984 ERROR + HIGHER_ACCESS_REQUIRED);
1985 cprintf("(except in %s> to sysop)\n", MAILROOM);
1988 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
1989 cprintf("%d Need net privileges to enter here.\n",
1990 ERROR + HIGHER_ACCESS_REQUIRED);
1993 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
1994 cprintf("%d Sorry, this is a read-only room.\n",
1995 ERROR + HIGHER_ACCESS_REQUIRED);
2002 if (CC->usersupp.axlevel < 6) {
2003 cprintf("%d You don't have permission to masquerade.\n",
2004 ERROR + HIGHER_ACCESS_REQUIRED);
2007 extract(newusername, entargs, 4);
2008 memset(CC->fake_postname, 0, 32);
2009 strcpy(CC->fake_postname, newusername);
2010 cprintf("%d Ok\n", OK);
2013 CC->cs_flags |= CS_POSTING;
2016 if (CC->quickroom.QRflags & QR_MAILBOX) {
2017 if (CC->usersupp.axlevel >= 2) {
2018 strcpy(buf, recipient);
2020 strcpy(buf, "sysop");
2021 e = alias(buf); /* alias and mail type */
2022 if ((buf[0] == 0) || (e == MES_ERROR)) {
2023 cprintf("%d Unknown address - cannot send message.\n",
2024 ERROR + NO_SUCH_USER);
2027 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2028 cprintf("%d Net privileges required for network mail.\n",
2029 ERROR + HIGHER_ACCESS_REQUIRED);
2032 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2033 && ((CC->usersupp.flags & US_INTERNET) == 0)
2034 && (!CC->internal_pgm)) {
2035 cprintf("%d You don't have access to Internet mail.\n",
2036 ERROR + HIGHER_ACCESS_REQUIRED);
2039 if (!strcasecmp(buf, "sysop")) {
2044 goto SKFALL; /* don't search local file */
2045 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2046 cprintf("%d Can't send mail to yourself!\n",
2047 ERROR + NO_SUCH_USER);
2050 /* Check to make sure the user exists; also get the correct
2051 * upper/lower casing of the name.
2053 a = getuser(&tempUS, buf);
2055 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2058 strcpy(buf, tempUS.fullname);
2061 SKFALL: b = MES_NORMAL;
2062 if (CC->quickroom.QRflags & QR_ANONONLY)
2064 if (CC->quickroom.QRflags & QR_ANONOPT) {
2068 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2071 /* If we're only checking the validity of the request, return
2072 * success without creating the message.
2075 cprintf("%d %s\n", OK, buf);
2079 cprintf("%d send message\n", SEND_LISTING);
2081 /* Read in the message from the client. */
2082 if (CC->fake_postname[0])
2083 msg = make_message(&CC->usersupp, buf,
2084 CC->quickroom.QRname, b, e, format_type,
2086 else if (CC->fake_username[0])
2087 msg = make_message(&CC->usersupp, buf,
2088 CC->quickroom.QRname, b, e, format_type,
2091 msg = make_message(&CC->usersupp, buf,
2092 CC->quickroom.QRname, b, e, format_type, "");
2095 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2096 CtdlFreeMessage(msg);
2097 CC->fake_postname[0] = '\0';
2104 * message entry - mode 3 (raw)
2106 void cmd_ent3(char *entargs)
2112 unsigned char ch, which_field;
2113 struct usersupp tempUS;
2115 struct CtdlMessage *msg;
2118 if (CC->internal_pgm == 0) {
2119 cprintf("%d This command is for internal programs only.\n",
2124 /* See if there's a recipient, but make sure it's a real one */
2125 extract(recp, entargs, 1);
2126 for (a = 0; a < strlen(recp); ++a)
2127 if (!isprint(recp[a]))
2128 strcpy(&recp[a], &recp[a + 1]);
2129 while (isspace(recp[0]))
2130 strcpy(recp, &recp[1]);
2131 while (isspace(recp[strlen(recp) - 1]))
2132 recp[strlen(recp) - 1] = 0;
2134 /* If we're in Mail, check the recipient */
2135 if (strlen(recp) > 0) {
2136 e = alias(recp); /* alias and mail type */
2137 if ((recp[0] == 0) || (e == MES_ERROR)) {
2138 cprintf("%d Unknown address - cannot send message.\n",
2139 ERROR + NO_SUCH_USER);
2142 if (e == MES_LOCAL) {
2143 a = getuser(&tempUS, recp);
2145 cprintf("%d No such user.\n",
2146 ERROR + NO_SUCH_USER);
2152 /* At this point, message has been approved. */
2153 if (extract_int(entargs, 0) == 0) {
2154 cprintf("%d OK to send\n", OK);
2158 msglen = extract_long(entargs, 2);
2159 msg = mallok(sizeof(struct CtdlMessage));
2161 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2165 memset(msg, 0, sizeof(struct CtdlMessage));
2166 tempbuf = mallok(msglen);
2167 if (tempbuf == NULL) {
2168 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2173 cprintf("%d %ld\n", SEND_BINARY, msglen);
2175 client_read(&ch, 1); /* 0xFF magic number */
2176 msg->cm_magic = CTDLMESSAGE_MAGIC;
2177 client_read(&ch, 1); /* anon type */
2178 msg->cm_anon_type = ch;
2179 client_read(&ch, 1); /* format type */
2180 msg->cm_format_type = ch;
2181 msglen = msglen - 3;
2183 while (msglen > 0) {
2184 client_read(&which_field, 1);
2185 if (!isalpha(which_field)) valid_msg = 0;
2189 client_read(&ch, 1);
2191 a = strlen(tempbuf);
2194 } while ( (ch != 0) && (msglen > 0) );
2196 msg->cm_fields[which_field] = strdoop(tempbuf);
2199 msg->cm_flags = CM_SKIP_HOOKS;
2200 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2201 CtdlFreeMessage(msg);
2207 * API function to delete messages which match a set of criteria
2208 * (returns the actual number of messages deleted)
2210 int CtdlDeleteMessages(char *room_name, /* which room */
2211 long dmsgnum, /* or "0" for any */
2212 char *content_type /* or "" for any */
2216 struct quickroom qrbuf;
2217 struct cdbdata *cdbfr;
2218 long *msglist = NULL;
2221 int num_deleted = 0;
2223 struct SuppMsgInfo smi;
2225 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2226 room_name, dmsgnum, content_type);
2228 /* get room record, obtaining a lock... */
2229 if (lgetroom(&qrbuf, room_name) != 0) {
2230 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2232 return (0); /* room not found */
2234 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2236 if (cdbfr != NULL) {
2237 msglist = mallok(cdbfr->len);
2238 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2239 num_msgs = cdbfr->len / sizeof(long);
2243 for (i = 0; i < num_msgs; ++i) {
2246 /* Set/clear a bit for each criterion */
2248 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2249 delete_this |= 0x01;
2251 if (strlen(content_type) == 0) {
2252 delete_this |= 0x02;
2254 GetSuppMsgInfo(&smi, msglist[i]);
2255 if (!strcasecmp(smi.smi_content_type,
2257 delete_this |= 0x02;
2261 /* Delete message only if all bits are set */
2262 if (delete_this == 0x03) {
2263 AdjRefCount(msglist[i], -1);
2269 num_msgs = sort_msglist(msglist, num_msgs);
2270 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2271 msglist, (num_msgs * sizeof(long)));
2273 qrbuf.QRhighest = msglist[num_msgs - 1];
2277 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2278 return (num_deleted);
2284 * Delete message from current room
2286 void cmd_dele(char *delstr)
2291 getuser(&CC->usersupp, CC->curr_user);
2292 if ((CC->usersupp.axlevel < 6)
2293 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2294 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2295 && (!(CC->internal_pgm))) {
2296 cprintf("%d Higher access required.\n",
2297 ERROR + HIGHER_ACCESS_REQUIRED);
2300 delnum = extract_long(delstr, 0);
2302 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2305 cprintf("%d %d message%s deleted.\n", OK,
2306 num_deleted, ((num_deleted != 1) ? "s" : ""));
2308 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2314 * move or copy a message to another room
2316 void cmd_move(char *args)
2320 struct quickroom qtemp;
2324 num = extract_long(args, 0);
2325 extract(targ, args, 1);
2326 targ[ROOMNAMELEN - 1] = 0;
2327 is_copy = extract_int(args, 2);
2329 getuser(&CC->usersupp, CC->curr_user);
2330 if ((CC->usersupp.axlevel < 6)
2331 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2332 cprintf("%d Higher access required.\n",
2333 ERROR + HIGHER_ACCESS_REQUIRED);
2337 if (getroom(&qtemp, targ) != 0) {
2338 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2342 err = CtdlSaveMsgPointerInRoom(targ, num,
2343 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2345 cprintf("%d Cannot store message in %s: error %d\n",
2350 /* Now delete the message from the source room,
2351 * if this is a 'move' rather than a 'copy' operation.
2353 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2355 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2361 * GetSuppMsgInfo() - Get the supplementary record for a message
2363 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2366 struct cdbdata *cdbsmi;
2369 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2370 smibuf->smi_msgnum = msgnum;
2371 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2373 /* Use the negative of the message number for its supp record index */
2374 TheIndex = (0L - msgnum);
2376 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2377 if (cdbsmi == NULL) {
2378 return; /* record not found; go with defaults */
2380 memcpy(smibuf, cdbsmi->ptr,
2381 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2382 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2389 * PutSuppMsgInfo() - (re)write supplementary record for a message
2391 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2395 /* Use the negative of the message number for its supp record index */
2396 TheIndex = (0L - smibuf->smi_msgnum);
2398 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2399 smibuf->smi_msgnum, smibuf->smi_refcount);
2401 cdb_store(CDB_MSGMAIN,
2402 &TheIndex, sizeof(long),
2403 smibuf, sizeof(struct SuppMsgInfo));
2408 * AdjRefCount - change the reference count for a message;
2409 * delete the message if it reaches zero
2411 void AdjRefCount(long msgnum, int incr)
2414 struct SuppMsgInfo smi;
2417 /* This is a *tight* critical section; please keep it that way, as
2418 * it may get called while nested in other critical sections.
2419 * Complicating this any further will surely cause deadlock!
2421 begin_critical_section(S_SUPPMSGMAIN);
2422 GetSuppMsgInfo(&smi, msgnum);
2423 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2424 msgnum, smi.smi_refcount);
2425 smi.smi_refcount += incr;
2426 PutSuppMsgInfo(&smi);
2427 end_critical_section(S_SUPPMSGMAIN);
2428 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2429 msgnum, smi.smi_refcount);
2431 /* If the reference count is now zero, delete the message
2432 * (and its supplementary record as well).
2434 if (smi.smi_refcount == 0) {
2435 lprintf(9, "Deleting message <%ld>\n", msgnum);
2437 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2438 delnum = (0L - msgnum);
2439 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2444 * Write a generic object to this room
2446 * Note: this could be much more efficient. Right now we use two temporary
2447 * files, and still pull the message into memory as with all others.
2449 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2450 char *content_type, /* MIME type of this object */
2451 char *tempfilename, /* Where to fetch it from */
2452 struct usersupp *is_mailbox, /* Mailbox room? */
2453 int is_binary, /* Is encoding necessary? */
2454 int is_unique, /* Del others of this type? */
2455 unsigned int flags /* Internal save flags */
2460 char filename[PATH_MAX];
2463 struct quickroom qrbuf;
2464 char roomname[ROOMNAMELEN];
2465 struct CtdlMessage *msg;
2468 if (is_mailbox != NULL)
2469 MailboxName(roomname, is_mailbox, req_room);
2471 safestrncpy(roomname, req_room, sizeof(roomname));
2472 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2474 strcpy(filename, tmpnam(NULL));
2475 fp = fopen(filename, "w");
2479 tempfp = fopen(tempfilename, "r");
2480 if (tempfp == NULL) {
2486 fprintf(fp, "Content-type: %s\n", content_type);
2487 lprintf(9, "Content-type: %s\n", content_type);
2489 if (is_binary == 0) {
2490 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2491 while (ch = getc(tempfp), ch > 0)
2497 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2500 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2501 tempfilename, filename);
2505 lprintf(9, "Allocating\n");
2506 msg = mallok(sizeof(struct CtdlMessage));
2507 memset(msg, 0, sizeof(struct CtdlMessage));
2508 msg->cm_magic = CTDLMESSAGE_MAGIC;
2509 msg->cm_anon_type = MES_NORMAL;
2510 msg->cm_format_type = 4;
2511 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2512 msg->cm_fields['O'] = strdoop(req_room);
2513 msg->cm_fields['N'] = strdoop(config.c_nodename);
2514 msg->cm_fields['H'] = strdoop(config.c_humannode);
2515 msg->cm_flags = flags;
2517 lprintf(9, "Loading\n");
2518 fp = fopen(filename, "rb");
2519 fseek(fp, 0L, SEEK_END);
2522 msg->cm_fields['M'] = mallok(len);
2523 fread(msg->cm_fields['M'], len, 1, fp);
2527 /* Create the requested room if we have to. */
2528 if (getroom(&qrbuf, roomname) != 0) {
2529 create_room(roomname,
2530 ( (is_mailbox != NULL) ? 4 : 3 ),
2533 /* If the caller specified this object as unique, delete all
2534 * other objects of this type that are currently in the room.
2537 lprintf(9, "Deleted %d other msgs of this type\n",
2538 CtdlDeleteMessages(roomname, 0L, content_type));
2540 /* Now write the data */
2541 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2542 CtdlFreeMessage(msg);
2550 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2551 config_msgnum = msgnum;
2555 char *CtdlGetSysConfig(char *sysconfname) {
2556 char hold_rm[ROOMNAMELEN];
2559 struct CtdlMessage *msg;
2562 strcpy(hold_rm, CC->quickroom.QRname);
2563 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2564 getroom(&CC->quickroom, hold_rm);
2569 /* We want the last (and probably only) config in this room */
2570 begin_critical_section(S_CONFIG);
2571 config_msgnum = (-1L);
2572 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2573 CtdlGetSysConfigBackend, NULL);
2574 msgnum = config_msgnum;
2575 end_critical_section(S_CONFIG);
2581 msg = CtdlFetchMessage(msgnum);
2583 conf = strdoop(msg->cm_fields['M']);
2584 CtdlFreeMessage(msg);
2591 getroom(&CC->quickroom, hold_rm);
2593 lprintf(9, "eggstracting...\n");
2594 if (conf != NULL) do {
2595 extract_token(buf, conf, 0, '\n');
2596 lprintf(9, "eggstracted <%s>\n", buf);
2597 strcpy(conf, &conf[strlen(buf)+1]);
2598 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2603 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2604 char temp[PATH_MAX];
2607 strcpy(temp, tmpnam(NULL));
2609 fp = fopen(temp, "w");
2610 if (fp == NULL) return;
2611 fprintf(fp, "%s", sysconfdata);
2614 /* this handy API function does all the work for us */
2615 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);