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) {
90 while (isspace(name[i - 1]) && i > 0) {
91 strcpy(&name[i - 1], &name[i]);
94 while (isspace(name[i + 1])) {
95 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];
112 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
114 fp = fopen("network/mail.aliases", "r");
116 fp = fopen("/dev/null", "r");
121 while (fgets(aaa, sizeof aaa, fp) != NULL) {
122 while (isspace(name[0]))
123 strcpy(name, &name[1]);
124 aaa[strlen(aaa) - 1] = 0;
126 for (a = 0; a < strlen(aaa); ++a) {
128 strcpy(bbb, &aaa[a + 1]);
132 if (!strcasecmp(name, aaa))
136 lprintf(7, "Mail is being forwarded to %s\n", name);
138 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
139 for (a=0; a<strlen(name); ++a) {
140 if (name[a] == '@') {
141 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
143 lprintf(7, "Changed to <%s>\n", name);
148 /* determine local or remote type, see citadel.h */
149 for (a = 0; a < strlen(name); ++a)
151 return (MES_INTERNET);
152 for (a = 0; a < strlen(name); ++a)
154 for (b = a; b < strlen(name); ++b)
156 return (MES_INTERNET);
158 for (a = 0; a < strlen(name); ++a)
162 lprintf(7, "Too many @'s in address\n");
166 for (a = 0; a < strlen(name); ++a)
168 strcpy(bbb, &name[a + 1]);
170 strcpy(bbb, &bbb[1]);
171 fp = fopen("network/mail.sysinfo", "r");
175 a = getstring(fp, aaa);
176 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
177 a = getstring(fp, aaa);
178 if (!strncmp(aaa, "use ", 4)) {
179 strcpy(bbb, &aaa[4]);
184 if (!strncmp(aaa, "uum", 3)) {
186 for (a = 0; a < strlen(bbb); ++a) {
192 while (bbb[strlen(bbb) - 1] == '_')
193 bbb[strlen(bbb) - 1] = 0;
194 sprintf(name, &aaa[4], bbb);
195 lprintf(9, "returning MES_INTERNET\n");
196 return (MES_INTERNET);
198 if (!strncmp(aaa, "bin", 3)) {
201 while (aaa[strlen(aaa) - 1] != '@')
202 aaa[strlen(aaa) - 1] = 0;
203 aaa[strlen(aaa) - 1] = 0;
204 while (aaa[strlen(aaa) - 1] == ' ')
205 aaa[strlen(aaa) - 1] = 0;
206 while (bbb[0] != '@')
207 strcpy(bbb, &bbb[1]);
208 strcpy(bbb, &bbb[1]);
209 while (bbb[0] == ' ')
210 strcpy(bbb, &bbb[1]);
211 sprintf(name, "%s @%s", aaa, bbb);
212 lprintf(9, "returning MES_BINARY\n");
217 lprintf(9, "returning MES_LOCAL\n");
226 fp = fopen("citadel.control", "r");
227 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
233 void simple_listing(long msgnum, void *userdata)
235 cprintf("%ld\n", msgnum);
240 /* Determine if a given message matches the fields in a message template.
241 * Return 0 for a successful match.
243 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
246 /* If there aren't any fields in the template, all messages will
249 if (template == NULL) return(0);
251 /* Null messages are bogus. */
252 if (msg == NULL) return(1);
254 for (i='A'; i<='Z'; ++i) {
255 if (template->cm_fields[i] != NULL) {
256 if (msg->cm_fields[i] == NULL) {
259 if (strcasecmp(msg->cm_fields[i],
260 template->cm_fields[i])) return 1;
264 /* All compares succeeded: we have a match! */
272 * API function to perform an operation for each qualifying message in the
273 * current room. (Returns the number of messages processed.)
275 int CtdlForEachMessage(int mode, long ref,
276 int moderation_level,
278 struct CtdlMessage *compare,
279 void (*CallBack) (long, void *),
285 struct cdbdata *cdbfr;
286 long *msglist = NULL;
288 int num_processed = 0;
290 struct SuppMsgInfo smi;
291 struct CtdlMessage *msg;
293 /* Learn about the user and room in question */
295 getuser(&CC->usersupp, CC->curr_user);
296 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
298 /* Load the message list */
299 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
301 msglist = mallok(cdbfr->len);
302 memcpy(msglist, cdbfr->ptr, cdbfr->len);
303 num_msgs = cdbfr->len / sizeof(long);
306 return 0; /* No messages at all? No further action. */
310 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
311 GetSuppMsgInfo(&smi, msglist[a]);
313 /* Filter out messages that are moderated below the level
314 * currently being viewed at.
316 if (smi.smi_mod < moderation_level) {
320 /* If the caller is looking for a specific MIME type, filter
321 * out all messages which are not of the type requested.
323 if (content_type != NULL) if (strlen(content_type) > 0) {
324 if (strcasecmp(smi.smi_content_type, content_type)) {
330 num_msgs = sort_msglist(msglist, num_msgs);
332 /* If a template was supplied, filter out the messages which
333 * don't match. (This could induce some delays!)
336 if (compare != NULL) {
337 for (a = 0; a < num_msgs; ++a) {
338 msg = CtdlFetchMessage(msglist[a]);
340 if (CtdlMsgCmp(msg, compare)) {
343 CtdlFreeMessage(msg);
351 * Now iterate through the message list, according to the
352 * criteria supplied by the caller.
355 for (a = 0; a < num_msgs; ++a) {
356 thismsg = msglist[a];
361 || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen))
362 || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen))
363 || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen)
364 && (CC->usersupp.flags & US_LASTOLD))
365 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
366 || ((mode == MSGS_FIRST) && (a < ref))
367 || ((mode == MSGS_GT) && (thismsg > ref))
368 || ((mode == MSGS_EQ) && (thismsg == ref))
371 if (CallBack) CallBack(thismsg, userdata);
375 phree(msglist); /* Clean up */
376 return num_processed;
382 * cmd_msgs() - get list of message #'s in this room
383 * implements the MSGS server command using CtdlForEachMessage()
385 void cmd_msgs(char *cmdbuf)
394 int with_template = 0;
395 struct CtdlMessage *template = NULL;
397 extract(which, cmdbuf, 0);
398 cm_ref = extract_int(cmdbuf, 1);
399 with_template = extract_int(cmdbuf, 2);
403 if (!strncasecmp(which, "OLD", 3))
405 else if (!strncasecmp(which, "NEW", 3))
407 else if (!strncasecmp(which, "FIRST", 5))
409 else if (!strncasecmp(which, "LAST", 4))
411 else if (!strncasecmp(which, "GT", 2))
414 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
415 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
420 cprintf("%d Send template then receive message list\n",
422 template = (struct CtdlMessage *)
423 mallok(sizeof(struct CtdlMessage));
424 memset(template, 0, sizeof(struct CtdlMessage));
425 while(client_gets(buf), strcmp(buf,"000")) {
426 extract(tfield, buf, 0);
427 extract(tvalue, buf, 1);
428 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
429 if (!strcasecmp(tfield, msgkeys[i])) {
430 template->cm_fields[i] =
437 cprintf("%d Message list...\n", LISTING_FOLLOWS);
440 CtdlForEachMessage(mode, cm_ref,
441 CC->usersupp.moderation_filter,
442 NULL, template, simple_listing, NULL);
443 if (template != NULL) CtdlFreeMessage(template);
451 * help_subst() - support routine for help file viewer
453 void help_subst(char *strbuf, char *source, char *dest)
458 while (p = pattern2(strbuf, source), (p >= 0)) {
459 strcpy(workbuf, &strbuf[p + strlen(source)]);
460 strcpy(&strbuf[p], dest);
461 strcat(strbuf, workbuf);
466 void do_help_subst(char *buffer)
470 help_subst(buffer, "^nodename", config.c_nodename);
471 help_subst(buffer, "^humannode", config.c_humannode);
472 help_subst(buffer, "^fqdn", config.c_fqdn);
473 help_subst(buffer, "^username", CC->usersupp.fullname);
474 sprintf(buf2, "%ld", CC->usersupp.usernum);
475 help_subst(buffer, "^usernum", buf2);
476 help_subst(buffer, "^sysadm", config.c_sysadm);
477 help_subst(buffer, "^variantname", CITADEL);
478 sprintf(buf2, "%d", config.c_maxsessions);
479 help_subst(buffer, "^maxsessions", buf2);
485 * memfmout() - Citadel text formatter and paginator.
486 * Although the original purpose of this routine was to format
487 * text to the reader's screen width, all we're really using it
488 * for here is to format text out to 80 columns before sending it
489 * to the client. The client software may reformat it again.
492 int width, /* screen width to use */
493 char *mptr, /* where are we going to get our text from? */
494 char subst, /* nonzero if we should do substitutions */
495 char *nl) /* string to terminate lines with */
507 c = 1; /* c is the current pos */
511 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
513 buffer[strlen(buffer) + 1] = 0;
514 buffer[strlen(buffer)] = ch;
517 if (buffer[0] == '^')
518 do_help_subst(buffer);
520 buffer[strlen(buffer) + 1] = 0;
522 strcpy(buffer, &buffer[1]);
530 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
532 if (((old == 13) || (old == 10)) && (isspace(real))) {
540 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
541 cprintf("%s%s", nl, aaa);
550 if ((strlen(aaa) + c) > (width - 5)) {
559 if ((ch == 13) || (ch == 10)) {
560 cprintf("%s%s", aaa, nl);
567 cprintf("%s%s", aaa, nl);
573 * Callback function for mime parser that simply lists the part
575 void list_this_part(char *name, char *filename, char *partnum, char *disp,
576 void *content, char *cbtype, size_t length, char *encoding,
580 cprintf("part=%s|%s|%s|%s|%s|%d\n",
581 name, filename, partnum, disp, cbtype, length);
586 * Callback function for mime parser that opens a section for downloading
588 void mime_download(char *name, char *filename, char *partnum, char *disp,
589 void *content, char *cbtype, size_t length, char *encoding,
593 /* Silently go away if there's already a download open... */
594 if (CC->download_fp != NULL)
597 /* ...or if this is not the desired section */
598 if (strcasecmp(desired_section, partnum))
601 CC->download_fp = tmpfile();
602 if (CC->download_fp == NULL)
605 fwrite(content, length, 1, CC->download_fp);
606 fflush(CC->download_fp);
607 rewind(CC->download_fp);
609 OpenCmdResult(filename, cbtype);
615 * Load a message from disk into memory.
616 * This is used by CtdlOutputMsg() and other fetch functions.
618 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
619 * using the CtdlMessageFree() function.
621 struct CtdlMessage *CtdlFetchMessage(long msgnum)
623 struct cdbdata *dmsgtext;
624 struct CtdlMessage *ret = NULL;
627 CIT_UBYTE field_header;
630 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
631 if (dmsgtext == NULL) {
634 mptr = dmsgtext->ptr;
636 /* Parse the three bytes that begin EVERY message on disk.
637 * The first is always 0xFF, the on-disk magic number.
638 * The second is the anonymous/public type byte.
639 * The third is the format type byte (vari, fixed, or MIME).
643 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
647 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
648 memset(ret, 0, sizeof(struct CtdlMessage));
650 ret->cm_magic = CTDLMESSAGE_MAGIC;
651 ret->cm_anon_type = *mptr++; /* Anon type byte */
652 ret->cm_format_type = *mptr++; /* Format type byte */
655 * The rest is zero or more arbitrary fields. Load them in.
656 * We're done when we encounter either a zero-length field or
657 * have just processed the 'M' (message text) field.
660 field_length = strlen(mptr);
661 if (field_length == 0)
663 field_header = *mptr++;
664 ret->cm_fields[field_header] = mallok(field_length);
665 strcpy(ret->cm_fields[field_header], mptr);
667 while (*mptr++ != 0); /* advance to next field */
669 } while ((field_length > 0) && (field_header != 'M'));
673 /* Always make sure there's something in the msg text field */
674 if (ret->cm_fields['M'] == NULL)
675 ret->cm_fields['M'] = strdoop("<no text>\n");
677 /* Perform "before read" hooks (aborting if any return nonzero) */
678 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
679 CtdlFreeMessage(ret);
688 * Returns 1 if the supplied pointer points to a valid Citadel message.
689 * If the pointer is NULL or the magic number check fails, returns 0.
691 int is_valid_message(struct CtdlMessage *msg) {
694 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
695 lprintf(3, "is_valid_message() -- self-check failed\n");
703 * 'Destructor' for struct CtdlMessage
705 void CtdlFreeMessage(struct CtdlMessage *msg)
709 if (is_valid_message(msg) == 0) return;
711 for (i = 0; i < 256; ++i)
712 if (msg->cm_fields[i] != NULL) {
713 phree(msg->cm_fields[i]);
716 msg->cm_magic = 0; /* just in case */
722 * Callback function for mime parser that wants to display text
724 void fixed_output(char *name, char *filename, char *partnum, char *disp,
725 void *content, char *cbtype, size_t length, char *encoding,
733 if (!strcasecmp(cbtype, "multipart/alternative")) {
734 strcpy(ma->prefix, partnum);
735 strcat(ma->prefix, ".");
741 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
743 && (ma->did_print == 1) ) {
744 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
750 if ( (!strcasecmp(cbtype, "text/plain"))
751 || (strlen(cbtype)==0) ) {
756 if (ch==10) cprintf("\r\n");
757 else cprintf("%c", ch);
760 else if (!strcasecmp(cbtype, "text/html")) {
761 ptr = html_to_ascii(content, 80, 0);
766 if (ch==10) cprintf("\r\n");
767 else cprintf("%c", ch);
771 else if (strncasecmp(cbtype, "multipart/", 10)) {
772 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
773 partnum, filename, cbtype, length);
779 * Get a message off disk. (returns om_* values found in msgbase.h)
782 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
783 int mode, /* how would you like that message? */
784 int headers_only, /* eschew the message body? */
785 int do_proto, /* do Citadel protocol responses? */
786 int crlf /* Use CRLF newlines instead of LF? */
788 struct CtdlMessage *TheMessage;
791 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
796 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
797 if (do_proto) cprintf("%d Not logged in.\n",
798 ERROR + NOT_LOGGED_IN);
799 return(om_not_logged_in);
802 /* FIXME ... small security issue
803 * We need to check to make sure the requested message is actually
804 * in the current room, and set msg_ok to 1 only if it is. This
805 * functionality is currently missing because I'm in a hurry to replace
806 * broken production code with nonbroken pre-beta code. :( -- ajc
809 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
811 return(om_no_such_msg);
816 * Fetch the message from disk
818 TheMessage = CtdlFetchMessage(msg_num);
819 if (TheMessage == NULL) {
820 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
822 return(om_no_such_msg);
825 retcode = CtdlOutputPreLoadedMsg(
826 TheMessage, msg_num, mode,
827 headers_only, do_proto, crlf);
829 CtdlFreeMessage(TheMessage);
835 * Get a message off disk. (returns om_* values found in msgbase.h)
838 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
840 int mode, /* how would you like that message? */
841 int headers_only, /* eschew the message body? */
842 int do_proto, /* do Citadel protocol responses? */
843 int crlf /* Use CRLF newlines instead of LF? */
849 char display_name[SIZ];
851 char *nl; /* newline string */
853 /* buffers needed for RFC822 translation */
863 sprintf(mid, "%ld", msg_num);
864 nl = (crlf ? "\r\n" : "\n");
866 if (!is_valid_message(TheMessage)) {
867 lprintf(1, "ERROR: invalid preloaded message for output\n");
868 return(om_no_such_msg);
871 /* Are we downloading a MIME component? */
872 if (mode == MT_DOWNLOAD) {
873 if (TheMessage->cm_format_type != FMT_RFC822) {
875 cprintf("%d This is not a MIME message.\n",
877 } else if (CC->download_fp != NULL) {
878 if (do_proto) cprintf(
879 "%d You already have a download open.\n",
882 /* Parse the message text component */
883 mptr = TheMessage->cm_fields['M'];
884 mime_parser(mptr, NULL,
885 *mime_download, NULL, NULL,
887 /* If there's no file open by this time, the requested
888 * section wasn't found, so print an error
890 if (CC->download_fp == NULL) {
891 if (do_proto) cprintf(
892 "%d Section %s not found.\n",
893 ERROR + FILE_NOT_FOUND,
897 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
900 /* now for the user-mode message reading loops */
901 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
903 /* Tell the client which format type we're using. If this is a
904 * MIME message, *lie* about it and tell the user it's fixed-format.
906 if (mode == MT_CITADEL) {
907 if (TheMessage->cm_format_type == FMT_RFC822) {
908 if (do_proto) cprintf("type=1\n");
911 if (do_proto) cprintf("type=%d\n",
912 TheMessage->cm_format_type);
916 /* nhdr=yes means that we're only displaying headers, no body */
917 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
918 if (do_proto) cprintf("nhdr=yes\n");
921 /* begin header processing loop for Citadel message format */
923 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
925 strcpy(display_name, "<unknown>");
926 if (TheMessage->cm_fields['A']) {
927 strcpy(buf, TheMessage->cm_fields['A']);
928 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
929 if (TheMessage->cm_anon_type == MES_ANON)
930 strcpy(display_name, "****");
931 else if (TheMessage->cm_anon_type == MES_AN2)
932 strcpy(display_name, "anonymous");
934 strcpy(display_name, buf);
936 && ((TheMessage->cm_anon_type == MES_ANON)
937 || (TheMessage->cm_anon_type == MES_AN2))) {
938 sprintf(&display_name[strlen(display_name)],
943 strcpy(allkeys, FORDER);
944 for (i=0; i<strlen(allkeys); ++i) {
945 k = (int) allkeys[i];
947 if (TheMessage->cm_fields[k] != NULL) {
949 if (do_proto) cprintf("%s=%s\n",
954 if (do_proto) cprintf("%s=%s\n",
956 TheMessage->cm_fields[k]
965 /* begin header processing loop for RFC822 transfer format */
970 strcpy(snode, NODENAME);
971 strcpy(lnode, HUMANNODE);
972 if (mode == MT_RFC822) {
973 cprintf("X-UIDL: %ld%s", msg_num, nl);
974 for (i = 0; i < 256; ++i) {
975 if (TheMessage->cm_fields[i]) {
976 mptr = TheMessage->cm_fields[i];
983 "Path:" removed for now because it confuses brain-dead Microsoft shitware
984 into thinking that mail messages are newsgroup messages instead. When we
985 add NNTP support back into Citadel we'll have to add code to only output
986 this field when appropriate.
988 cprintf("Path: %s%s", mptr, nl);
992 cprintf("Subject: %s%s", mptr, nl);
998 cprintf("X-Citadel-Room: %s%s",
1001 strcpy(snode, mptr);
1003 cprintf("To: %s%s", mptr, nl);
1004 else if (i == 'T') {
1005 datestring(datestamp, atol(mptr),
1006 DATESTRING_RFC822 );
1007 cprintf("Date: %s%s", datestamp, nl);
1013 for (i=0; i<strlen(suser); ++i) {
1014 suser[i] = tolower(suser[i]);
1015 if (!isalnum(suser[i])) suser[i]='_';
1018 if (mode == MT_RFC822) {
1019 if (!strcasecmp(snode, NODENAME)) {
1020 strcpy(snode, FQDN);
1023 /* Construct a fun message id */
1024 cprintf("Message-ID: <%s", mid);
1025 if (strchr(mid, '@')==NULL) {
1026 cprintf("@%s", snode);
1030 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1032 if (strlen(fuser) > 0) {
1033 cprintf("From: %s (%s)%s", fuser, luser, nl);
1036 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1039 cprintf("Organization: %s%s", lnode, nl);
1042 /* end header processing loop ... at this point, we're in the text */
1044 mptr = TheMessage->cm_fields['M'];
1046 /* Tell the client about the MIME parts in this message */
1047 if (TheMessage->cm_format_type == FMT_RFC822) {
1048 if (mode == MT_CITADEL) {
1049 mime_parser(mptr, NULL,
1050 *list_this_part, NULL, NULL,
1053 else if (mode == MT_MIME) { /* list parts only */
1054 mime_parser(mptr, NULL,
1055 *list_this_part, NULL, NULL,
1057 if (do_proto) cprintf("000\n");
1060 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1061 /* FIXME ... we have to put some code in here to avoid
1062 * printing duplicate header information when both
1063 * Citadel and RFC822 headers exist. Preference should
1064 * probably be given to the RFC822 headers.
1066 while (ch=*(mptr++), ch!=0) {
1068 else if (ch==10) cprintf("%s", nl);
1069 else cprintf("%c", ch);
1071 if (do_proto) cprintf("000\n");
1077 if (do_proto) cprintf("000\n");
1081 /* signify start of msg text */
1082 if (mode == MT_CITADEL)
1083 if (do_proto) cprintf("text\n");
1084 if (mode == MT_RFC822) {
1085 if (TheMessage->cm_fields['U'] == NULL) {
1086 cprintf("Subject: (no subject)%s", nl);
1091 /* If the format type on disk is 1 (fixed-format), then we want
1092 * everything to be output completely literally ... regardless of
1093 * what message transfer format is in use.
1095 if (TheMessage->cm_format_type == FMT_FIXED) {
1097 while (ch = *mptr++, ch > 0) {
1100 if ((ch == 10) || (strlen(buf) > 250)) {
1101 cprintf("%s%s", buf, nl);
1104 buf[strlen(buf) + 1] = 0;
1105 buf[strlen(buf)] = ch;
1108 if (strlen(buf) > 0)
1109 cprintf("%s%s", buf, nl);
1112 /* If the message on disk is format 0 (Citadel vari-format), we
1113 * output using the formatter at 80 columns. This is the final output
1114 * form if the transfer format is RFC822, but if the transfer format
1115 * is Citadel proprietary, it'll still work, because the indentation
1116 * for new paragraphs is correct and the client will reformat the
1117 * message to the reader's screen width.
1119 if (TheMessage->cm_format_type == FMT_CITADEL) {
1120 memfmout(80, mptr, 0, nl);
1123 /* If the message on disk is format 4 (MIME), we've gotta hand it
1124 * off to the MIME parser. The client has already been told that
1125 * this message is format 1 (fixed format), so the callback function
1126 * we use will display those parts as-is.
1128 if (TheMessage->cm_format_type == FMT_RFC822) {
1129 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1130 memset(ma, 0, sizeof(struct ma_info));
1131 mime_parser(mptr, NULL,
1132 *fixed_output, NULL, NULL,
1136 /* now we're done */
1137 if (do_proto) cprintf("000\n");
1144 * display a message (mode 0 - Citadel proprietary)
1146 void cmd_msg0(char *cmdbuf)
1149 int headers_only = 0;
1151 msgid = extract_long(cmdbuf, 0);
1152 headers_only = extract_int(cmdbuf, 1);
1154 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1160 * display a message (mode 2 - RFC822)
1162 void cmd_msg2(char *cmdbuf)
1165 int headers_only = 0;
1167 msgid = extract_long(cmdbuf, 0);
1168 headers_only = extract_int(cmdbuf, 1);
1170 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1176 * display a message (mode 3 - IGnet raw format - internal programs only)
1178 void cmd_msg3(char *cmdbuf)
1181 struct CtdlMessage *msg;
1184 if (CC->internal_pgm == 0) {
1185 cprintf("%d This command is for internal programs only.\n",
1190 msgnum = extract_long(cmdbuf, 0);
1191 msg = CtdlFetchMessage(msgnum);
1193 cprintf("%d Message %ld not found.\n",
1198 serialize_message(&smr, msg);
1199 CtdlFreeMessage(msg);
1202 cprintf("%d Unable to serialize message\n",
1203 ERROR+INTERNAL_ERROR);
1207 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1208 client_write(smr.ser, smr.len);
1215 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1217 void cmd_msg4(char *cmdbuf)
1221 msgid = extract_long(cmdbuf, 0);
1222 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1226 * Open a component of a MIME message as a download file
1228 void cmd_opna(char *cmdbuf)
1232 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1234 msgid = extract_long(cmdbuf, 0);
1235 extract(desired_section, cmdbuf, 1);
1237 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1242 * Save a message pointer into a specified room
1243 * (Returns 0 for success, nonzero for failure)
1244 * roomname may be NULL to use the current room
1246 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1248 char hold_rm[ROOMNAMELEN];
1249 struct cdbdata *cdbfr;
1252 long highest_msg = 0L;
1253 struct CtdlMessage *msg = NULL;
1255 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1256 roomname, msgid, flags);
1258 strcpy(hold_rm, CC->quickroom.QRname);
1260 /* We may need to check to see if this message is real */
1261 if ( (flags & SM_VERIFY_GOODNESS)
1262 || (flags & SM_DO_REPL_CHECK)
1264 msg = CtdlFetchMessage(msgid);
1265 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1268 /* Perform replication checks if necessary */
1269 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1271 if (getroom(&CC->quickroom,
1272 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1274 lprintf(9, "No such room <%s>\n", roomname);
1275 if (msg != NULL) CtdlFreeMessage(msg);
1276 return(ERROR + ROOM_NOT_FOUND);
1279 if (ReplicationChecks(msg) != 0) {
1280 getroom(&CC->quickroom, hold_rm);
1281 if (msg != NULL) CtdlFreeMessage(msg);
1282 lprintf(9, "Did replication, and newer exists\n");
1287 /* Now the regular stuff */
1288 if (lgetroom(&CC->quickroom,
1289 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1291 lprintf(9, "No such room <%s>\n", roomname);
1292 if (msg != NULL) CtdlFreeMessage(msg);
1293 return(ERROR + ROOM_NOT_FOUND);
1296 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1297 if (cdbfr == NULL) {
1301 msglist = mallok(cdbfr->len);
1302 if (msglist == NULL)
1303 lprintf(3, "ERROR malloc msglist!\n");
1304 num_msgs = cdbfr->len / sizeof(long);
1305 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1310 /* Make sure the message doesn't already exist in this room. It
1311 * is absolutely taboo to have more than one reference to the same
1312 * message in a room.
1314 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1315 if (msglist[i] == msgid) {
1316 lputroom(&CC->quickroom); /* unlock the room */
1317 getroom(&CC->quickroom, hold_rm);
1318 if (msg != NULL) CtdlFreeMessage(msg);
1319 return(ERROR + ALREADY_EXISTS);
1323 /* Now add the new message */
1325 msglist = reallok(msglist,
1326 (num_msgs * sizeof(long)));
1328 if (msglist == NULL) {
1329 lprintf(3, "ERROR: can't realloc message list!\n");
1331 msglist[num_msgs - 1] = msgid;
1333 /* Sort the message list, so all the msgid's are in order */
1334 num_msgs = sort_msglist(msglist, num_msgs);
1336 /* Determine the highest message number */
1337 highest_msg = msglist[num_msgs - 1];
1339 /* Write it back to disk. */
1340 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1341 msglist, num_msgs * sizeof(long));
1343 /* Free up the memory we used. */
1346 /* Update the highest-message pointer and unlock the room. */
1347 CC->quickroom.QRhighest = highest_msg;
1348 lputroom(&CC->quickroom);
1349 getroom(&CC->quickroom, hold_rm);
1351 /* Bump the reference count for this message. */
1352 if ((flags & SM_DONT_BUMP_REF)==0) {
1353 AdjRefCount(msgid, +1);
1356 /* Return success. */
1357 if (msg != NULL) CtdlFreeMessage(msg);
1364 * Message base operation to send a message to the master file
1365 * (returns new message number)
1367 * This is the back end for CtdlSaveMsg() and should not be directly
1368 * called by server-side modules.
1371 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1372 FILE *save_a_copy) /* save a copy to disk? */
1379 /* Get a new message number */
1380 newmsgid = get_new_message_number();
1381 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1383 /* Generate an ID if we don't have one already */
1384 if (msg->cm_fields['I']==NULL) {
1385 msg->cm_fields['I'] = strdoop(msgidbuf);
1388 serialize_message(&smr, msg);
1391 cprintf("%d Unable to serialize message\n",
1392 ERROR+INTERNAL_ERROR);
1396 /* Write our little bundle of joy into the message base */
1397 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1398 smr.ser, smr.len) < 0) {
1399 lprintf(2, "Can't store message\n");
1405 /* If the caller specified that a copy should be saved to a particular
1406 * file handle, do that now too.
1408 if (save_a_copy != NULL) {
1409 fwrite(smr.ser, smr.len, 1, save_a_copy);
1412 /* Free the memory we used for the serialized message */
1415 /* Return the *local* message ID to the caller
1416 * (even if we're storing an incoming network message)
1424 * Serialize a struct CtdlMessage into the format used on disk and network.
1426 * This function loads up a "struct ser_ret" (defined in server.h) which
1427 * contains the length of the serialized message and a pointer to the
1428 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1430 void serialize_message(struct ser_ret *ret, /* return values */
1431 struct CtdlMessage *msg) /* unserialized msg */
1435 static char *forder = FORDER;
1437 if (is_valid_message(msg) == 0) return; /* self check */
1440 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1441 ret->len = ret->len +
1442 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1444 lprintf(9, "calling malloc(%d)\n", ret->len);
1445 ret->ser = mallok(ret->len);
1446 if (ret->ser == NULL) {
1452 ret->ser[1] = msg->cm_anon_type;
1453 ret->ser[2] = msg->cm_format_type;
1456 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1457 ret->ser[wlen++] = (char)forder[i];
1458 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1459 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1461 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1470 * Back end for the ReplicationChecks() function
1472 void check_repl(long msgnum, void *userdata) {
1473 struct CtdlMessage *msg;
1474 time_t timestamp = (-1L);
1476 lprintf(9, "check_repl() found message %ld\n", msgnum);
1477 msg = CtdlFetchMessage(msgnum);
1478 if (msg == NULL) return;
1479 if (msg->cm_fields['T'] != NULL) {
1480 timestamp = atol(msg->cm_fields['T']);
1482 CtdlFreeMessage(msg);
1484 if (timestamp > msg_repl->highest) {
1485 msg_repl->highest = timestamp; /* newer! */
1486 lprintf(9, "newer!\n");
1489 lprintf(9, "older!\n");
1491 /* Existing isn't newer? Then delete the old one(s). */
1492 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1497 * Check to see if any messages already exist which carry the same Extended ID
1501 * -> With older timestamps: delete them and return 0. Message will be saved.
1502 * -> With newer timestamps: return 1. Message save will be aborted.
1504 int ReplicationChecks(struct CtdlMessage *msg) {
1505 struct CtdlMessage *template;
1508 lprintf(9, "ReplicationChecks() started\n");
1509 /* No extended id? Don't do anything. */
1510 if (msg->cm_fields['E'] == NULL) return 0;
1511 if (strlen(msg->cm_fields['E']) == 0) return 0;
1512 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1514 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1515 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1516 msg_repl->highest = atol(msg->cm_fields['T']);
1518 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1519 memset(template, 0, sizeof(struct CtdlMessage));
1520 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1522 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1525 /* If a newer message exists with the same Extended ID, abort
1528 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1532 CtdlFreeMessage(template);
1533 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1541 * Save a message to disk
1543 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1544 char *rec, /* Recipient (mail) */
1545 char *force, /* force a particular room? */
1546 int supplied_mailtype) /* local or remote type */
1549 char hold_rm[ROOMNAMELEN];
1550 char actual_rm[ROOMNAMELEN];
1551 char force_room[ROOMNAMELEN];
1552 char content_type[SIZ]; /* We have to learn this */
1553 char recipient[SIZ];
1556 struct usersupp userbuf;
1558 struct SuppMsgInfo smi;
1559 FILE *network_fp = NULL;
1560 static int seqnum = 1;
1561 struct CtdlMessage *imsg;
1565 lprintf(9, "CtdlSaveMsg() called\n");
1566 if (is_valid_message(msg) == 0) return(-1); /* self check */
1567 mailtype = supplied_mailtype;
1569 /* If this message has no timestamp, we take the liberty of
1570 * giving it one, right now.
1572 if (msg->cm_fields['T'] == NULL) {
1573 lprintf(9, "Generating timestamp\n");
1574 sprintf(aaa, "%ld", time(NULL));
1575 msg->cm_fields['T'] = strdoop(aaa);
1578 /* If this message has no path, we generate one.
1580 if (msg->cm_fields['P'] == NULL) {
1581 lprintf(9, "Generating path\n");
1582 if (msg->cm_fields['A'] != NULL) {
1583 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1584 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1585 if (isspace(msg->cm_fields['P'][a])) {
1586 msg->cm_fields['P'][a] = ' ';
1591 msg->cm_fields['P'] = strdoop("unknown");
1595 strcpy(force_room, force);
1597 /* Strip non-printable characters out of the recipient name */
1598 lprintf(9, "Checking recipient (if present)\n");
1599 strcpy(recipient, rec);
1600 for (a = 0; a < strlen(recipient); ++a)
1601 if (!isprint(recipient[a]))
1602 strcpy(&recipient[a], &recipient[a + 1]);
1604 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1605 for (a=0; a<strlen(recipient); ++a) {
1606 if (recipient[a] == '@') {
1607 if (CtdlHostAlias(&recipient[a+1])
1608 == hostalias_localhost) {
1610 lprintf(7, "Changed to <%s>\n", recipient);
1611 mailtype = MES_LOCAL;
1616 lprintf(9, "Recipient is <%s>\n", recipient);
1618 /* Learn about what's inside, because it's what's inside that counts */
1619 lprintf(9, "Learning what's inside\n");
1620 if (msg->cm_fields['M'] == NULL) {
1621 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1624 switch (msg->cm_format_type) {
1626 strcpy(content_type, "text/x-citadel-variformat");
1629 strcpy(content_type, "text/plain");
1632 strcpy(content_type, "text/plain");
1633 /* advance past header fields */
1634 mptr = msg->cm_fields['M'];
1637 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1638 safestrncpy(content_type, mptr,
1639 sizeof(content_type));
1640 strcpy(content_type, &content_type[14]);
1641 for (a = 0; a < strlen(content_type); ++a)
1642 if ((content_type[a] == ';')
1643 || (content_type[a] == ' ')
1644 || (content_type[a] == 13)
1645 || (content_type[a] == 10))
1646 content_type[a] = 0;
1653 /* Goto the correct room */
1654 lprintf(9, "Switching rooms\n");
1655 strcpy(hold_rm, CC->quickroom.QRname);
1656 strcpy(actual_rm, CC->quickroom.QRname);
1658 /* If the user is a twit, move to the twit room for posting */
1659 lprintf(9, "Handling twit stuff\n");
1661 if (CC->usersupp.axlevel == 2) {
1662 strcpy(hold_rm, actual_rm);
1663 strcpy(actual_rm, config.c_twitroom);
1667 /* ...or if this message is destined for Aide> then go there. */
1668 if (strlen(force_room) > 0) {
1669 strcpy(actual_rm, force_room);
1672 lprintf(9, "Possibly relocating\n");
1673 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1674 getroom(&CC->quickroom, actual_rm);
1678 * If this message has no O (room) field, generate one.
1680 if (msg->cm_fields['O'] == NULL) {
1681 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1684 /* Perform "before save" hooks (aborting if any return nonzero) */
1685 lprintf(9, "Performing before-save hooks\n");
1686 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1688 /* If this message has an Extended ID, perform replication checks */
1689 lprintf(9, "Performing replication checks\n");
1690 if (ReplicationChecks(msg) > 0) return(-1);
1692 /* Network mail - send a copy to the network program. */
1693 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1694 lprintf(9, "Sending network spool\n");
1695 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1696 (long) getpid(), CC->cs_pid, ++seqnum);
1697 lprintf(9, "Saving a copy to %s\n", aaa);
1698 network_fp = fopen(aaa, "ab+");
1699 if (network_fp == NULL)
1700 lprintf(2, "ERROR: %s\n", strerror(errno));
1703 /* Save it to disk */
1704 lprintf(9, "Saving to disk\n");
1705 newmsgid = send_message(msg, network_fp);
1706 if (network_fp != NULL) {
1708 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1711 if (newmsgid <= 0L) return(-1);
1713 /* Write a supplemental message info record. This doesn't have to
1714 * be a critical section because nobody else knows about this message
1717 lprintf(9, "Creating SuppMsgInfo record\n");
1718 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1719 smi.smi_msgnum = newmsgid;
1720 smi.smi_refcount = 0;
1721 safestrncpy(smi.smi_content_type, content_type, 64);
1722 PutSuppMsgInfo(&smi);
1724 /* Now figure out where to store the pointers */
1725 lprintf(9, "Storing pointers\n");
1727 /* If this is being done by the networker delivering a private
1728 * message, we want to BYPASS saving the sender's copy (because there
1729 * is no local sender; it would otherwise go to the Trashcan).
1731 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1732 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1733 lprintf(3, "ERROR saving message pointer!\n");
1734 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1738 /* For internet mail, drop a copy in the outbound queue room */
1739 if (mailtype == MES_INTERNET) {
1740 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1743 /* Bump this user's messages posted counter. */
1744 lprintf(9, "Updating user\n");
1745 lgetuser(&CC->usersupp, CC->curr_user);
1746 CC->usersupp.posted = CC->usersupp.posted + 1;
1747 lputuser(&CC->usersupp);
1749 /* If this is private, local mail, make a copy in the
1750 * recipient's mailbox and bump the reference count.
1752 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1753 if (getuser(&userbuf, recipient) == 0) {
1754 lprintf(9, "Delivering private mail\n");
1755 MailboxName(actual_rm, &userbuf, MAILROOM);
1756 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1759 lprintf(9, "No user <%s>, saving in %s> instead\n",
1760 recipient, AIDEROOM);
1761 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1765 /* Perform "after save" hooks */
1766 lprintf(9, "Performing after-save hooks\n");
1767 PerformMessageHooks(msg, EVT_AFTERSAVE);
1770 lprintf(9, "Returning to original room\n");
1771 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1772 getroom(&CC->quickroom, hold_rm);
1774 /* For internet mail, generate delivery instructions
1775 * (Yes, this is recursive! Deal with it!)
1777 if (mailtype == MES_INTERNET) {
1778 lprintf(9, "Generating delivery instructions\n");
1779 instr = mallok(2048);
1781 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1784 SPOOLMIME, newmsgid, time(NULL),
1785 msg->cm_fields['A'], msg->cm_fields['N'],
1788 imsg = mallok(sizeof(struct CtdlMessage));
1789 memset(imsg, 0, sizeof(struct CtdlMessage));
1790 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1791 imsg->cm_anon_type = MES_NORMAL;
1792 imsg->cm_format_type = FMT_RFC822;
1793 imsg->cm_fields['A'] = strdoop("Citadel");
1794 imsg->cm_fields['M'] = instr;
1795 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1796 CtdlFreeMessage(imsg);
1805 * Convenience function for generating small administrative messages.
1807 void quickie_message(char *from, char *to, char *room, char *text)
1809 struct CtdlMessage *msg;
1811 msg = mallok(sizeof(struct CtdlMessage));
1812 memset(msg, 0, sizeof(struct CtdlMessage));
1813 msg->cm_magic = CTDLMESSAGE_MAGIC;
1814 msg->cm_anon_type = MES_NORMAL;
1815 msg->cm_format_type = 0;
1816 msg->cm_fields['A'] = strdoop(from);
1817 msg->cm_fields['O'] = strdoop(room);
1818 msg->cm_fields['N'] = strdoop(NODENAME);
1820 msg->cm_fields['R'] = strdoop(to);
1821 msg->cm_fields['M'] = strdoop(text);
1823 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1824 CtdlFreeMessage(msg);
1825 syslog(LOG_NOTICE, text);
1831 * Back end function used by make_message() and similar functions
1833 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1834 size_t maxlen, /* maximum message length */
1835 char *exist /* if non-null, append to it;
1836 exist is ALWAYS freed */
1840 size_t message_len = 0;
1841 size_t buffer_len = 0;
1845 if (exist == NULL) {
1849 m = reallok(exist, strlen(exist) + 4096);
1850 if (m == NULL) phree(exist);
1853 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1860 /* read in the lines of message text one by one */
1861 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1863 /* strip trailing newline type stuff */
1864 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1865 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1867 linelen = strlen(buf);
1869 /* augment the buffer if we have to */
1870 if ((message_len + linelen + 2) > buffer_len) {
1871 lprintf(9, "realloking\n");
1872 ptr = reallok(m, (buffer_len * 2) );
1873 if (ptr == NULL) { /* flush if can't allocate */
1874 while ( (client_gets(buf)>0) &&
1875 strcmp(buf, terminator)) ;;
1878 buffer_len = (buffer_len * 2);
1880 lprintf(9, "buffer_len is %d\n", buffer_len);
1884 /* Add the new line to the buffer. We avoid using strcat()
1885 * because that would involve traversing the entire message
1886 * after each line, and this function needs to run fast.
1888 strcpy(&m[message_len], buf);
1889 m[message_len + linelen] = '\n';
1890 m[message_len + linelen + 1] = 0;
1891 message_len = message_len + linelen + 1;
1893 /* if we've hit the max msg length, flush the rest */
1894 if (message_len >= maxlen) {
1895 while ( (client_gets(buf)>0)
1896 && strcmp(buf, terminator)) ;;
1907 * Build a binary message to be saved on disk.
1910 struct CtdlMessage *make_message(
1911 struct usersupp *author, /* author's usersupp structure */
1912 char *recipient, /* NULL if it's not mail */
1913 char *room, /* room where it's going */
1914 int type, /* see MES_ types in header file */
1915 int net_type, /* see MES_ types in header file */
1916 int format_type, /* local or remote (see citadel.h) */
1917 char *fake_name) /* who we're masquerading as */
1923 struct CtdlMessage *msg;
1925 msg = mallok(sizeof(struct CtdlMessage));
1926 memset(msg, 0, sizeof(struct CtdlMessage));
1927 msg->cm_magic = CTDLMESSAGE_MAGIC;
1928 msg->cm_anon_type = type;
1929 msg->cm_format_type = format_type;
1931 /* Don't confuse the poor folks if it's not routed mail. */
1932 strcpy(dest_node, "");
1934 /* If net_type is MES_BINARY, split out the destination node. */
1935 if (net_type == MES_BINARY) {
1936 strcpy(dest_node, NODENAME);
1937 for (a = 0; a < strlen(recipient); ++a) {
1938 if (recipient[a] == '@') {
1940 strcpy(dest_node, &recipient[a + 1]);
1945 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1946 if (net_type == MES_INTERNET) {
1947 strcpy(dest_node, "internet");
1950 while (isspace(recipient[strlen(recipient) - 1]))
1951 recipient[strlen(recipient) - 1] = 0;
1953 sprintf(buf, "cit%ld", author->usernum); /* Path */
1954 msg->cm_fields['P'] = strdoop(buf);
1956 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1957 msg->cm_fields['T'] = strdoop(buf);
1959 if (fake_name[0]) /* author */
1960 msg->cm_fields['A'] = strdoop(fake_name);
1962 msg->cm_fields['A'] = strdoop(author->fullname);
1964 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1965 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1967 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1969 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1970 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1972 if (recipient[0] != 0)
1973 msg->cm_fields['R'] = strdoop(recipient);
1974 if (dest_node[0] != 0)
1975 msg->cm_fields['D'] = strdoop(dest_node);
1978 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1979 config.c_maxmsglen, NULL);
1990 * message entry - mode 0 (normal)
1992 void cmd_ent0(char *entargs)
1995 char recipient[SIZ];
1997 int format_type = 0;
1998 char newusername[SIZ];
1999 struct CtdlMessage *msg;
2003 struct usersupp tempUS;
2006 post = extract_int(entargs, 0);
2007 extract(recipient, entargs, 1);
2008 anon_flag = extract_int(entargs, 2);
2009 format_type = extract_int(entargs, 3);
2011 /* first check to make sure the request is valid. */
2013 if (!(CC->logged_in)) {
2014 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
2017 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2018 cprintf("%d Need to be validated to enter ",
2019 ERROR + HIGHER_ACCESS_REQUIRED);
2020 cprintf("(except in %s> to sysop)\n", MAILROOM);
2023 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
2024 cprintf("%d Need net privileges to enter here.\n",
2025 ERROR + HIGHER_ACCESS_REQUIRED);
2028 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
2029 cprintf("%d Sorry, this is a read-only room.\n",
2030 ERROR + HIGHER_ACCESS_REQUIRED);
2037 if (CC->usersupp.axlevel < 6) {
2038 cprintf("%d You don't have permission to masquerade.\n",
2039 ERROR + HIGHER_ACCESS_REQUIRED);
2042 extract(newusername, entargs, 4);
2043 memset(CC->fake_postname, 0, 32);
2044 strcpy(CC->fake_postname, newusername);
2045 cprintf("%d Ok\n", OK);
2048 CC->cs_flags |= CS_POSTING;
2051 if (CC->quickroom.QRflags & QR_MAILBOX) {
2052 if (CC->usersupp.axlevel >= 2) {
2053 strcpy(buf, recipient);
2055 strcpy(buf, "sysop");
2056 e = alias(buf); /* alias and mail type */
2057 if ((buf[0] == 0) || (e == MES_ERROR)) {
2058 cprintf("%d Unknown address - cannot send message.\n",
2059 ERROR + NO_SUCH_USER);
2062 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2063 cprintf("%d Net privileges required for network mail.\n",
2064 ERROR + HIGHER_ACCESS_REQUIRED);
2067 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2068 && ((CC->usersupp.flags & US_INTERNET) == 0)
2069 && (!CC->internal_pgm)) {
2070 cprintf("%d You don't have access to Internet mail.\n",
2071 ERROR + HIGHER_ACCESS_REQUIRED);
2074 if (!strcasecmp(buf, "sysop")) {
2077 else if (e == MES_LOCAL) { /* don't search local file */
2078 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2079 cprintf("%d Can't send mail to yourself!\n",
2080 ERROR + NO_SUCH_USER);
2083 /* Check to make sure the user exists; also get the correct
2084 * upper/lower casing of the name.
2086 a = getuser(&tempUS, buf);
2088 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2091 strcpy(buf, tempUS.fullname);
2096 if (CC->quickroom.QRflags & QR_ANONONLY)
2098 if (CC->quickroom.QRflags & QR_ANONOPT) {
2102 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2105 /* If we're only checking the validity of the request, return
2106 * success without creating the message.
2109 cprintf("%d %s\n", OK, buf);
2113 cprintf("%d send message\n", SEND_LISTING);
2115 /* Read in the message from the client. */
2116 if (CC->fake_postname[0])
2117 msg = make_message(&CC->usersupp, buf,
2118 CC->quickroom.QRname, b, e, format_type,
2120 else if (CC->fake_username[0])
2121 msg = make_message(&CC->usersupp, buf,
2122 CC->quickroom.QRname, b, e, format_type,
2125 msg = make_message(&CC->usersupp, buf,
2126 CC->quickroom.QRname, b, e, format_type, "");
2129 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2130 CtdlFreeMessage(msg);
2131 CC->fake_postname[0] = '\0';
2138 * message entry - mode 3 (raw)
2140 void cmd_ent3(char *entargs)
2146 unsigned char ch, which_field;
2147 struct usersupp tempUS;
2149 struct CtdlMessage *msg;
2152 if (CC->internal_pgm == 0) {
2153 cprintf("%d This command is for internal programs only.\n",
2158 /* See if there's a recipient, but make sure it's a real one */
2159 extract(recp, entargs, 1);
2160 for (a = 0; a < strlen(recp); ++a)
2161 if (!isprint(recp[a]))
2162 strcpy(&recp[a], &recp[a + 1]);
2163 while (isspace(recp[0]))
2164 strcpy(recp, &recp[1]);
2165 while (isspace(recp[strlen(recp) - 1]))
2166 recp[strlen(recp) - 1] = 0;
2168 /* If we're in Mail, check the recipient */
2169 if (strlen(recp) > 0) {
2170 e = alias(recp); /* alias and mail type */
2171 if ((recp[0] == 0) || (e == MES_ERROR)) {
2172 cprintf("%d Unknown address - cannot send message.\n",
2173 ERROR + NO_SUCH_USER);
2176 if (e == MES_LOCAL) {
2177 a = getuser(&tempUS, recp);
2179 cprintf("%d No such user.\n",
2180 ERROR + NO_SUCH_USER);
2186 /* At this point, message has been approved. */
2187 if (extract_int(entargs, 0) == 0) {
2188 cprintf("%d OK to send\n", OK);
2192 msglen = extract_long(entargs, 2);
2193 msg = mallok(sizeof(struct CtdlMessage));
2195 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2199 memset(msg, 0, sizeof(struct CtdlMessage));
2200 tempbuf = mallok(msglen);
2201 if (tempbuf == NULL) {
2202 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2207 cprintf("%d %ld\n", SEND_BINARY, msglen);
2209 client_read(&ch, 1); /* 0xFF magic number */
2210 msg->cm_magic = CTDLMESSAGE_MAGIC;
2211 client_read(&ch, 1); /* anon type */
2212 msg->cm_anon_type = ch;
2213 client_read(&ch, 1); /* format type */
2214 msg->cm_format_type = ch;
2215 msglen = msglen - 3;
2217 while (msglen > 0) {
2218 client_read(&which_field, 1);
2219 if (!isalpha(which_field)) valid_msg = 0;
2223 client_read(&ch, 1);
2225 a = strlen(tempbuf);
2228 } while ( (ch != 0) && (msglen > 0) );
2230 msg->cm_fields[which_field] = strdoop(tempbuf);
2233 msg->cm_flags = CM_SKIP_HOOKS;
2234 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2235 CtdlFreeMessage(msg);
2241 * API function to delete messages which match a set of criteria
2242 * (returns the actual number of messages deleted)
2244 int CtdlDeleteMessages(char *room_name, /* which room */
2245 long dmsgnum, /* or "0" for any */
2246 char *content_type /* or "" for any */
2250 struct quickroom qrbuf;
2251 struct cdbdata *cdbfr;
2252 long *msglist = NULL;
2255 int num_deleted = 0;
2257 struct SuppMsgInfo smi;
2259 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2260 room_name, dmsgnum, content_type);
2262 /* get room record, obtaining a lock... */
2263 if (lgetroom(&qrbuf, room_name) != 0) {
2264 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2266 return (0); /* room not found */
2268 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2270 if (cdbfr != NULL) {
2271 msglist = mallok(cdbfr->len);
2272 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2273 num_msgs = cdbfr->len / sizeof(long);
2277 for (i = 0; i < num_msgs; ++i) {
2280 /* Set/clear a bit for each criterion */
2282 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2283 delete_this |= 0x01;
2285 if (strlen(content_type) == 0) {
2286 delete_this |= 0x02;
2288 GetSuppMsgInfo(&smi, msglist[i]);
2289 if (!strcasecmp(smi.smi_content_type,
2291 delete_this |= 0x02;
2295 /* Delete message only if all bits are set */
2296 if (delete_this == 0x03) {
2297 AdjRefCount(msglist[i], -1);
2303 num_msgs = sort_msglist(msglist, num_msgs);
2304 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2305 msglist, (num_msgs * sizeof(long)));
2307 qrbuf.QRhighest = msglist[num_msgs - 1];
2311 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2312 return (num_deleted);
2318 * Delete message from current room
2320 void cmd_dele(char *delstr)
2325 getuser(&CC->usersupp, CC->curr_user);
2326 if ((CC->usersupp.axlevel < 6)
2327 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2328 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2329 && (!(CC->internal_pgm))) {
2330 cprintf("%d Higher access required.\n",
2331 ERROR + HIGHER_ACCESS_REQUIRED);
2334 delnum = extract_long(delstr, 0);
2336 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2339 cprintf("%d %d message%s deleted.\n", OK,
2340 num_deleted, ((num_deleted != 1) ? "s" : ""));
2342 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2348 * Back end API function for moves and deletes
2350 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2353 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2354 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2355 if (err != 0) return(err);
2363 * move or copy a message to another room
2365 void cmd_move(char *args)
2369 struct quickroom qtemp;
2373 num = extract_long(args, 0);
2374 extract(targ, args, 1);
2375 targ[ROOMNAMELEN - 1] = 0;
2376 is_copy = extract_int(args, 2);
2378 getuser(&CC->usersupp, CC->curr_user);
2379 if ((CC->usersupp.axlevel < 6)
2380 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2381 cprintf("%d Higher access required.\n",
2382 ERROR + HIGHER_ACCESS_REQUIRED);
2386 if (getroom(&qtemp, targ) != 0) {
2387 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2391 err = CtdlCopyMsgToRoom(num, targ);
2393 cprintf("%d Cannot store message in %s: error %d\n",
2398 /* Now delete the message from the source room,
2399 * if this is a 'move' rather than a 'copy' operation.
2401 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2403 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2409 * GetSuppMsgInfo() - Get the supplementary record for a message
2411 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2414 struct cdbdata *cdbsmi;
2417 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2418 smibuf->smi_msgnum = msgnum;
2419 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2421 /* Use the negative of the message number for its supp record index */
2422 TheIndex = (0L - msgnum);
2424 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2425 if (cdbsmi == NULL) {
2426 return; /* record not found; go with defaults */
2428 memcpy(smibuf, cdbsmi->ptr,
2429 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2430 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2437 * PutSuppMsgInfo() - (re)write supplementary record for a message
2439 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2443 /* Use the negative of the message number for its supp record index */
2444 TheIndex = (0L - smibuf->smi_msgnum);
2446 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2447 smibuf->smi_msgnum, smibuf->smi_refcount);
2449 cdb_store(CDB_MSGMAIN,
2450 &TheIndex, sizeof(long),
2451 smibuf, sizeof(struct SuppMsgInfo));
2456 * AdjRefCount - change the reference count for a message;
2457 * delete the message if it reaches zero
2459 void AdjRefCount(long msgnum, int incr)
2462 struct SuppMsgInfo smi;
2465 /* This is a *tight* critical section; please keep it that way, as
2466 * it may get called while nested in other critical sections.
2467 * Complicating this any further will surely cause deadlock!
2469 begin_critical_section(S_SUPPMSGMAIN);
2470 GetSuppMsgInfo(&smi, msgnum);
2471 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2472 msgnum, smi.smi_refcount);
2473 smi.smi_refcount += incr;
2474 PutSuppMsgInfo(&smi);
2475 end_critical_section(S_SUPPMSGMAIN);
2476 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2477 msgnum, smi.smi_refcount);
2479 /* If the reference count is now zero, delete the message
2480 * (and its supplementary record as well).
2482 if (smi.smi_refcount == 0) {
2483 lprintf(9, "Deleting message <%ld>\n", msgnum);
2485 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2486 delnum = (0L - msgnum);
2487 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2492 * Write a generic object to this room
2494 * Note: this could be much more efficient. Right now we use two temporary
2495 * files, and still pull the message into memory as with all others.
2497 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2498 char *content_type, /* MIME type of this object */
2499 char *tempfilename, /* Where to fetch it from */
2500 struct usersupp *is_mailbox, /* Mailbox room? */
2501 int is_binary, /* Is encoding necessary? */
2502 int is_unique, /* Del others of this type? */
2503 unsigned int flags /* Internal save flags */
2508 char filename[PATH_MAX];
2511 struct quickroom qrbuf;
2512 char roomname[ROOMNAMELEN];
2513 struct CtdlMessage *msg;
2516 if (is_mailbox != NULL)
2517 MailboxName(roomname, is_mailbox, req_room);
2519 safestrncpy(roomname, req_room, sizeof(roomname));
2520 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2522 strcpy(filename, tmpnam(NULL));
2523 fp = fopen(filename, "w");
2527 tempfp = fopen(tempfilename, "r");
2528 if (tempfp == NULL) {
2534 fprintf(fp, "Content-type: %s\n", content_type);
2535 lprintf(9, "Content-type: %s\n", content_type);
2537 if (is_binary == 0) {
2538 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2539 while (ch = getc(tempfp), ch > 0)
2545 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2548 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2549 tempfilename, filename);
2553 lprintf(9, "Allocating\n");
2554 msg = mallok(sizeof(struct CtdlMessage));
2555 memset(msg, 0, sizeof(struct CtdlMessage));
2556 msg->cm_magic = CTDLMESSAGE_MAGIC;
2557 msg->cm_anon_type = MES_NORMAL;
2558 msg->cm_format_type = 4;
2559 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2560 msg->cm_fields['O'] = strdoop(req_room);
2561 msg->cm_fields['N'] = strdoop(config.c_nodename);
2562 msg->cm_fields['H'] = strdoop(config.c_humannode);
2563 msg->cm_flags = flags;
2565 lprintf(9, "Loading\n");
2566 fp = fopen(filename, "rb");
2567 fseek(fp, 0L, SEEK_END);
2570 msg->cm_fields['M'] = mallok(len);
2571 fread(msg->cm_fields['M'], len, 1, fp);
2575 /* Create the requested room if we have to. */
2576 if (getroom(&qrbuf, roomname) != 0) {
2577 create_room(roomname,
2578 ( (is_mailbox != NULL) ? 5 : 3 ),
2581 /* If the caller specified this object as unique, delete all
2582 * other objects of this type that are currently in the room.
2585 lprintf(9, "Deleted %d other msgs of this type\n",
2586 CtdlDeleteMessages(roomname, 0L, content_type));
2588 /* Now write the data */
2589 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2590 CtdlFreeMessage(msg);
2598 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2599 config_msgnum = msgnum;
2603 char *CtdlGetSysConfig(char *sysconfname) {
2604 char hold_rm[ROOMNAMELEN];
2607 struct CtdlMessage *msg;
2610 strcpy(hold_rm, CC->quickroom.QRname);
2611 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2612 getroom(&CC->quickroom, hold_rm);
2617 /* We want the last (and probably only) config in this room */
2618 begin_critical_section(S_CONFIG);
2619 config_msgnum = (-1L);
2620 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2621 CtdlGetSysConfigBackend, NULL);
2622 msgnum = config_msgnum;
2623 end_critical_section(S_CONFIG);
2629 msg = CtdlFetchMessage(msgnum);
2631 conf = strdoop(msg->cm_fields['M']);
2632 CtdlFreeMessage(msg);
2639 getroom(&CC->quickroom, hold_rm);
2641 lprintf(9, "eggstracting...\n");
2642 if (conf != NULL) do {
2643 extract_token(buf, conf, 0, '\n');
2644 lprintf(9, "eggstracted <%s>\n", buf);
2645 strcpy(conf, &conf[strlen(buf)+1]);
2646 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2651 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2652 char temp[PATH_MAX];
2655 strcpy(temp, tmpnam(NULL));
2657 fp = fopen(temp, "w");
2658 if (fp == NULL) return;
2659 fprintf(fp, "%s", sysconfdata);
2662 /* this handy API function does all the work for us */
2663 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);