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[256];
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, *mime_download, NULL, 0);
885 /* If there's no file open by this time, the requested
886 * section wasn't found, so print an error
888 if (CC->download_fp == NULL) {
889 if (do_proto) cprintf(
890 "%d Section %s not found.\n",
891 ERROR + FILE_NOT_FOUND,
895 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
898 /* now for the user-mode message reading loops */
899 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
901 /* Tell the client which format type we're using. If this is a
902 * MIME message, *lie* about it and tell the user it's fixed-format.
904 if (mode == MT_CITADEL) {
905 if (TheMessage->cm_format_type == FMT_RFC822) {
906 if (do_proto) cprintf("type=1\n");
909 if (do_proto) cprintf("type=%d\n",
910 TheMessage->cm_format_type);
914 /* nhdr=yes means that we're only displaying headers, no body */
915 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
916 if (do_proto) cprintf("nhdr=yes\n");
919 /* begin header processing loop for Citadel message format */
921 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
923 strcpy(display_name, "<unknown>");
924 if (TheMessage->cm_fields['A']) {
925 strcpy(buf, TheMessage->cm_fields['A']);
926 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
927 if (TheMessage->cm_anon_type == MES_ANON)
928 strcpy(display_name, "****");
929 else if (TheMessage->cm_anon_type == MES_AN2)
930 strcpy(display_name, "anonymous");
932 strcpy(display_name, buf);
934 && ((TheMessage->cm_anon_type == MES_ANON)
935 || (TheMessage->cm_anon_type == MES_AN2))) {
936 sprintf(&display_name[strlen(display_name)],
941 strcpy(allkeys, FORDER);
942 for (i=0; i<strlen(allkeys); ++i) {
943 k = (int) allkeys[i];
945 if (TheMessage->cm_fields[k] != NULL) {
947 if (do_proto) cprintf("%s=%s\n",
952 if (do_proto) cprintf("%s=%s\n",
954 TheMessage->cm_fields[k]
963 /* begin header processing loop for RFC822 transfer format */
968 strcpy(snode, NODENAME);
969 strcpy(lnode, HUMANNODE);
970 if (mode == MT_RFC822) {
971 cprintf("X-UIDL: %ld%s", msg_num, nl);
972 for (i = 0; i < 256; ++i) {
973 if (TheMessage->cm_fields[i]) {
974 mptr = TheMessage->cm_fields[i];
981 cprintf("Path: %s%s", mptr, nl);
984 cprintf("Subject: %s%s", mptr, nl);
990 cprintf("X-Citadel-Room: %s%s",
995 cprintf("To: %s%s", mptr, nl);
997 datestring(datestamp, atol(mptr),
999 cprintf("Date: %s%s", datestamp, nl);
1005 for (i=0; i<strlen(suser); ++i) {
1006 suser[i] = tolower(suser[i]);
1007 if (!isalnum(suser[i])) suser[i]='_';
1010 if (mode == MT_RFC822) {
1011 if (!strcasecmp(snode, NODENAME)) {
1012 strcpy(snode, FQDN);
1015 /* Construct a fun message id */
1016 cprintf("Message-ID: <%s", mid);
1017 if (strchr(mid, '@')==NULL) {
1018 cprintf("@%s", snode);
1022 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1024 if (strlen(fuser) > 0) {
1025 cprintf("From: %s (%s)%s", fuser, luser, nl);
1028 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1031 cprintf("Organization: %s%s", lnode, nl);
1034 /* end header processing loop ... at this point, we're in the text */
1036 mptr = TheMessage->cm_fields['M'];
1038 /* Tell the client about the MIME parts in this message */
1039 if (TheMessage->cm_format_type == FMT_RFC822) {
1040 if (mode == MT_CITADEL) {
1041 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1043 else if (mode == MT_MIME) { /* list parts only */
1044 mime_parser(mptr, NULL, *list_this_part, NULL, 0);
1045 if (do_proto) cprintf("000\n");
1048 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1049 /* FIXME ... we have to put some code in here to avoid
1050 * printing duplicate header information when both
1051 * Citadel and RFC822 headers exist. Preference should
1052 * probably be given to the RFC822 headers.
1054 while (ch=*(mptr++), ch!=0) {
1056 else if (ch==10) cprintf("%s", nl);
1057 else cprintf("%c", ch);
1059 if (do_proto) cprintf("000\n");
1065 if (do_proto) cprintf("000\n");
1069 /* signify start of msg text */
1070 if (mode == MT_CITADEL)
1071 if (do_proto) cprintf("text\n");
1072 if (mode == MT_RFC822) {
1073 if (TheMessage->cm_fields['U'] == NULL) {
1074 cprintf("Subject: (no subject)%s", nl);
1079 /* If the format type on disk is 1 (fixed-format), then we want
1080 * everything to be output completely literally ... regardless of
1081 * what message transfer format is in use.
1083 if (TheMessage->cm_format_type == FMT_FIXED) {
1085 while (ch = *mptr++, ch > 0) {
1088 if ((ch == 10) || (strlen(buf) > 250)) {
1089 cprintf("%s%s", buf, nl);
1092 buf[strlen(buf) + 1] = 0;
1093 buf[strlen(buf)] = ch;
1096 if (strlen(buf) > 0)
1097 cprintf("%s%s", buf, nl);
1100 /* If the message on disk is format 0 (Citadel vari-format), we
1101 * output using the formatter at 80 columns. This is the final output
1102 * form if the transfer format is RFC822, but if the transfer format
1103 * is Citadel proprietary, it'll still work, because the indentation
1104 * for new paragraphs is correct and the client will reformat the
1105 * message to the reader's screen width.
1107 if (TheMessage->cm_format_type == FMT_CITADEL) {
1108 memfmout(80, mptr, 0, nl);
1111 /* If the message on disk is format 4 (MIME), we've gotta hand it
1112 * off to the MIME parser. The client has already been told that
1113 * this message is format 1 (fixed format), so the callback function
1114 * we use will display those parts as-is.
1116 if (TheMessage->cm_format_type == FMT_RFC822) {
1117 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1118 memset(ma, 0, sizeof(struct ma_info));
1119 mime_parser(mptr, NULL, *fixed_output, NULL, 0);
1122 /* now we're done */
1123 if (do_proto) cprintf("000\n");
1130 * display a message (mode 0 - Citadel proprietary)
1132 void cmd_msg0(char *cmdbuf)
1135 int headers_only = 0;
1137 msgid = extract_long(cmdbuf, 0);
1138 headers_only = extract_int(cmdbuf, 1);
1140 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1146 * display a message (mode 2 - RFC822)
1148 void cmd_msg2(char *cmdbuf)
1151 int headers_only = 0;
1153 msgid = extract_long(cmdbuf, 0);
1154 headers_only = extract_int(cmdbuf, 1);
1156 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1162 * display a message (mode 3 - IGnet raw format - internal programs only)
1164 void cmd_msg3(char *cmdbuf)
1167 struct CtdlMessage *msg;
1170 if (CC->internal_pgm == 0) {
1171 cprintf("%d This command is for internal programs only.\n",
1176 msgnum = extract_long(cmdbuf, 0);
1177 msg = CtdlFetchMessage(msgnum);
1179 cprintf("%d Message %ld not found.\n",
1184 serialize_message(&smr, msg);
1185 CtdlFreeMessage(msg);
1188 cprintf("%d Unable to serialize message\n",
1189 ERROR+INTERNAL_ERROR);
1193 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1194 client_write(smr.ser, smr.len);
1201 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1203 void cmd_msg4(char *cmdbuf)
1207 msgid = extract_long(cmdbuf, 0);
1208 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1212 * Open a component of a MIME message as a download file
1214 void cmd_opna(char *cmdbuf)
1218 CtdlAllocUserData(SYM_DESIRED_SECTION, 256);
1220 msgid = extract_long(cmdbuf, 0);
1221 extract(desired_section, cmdbuf, 1);
1223 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1228 * Save a message pointer into a specified room
1229 * (Returns 0 for success, nonzero for failure)
1230 * roomname may be NULL to use the current room
1232 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1234 char hold_rm[ROOMNAMELEN];
1235 struct cdbdata *cdbfr;
1238 long highest_msg = 0L;
1239 struct CtdlMessage *msg = NULL;
1241 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1242 roomname, msgid, flags);
1244 strcpy(hold_rm, CC->quickroom.QRname);
1246 /* We may need to check to see if this message is real */
1247 if ( (flags & SM_VERIFY_GOODNESS)
1248 || (flags & SM_DO_REPL_CHECK)
1250 msg = CtdlFetchMessage(msgid);
1251 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1254 /* Perform replication checks if necessary */
1255 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1257 if (getroom(&CC->quickroom,
1258 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1260 lprintf(9, "No such room <%s>\n", roomname);
1261 if (msg != NULL) CtdlFreeMessage(msg);
1262 return(ERROR + ROOM_NOT_FOUND);
1265 if (ReplicationChecks(msg) != 0) {
1266 getroom(&CC->quickroom, hold_rm);
1267 if (msg != NULL) CtdlFreeMessage(msg);
1268 lprintf(9, "Did replication, and newer exists\n");
1273 /* Now the regular stuff */
1274 if (lgetroom(&CC->quickroom,
1275 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1277 lprintf(9, "No such room <%s>\n", roomname);
1278 if (msg != NULL) CtdlFreeMessage(msg);
1279 return(ERROR + ROOM_NOT_FOUND);
1282 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1283 if (cdbfr == NULL) {
1287 msglist = mallok(cdbfr->len);
1288 if (msglist == NULL)
1289 lprintf(3, "ERROR malloc msglist!\n");
1290 num_msgs = cdbfr->len / sizeof(long);
1291 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1296 /* Make sure the message doesn't already exist in this room. It
1297 * is absolutely taboo to have more than one reference to the same
1298 * message in a room.
1300 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1301 if (msglist[i] == msgid) {
1302 lputroom(&CC->quickroom); /* unlock the room */
1303 getroom(&CC->quickroom, hold_rm);
1304 if (msg != NULL) CtdlFreeMessage(msg);
1305 return(ERROR + ALREADY_EXISTS);
1309 /* Now add the new message */
1311 msglist = reallok(msglist,
1312 (num_msgs * sizeof(long)));
1314 if (msglist == NULL) {
1315 lprintf(3, "ERROR: can't realloc message list!\n");
1317 msglist[num_msgs - 1] = msgid;
1319 /* Sort the message list, so all the msgid's are in order */
1320 num_msgs = sort_msglist(msglist, num_msgs);
1322 /* Determine the highest message number */
1323 highest_msg = msglist[num_msgs - 1];
1325 /* Write it back to disk. */
1326 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1327 msglist, num_msgs * sizeof(long));
1329 /* Free up the memory we used. */
1332 /* Update the highest-message pointer and unlock the room. */
1333 CC->quickroom.QRhighest = highest_msg;
1334 lputroom(&CC->quickroom);
1335 getroom(&CC->quickroom, hold_rm);
1337 /* Bump the reference count for this message. */
1338 if ((flags & SM_DONT_BUMP_REF)==0) {
1339 AdjRefCount(msgid, +1);
1342 /* Return success. */
1343 if (msg != NULL) CtdlFreeMessage(msg);
1350 * Message base operation to send a message to the master file
1351 * (returns new message number)
1353 * This is the back end for CtdlSaveMsg() and should not be directly
1354 * called by server-side modules.
1357 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1358 FILE *save_a_copy) /* save a copy to disk? */
1365 /* Get a new message number */
1366 newmsgid = get_new_message_number();
1367 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1369 /* Generate an ID if we don't have one already */
1370 if (msg->cm_fields['I']==NULL) {
1371 msg->cm_fields['I'] = strdoop(msgidbuf);
1374 serialize_message(&smr, msg);
1377 cprintf("%d Unable to serialize message\n",
1378 ERROR+INTERNAL_ERROR);
1382 /* Write our little bundle of joy into the message base */
1383 begin_critical_section(S_MSGMAIN);
1384 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1385 smr.ser, smr.len) < 0) {
1386 lprintf(2, "Can't store message\n");
1391 end_critical_section(S_MSGMAIN);
1393 /* If the caller specified that a copy should be saved to a particular
1394 * file handle, do that now too.
1396 if (save_a_copy != NULL) {
1397 fwrite(smr.ser, smr.len, 1, save_a_copy);
1400 /* Free the memory we used for the serialized message */
1403 /* Return the *local* message ID to the caller
1404 * (even if we're storing an incoming network message)
1412 * Serialize a struct CtdlMessage into the format used on disk and network.
1414 * This function loads up a "struct ser_ret" (defined in server.h) which
1415 * contains the length of the serialized message and a pointer to the
1416 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1418 void serialize_message(struct ser_ret *ret, /* return values */
1419 struct CtdlMessage *msg) /* unserialized msg */
1423 static char *forder = FORDER;
1425 if (is_valid_message(msg) == 0) return; /* self check */
1428 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1429 ret->len = ret->len +
1430 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1432 lprintf(9, "calling malloc(%d)\n", ret->len);
1433 ret->ser = mallok(ret->len);
1434 if (ret->ser == NULL) {
1440 ret->ser[1] = msg->cm_anon_type;
1441 ret->ser[2] = msg->cm_format_type;
1444 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1445 ret->ser[wlen++] = (char)forder[i];
1446 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1447 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1449 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1458 * Back end for the ReplicationChecks() function
1460 void check_repl(long msgnum, void *userdata) {
1461 struct CtdlMessage *msg;
1462 time_t timestamp = (-1L);
1464 lprintf(9, "check_repl() found message %ld\n", msgnum);
1465 msg = CtdlFetchMessage(msgnum);
1466 if (msg == NULL) return;
1467 if (msg->cm_fields['T'] != NULL) {
1468 timestamp = atol(msg->cm_fields['T']);
1470 CtdlFreeMessage(msg);
1472 if (timestamp > msg_repl->highest) {
1473 msg_repl->highest = timestamp; /* newer! */
1474 lprintf(9, "newer!\n");
1477 lprintf(9, "older!\n");
1479 /* Existing isn't newer? Then delete the old one(s). */
1480 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1485 * Check to see if any messages already exist which carry the same Extended ID
1489 * -> With older timestamps: delete them and return 0. Message will be saved.
1490 * -> With newer timestamps: return 1. Message save will be aborted.
1492 int ReplicationChecks(struct CtdlMessage *msg) {
1493 struct CtdlMessage *template;
1496 lprintf(9, "ReplicationChecks() started\n");
1497 /* No extended id? Don't do anything. */
1498 if (msg->cm_fields['E'] == NULL) return 0;
1499 if (strlen(msg->cm_fields['E']) == 0) return 0;
1500 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1502 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1503 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1504 msg_repl->highest = atol(msg->cm_fields['T']);
1506 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1507 memset(template, 0, sizeof(struct CtdlMessage));
1508 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1510 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1513 /* If a newer message exists with the same Extended ID, abort
1516 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1520 CtdlFreeMessage(template);
1521 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1529 * Save a message to disk
1531 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1532 char *rec, /* Recipient (mail) */
1533 char *force, /* force a particular room? */
1534 int supplied_mailtype) /* local or remote type */
1537 char hold_rm[ROOMNAMELEN];
1538 char actual_rm[ROOMNAMELEN];
1539 char force_room[ROOMNAMELEN];
1540 char content_type[256]; /* We have to learn this */
1541 char recipient[256];
1544 struct usersupp userbuf;
1546 struct SuppMsgInfo smi;
1547 FILE *network_fp = NULL;
1548 static int seqnum = 1;
1549 struct CtdlMessage *imsg;
1553 lprintf(9, "CtdlSaveMsg() called\n");
1554 if (is_valid_message(msg) == 0) return(-1); /* self check */
1555 mailtype = supplied_mailtype;
1557 /* If this message has no timestamp, we take the liberty of
1558 * giving it one, right now.
1560 if (msg->cm_fields['T'] == NULL) {
1561 lprintf(9, "Generating timestamp\n");
1562 sprintf(aaa, "%ld", time(NULL));
1563 msg->cm_fields['T'] = strdoop(aaa);
1566 /* If this message has no path, we generate one.
1568 if (msg->cm_fields['P'] == NULL) {
1569 lprintf(9, "Generating path\n");
1570 if (msg->cm_fields['A'] != NULL) {
1571 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1572 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1573 if (isspace(msg->cm_fields['P'][a])) {
1574 msg->cm_fields['P'][a] = ' ';
1579 msg->cm_fields['P'] = strdoop("unknown");
1583 strcpy(force_room, force);
1585 /* Strip non-printable characters out of the recipient name */
1586 lprintf(9, "Checking recipient (if present)\n");
1587 strcpy(recipient, rec);
1588 for (a = 0; a < strlen(recipient); ++a)
1589 if (!isprint(recipient[a]))
1590 strcpy(&recipient[a], &recipient[a + 1]);
1592 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1593 for (a=0; a<strlen(recipient); ++a) {
1594 if (recipient[a] == '@') {
1595 if (CtdlHostAlias(&recipient[a+1])
1596 == hostalias_localhost) {
1598 lprintf(7, "Changed to <%s>\n", recipient);
1599 mailtype = MES_LOCAL;
1604 lprintf(9, "Recipient is <%s>\n", recipient);
1606 /* Learn about what's inside, because it's what's inside that counts */
1607 lprintf(9, "Learning what's inside\n");
1608 if (msg->cm_fields['M'] == NULL) {
1609 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1612 switch (msg->cm_format_type) {
1614 strcpy(content_type, "text/x-citadel-variformat");
1617 strcpy(content_type, "text/plain");
1620 strcpy(content_type, "text/plain");
1621 /* advance past header fields */
1622 mptr = msg->cm_fields['M'];
1625 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1626 safestrncpy(content_type, mptr,
1627 sizeof(content_type));
1628 strcpy(content_type, &content_type[14]);
1629 for (a = 0; a < strlen(content_type); ++a)
1630 if ((content_type[a] == ';')
1631 || (content_type[a] == ' ')
1632 || (content_type[a] == 13)
1633 || (content_type[a] == 10))
1634 content_type[a] = 0;
1641 /* Goto the correct room */
1642 lprintf(9, "Switching rooms\n");
1643 strcpy(hold_rm, CC->quickroom.QRname);
1644 strcpy(actual_rm, CC->quickroom.QRname);
1646 /* If the user is a twit, move to the twit room for posting */
1647 lprintf(9, "Handling twit stuff\n");
1649 if (CC->usersupp.axlevel == 2) {
1650 strcpy(hold_rm, actual_rm);
1651 strcpy(actual_rm, config.c_twitroom);
1655 /* ...or if this message is destined for Aide> then go there. */
1656 if (strlen(force_room) > 0) {
1657 strcpy(actual_rm, force_room);
1660 lprintf(9, "Possibly relocating\n");
1661 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1662 getroom(&CC->quickroom, actual_rm);
1666 * If this message has no O (room) field, generate one.
1668 if (msg->cm_fields['O'] == NULL) {
1669 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1672 /* Perform "before save" hooks (aborting if any return nonzero) */
1673 lprintf(9, "Performing before-save hooks\n");
1674 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1676 /* If this message has an Extended ID, perform replication checks */
1677 lprintf(9, "Performing replication checks\n");
1678 if (ReplicationChecks(msg) > 0) return(-1);
1680 /* Network mail - send a copy to the network program. */
1681 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1682 lprintf(9, "Sending network spool\n");
1683 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1684 (long) getpid(), CC->cs_pid, ++seqnum);
1685 lprintf(9, "Saving a copy to %s\n", aaa);
1686 network_fp = fopen(aaa, "ab+");
1687 if (network_fp == NULL)
1688 lprintf(2, "ERROR: %s\n", strerror(errno));
1691 /* Save it to disk */
1692 lprintf(9, "Saving to disk\n");
1693 newmsgid = send_message(msg, network_fp);
1694 if (network_fp != NULL) {
1696 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1699 if (newmsgid <= 0L) return(-1);
1701 /* Write a supplemental message info record. This doesn't have to
1702 * be a critical section because nobody else knows about this message
1705 lprintf(9, "Creating SuppMsgInfo record\n");
1706 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1707 smi.smi_msgnum = newmsgid;
1708 smi.smi_refcount = 0;
1709 safestrncpy(smi.smi_content_type, content_type, 64);
1710 PutSuppMsgInfo(&smi);
1712 /* Now figure out where to store the pointers */
1713 lprintf(9, "Storing pointers\n");
1715 /* If this is being done by the networker delivering a private
1716 * message, we want to BYPASS saving the sender's copy (because there
1717 * is no local sender; it would otherwise go to the Trashcan).
1719 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1720 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1721 lprintf(3, "ERROR saving message pointer!\n");
1722 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1726 /* For internet mail, drop a copy in the outbound queue room */
1727 if (mailtype == MES_INTERNET) {
1728 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1731 /* Bump this user's messages posted counter. */
1732 lprintf(9, "Updating user\n");
1733 lgetuser(&CC->usersupp, CC->curr_user);
1734 CC->usersupp.posted = CC->usersupp.posted + 1;
1735 lputuser(&CC->usersupp);
1737 /* If this is private, local mail, make a copy in the
1738 * recipient's mailbox and bump the reference count.
1740 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1741 if (getuser(&userbuf, recipient) == 0) {
1742 lprintf(9, "Delivering private mail\n");
1743 MailboxName(actual_rm, &userbuf, MAILROOM);
1744 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1747 lprintf(9, "No user <%s>, saving in %s> instead\n",
1748 recipient, AIDEROOM);
1749 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1753 /* Perform "after save" hooks */
1754 lprintf(9, "Performing after-save hooks\n");
1755 PerformMessageHooks(msg, EVT_AFTERSAVE);
1758 lprintf(9, "Returning to original room\n");
1759 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1760 getroom(&CC->quickroom, hold_rm);
1762 /* For internet mail, generate delivery instructions
1763 * (Yes, this is recursive! Deal with it!)
1765 if (mailtype == MES_INTERNET) {
1766 lprintf(9, "Generating delivery instructions\n");
1767 instr = mallok(2048);
1769 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1772 SPOOLMIME, newmsgid, time(NULL),
1773 msg->cm_fields['A'], msg->cm_fields['N'],
1776 imsg = mallok(sizeof(struct CtdlMessage));
1777 memset(imsg, 0, sizeof(struct CtdlMessage));
1778 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1779 imsg->cm_anon_type = MES_NORMAL;
1780 imsg->cm_format_type = FMT_RFC822;
1781 imsg->cm_fields['A'] = strdoop("Citadel");
1782 imsg->cm_fields['M'] = instr;
1783 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1784 CtdlFreeMessage(imsg);
1793 * Convenience function for generating small administrative messages.
1795 void quickie_message(char *from, char *to, char *room, char *text)
1797 struct CtdlMessage *msg;
1799 msg = mallok(sizeof(struct CtdlMessage));
1800 memset(msg, 0, sizeof(struct CtdlMessage));
1801 msg->cm_magic = CTDLMESSAGE_MAGIC;
1802 msg->cm_anon_type = MES_NORMAL;
1803 msg->cm_format_type = 0;
1804 msg->cm_fields['A'] = strdoop(from);
1805 msg->cm_fields['O'] = strdoop(room);
1806 msg->cm_fields['N'] = strdoop(NODENAME);
1808 msg->cm_fields['R'] = strdoop(to);
1809 msg->cm_fields['M'] = strdoop(text);
1811 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1812 CtdlFreeMessage(msg);
1813 syslog(LOG_NOTICE, text);
1819 * Back end function used by make_message() and similar functions
1821 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1822 size_t maxlen, /* maximum message length */
1823 char *exist /* if non-null, append to it;
1824 exist is ALWAYS freed */
1828 size_t message_len = 0;
1829 size_t buffer_len = 0;
1833 if (exist == NULL) {
1837 m = reallok(exist, strlen(exist) + 4096);
1838 if (m == NULL) phree(exist);
1841 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1848 /* read in the lines of message text one by one */
1849 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1851 /* strip trailing newline type stuff */
1852 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1853 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1855 linelen = strlen(buf);
1857 /* augment the buffer if we have to */
1858 if ((message_len + linelen + 2) > buffer_len) {
1859 lprintf(9, "realloking\n");
1860 ptr = reallok(m, (buffer_len * 2) );
1861 if (ptr == NULL) { /* flush if can't allocate */
1862 while ( (client_gets(buf)>0) &&
1863 strcmp(buf, terminator)) ;;
1866 buffer_len = (buffer_len * 2);
1868 lprintf(9, "buffer_len is %d\n", buffer_len);
1872 /* Add the new line to the buffer. We avoid using strcat()
1873 * because that would involve traversing the entire message
1874 * after each line, and this function needs to run fast.
1876 strcpy(&m[message_len], buf);
1877 m[message_len + linelen] = '\n';
1878 m[message_len + linelen + 1] = 0;
1879 message_len = message_len + linelen + 1;
1881 /* if we've hit the max msg length, flush the rest */
1882 if (message_len >= maxlen) {
1883 while ( (client_gets(buf)>0)
1884 && strcmp(buf, terminator)) ;;
1895 * Build a binary message to be saved on disk.
1898 struct CtdlMessage *make_message(
1899 struct usersupp *author, /* author's usersupp structure */
1900 char *recipient, /* NULL if it's not mail */
1901 char *room, /* room where it's going */
1902 int type, /* see MES_ types in header file */
1903 int net_type, /* see MES_ types in header file */
1904 int format_type, /* local or remote (see citadel.h) */
1905 char *fake_name) /* who we're masquerading as */
1911 struct CtdlMessage *msg;
1913 msg = mallok(sizeof(struct CtdlMessage));
1914 memset(msg, 0, sizeof(struct CtdlMessage));
1915 msg->cm_magic = CTDLMESSAGE_MAGIC;
1916 msg->cm_anon_type = type;
1917 msg->cm_format_type = format_type;
1919 /* Don't confuse the poor folks if it's not routed mail. */
1920 strcpy(dest_node, "");
1922 /* If net_type is MES_BINARY, split out the destination node. */
1923 if (net_type == MES_BINARY) {
1924 strcpy(dest_node, NODENAME);
1925 for (a = 0; a < strlen(recipient); ++a) {
1926 if (recipient[a] == '@') {
1928 strcpy(dest_node, &recipient[a + 1]);
1933 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1934 if (net_type == MES_INTERNET) {
1935 strcpy(dest_node, "internet");
1938 while (isspace(recipient[strlen(recipient) - 1]))
1939 recipient[strlen(recipient) - 1] = 0;
1941 sprintf(buf, "cit%ld", author->usernum); /* Path */
1942 msg->cm_fields['P'] = strdoop(buf);
1944 sprintf(buf, "%ld", time(NULL)); /* timestamp */
1945 msg->cm_fields['T'] = strdoop(buf);
1947 if (fake_name[0]) /* author */
1948 msg->cm_fields['A'] = strdoop(fake_name);
1950 msg->cm_fields['A'] = strdoop(author->fullname);
1952 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
1953 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
1955 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1957 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
1958 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
1960 if (recipient[0] != 0)
1961 msg->cm_fields['R'] = strdoop(recipient);
1962 if (dest_node[0] != 0)
1963 msg->cm_fields['D'] = strdoop(dest_node);
1966 msg->cm_fields['M'] = CtdlReadMessageBody("000",
1967 config.c_maxmsglen, NULL);
1978 * message entry - mode 0 (normal)
1980 void cmd_ent0(char *entargs)
1983 char recipient[256];
1985 int format_type = 0;
1986 char newusername[256];
1987 struct CtdlMessage *msg;
1991 struct usersupp tempUS;
1994 post = extract_int(entargs, 0);
1995 extract(recipient, entargs, 1);
1996 anon_flag = extract_int(entargs, 2);
1997 format_type = extract_int(entargs, 3);
1999 /* first check to make sure the request is valid. */
2001 if (!(CC->logged_in)) {
2002 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
2005 if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2006 cprintf("%d Need to be validated to enter ",
2007 ERROR + HIGHER_ACCESS_REQUIRED);
2008 cprintf("(except in %s> to sysop)\n", MAILROOM);
2011 if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) {
2012 cprintf("%d Need net privileges to enter here.\n",
2013 ERROR + HIGHER_ACCESS_REQUIRED);
2016 if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) {
2017 cprintf("%d Sorry, this is a read-only room.\n",
2018 ERROR + HIGHER_ACCESS_REQUIRED);
2025 if (CC->usersupp.axlevel < 6) {
2026 cprintf("%d You don't have permission to masquerade.\n",
2027 ERROR + HIGHER_ACCESS_REQUIRED);
2030 extract(newusername, entargs, 4);
2031 memset(CC->fake_postname, 0, 32);
2032 strcpy(CC->fake_postname, newusername);
2033 cprintf("%d Ok\n", OK);
2036 CC->cs_flags |= CS_POSTING;
2039 if (CC->quickroom.QRflags & QR_MAILBOX) {
2040 if (CC->usersupp.axlevel >= 2) {
2041 strcpy(buf, recipient);
2043 strcpy(buf, "sysop");
2044 e = alias(buf); /* alias and mail type */
2045 if ((buf[0] == 0) || (e == MES_ERROR)) {
2046 cprintf("%d Unknown address - cannot send message.\n",
2047 ERROR + NO_SUCH_USER);
2050 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2051 cprintf("%d Net privileges required for network mail.\n",
2052 ERROR + HIGHER_ACCESS_REQUIRED);
2055 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2056 && ((CC->usersupp.flags & US_INTERNET) == 0)
2057 && (!CC->internal_pgm)) {
2058 cprintf("%d You don't have access to Internet mail.\n",
2059 ERROR + HIGHER_ACCESS_REQUIRED);
2062 if (!strcasecmp(buf, "sysop")) {
2065 else if (e == MES_LOCAL) { /* don't search local file */
2066 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2067 cprintf("%d Can't send mail to yourself!\n",
2068 ERROR + NO_SUCH_USER);
2071 /* Check to make sure the user exists; also get the correct
2072 * upper/lower casing of the name.
2074 a = getuser(&tempUS, buf);
2076 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2079 strcpy(buf, tempUS.fullname);
2084 if (CC->quickroom.QRflags & QR_ANONONLY)
2086 if (CC->quickroom.QRflags & QR_ANONOPT) {
2090 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2093 /* If we're only checking the validity of the request, return
2094 * success without creating the message.
2097 cprintf("%d %s\n", OK, buf);
2101 cprintf("%d send message\n", SEND_LISTING);
2103 /* Read in the message from the client. */
2104 if (CC->fake_postname[0])
2105 msg = make_message(&CC->usersupp, buf,
2106 CC->quickroom.QRname, b, e, format_type,
2108 else if (CC->fake_username[0])
2109 msg = make_message(&CC->usersupp, buf,
2110 CC->quickroom.QRname, b, e, format_type,
2113 msg = make_message(&CC->usersupp, buf,
2114 CC->quickroom.QRname, b, e, format_type, "");
2117 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2118 CtdlFreeMessage(msg);
2119 CC->fake_postname[0] = '\0';
2126 * message entry - mode 3 (raw)
2128 void cmd_ent3(char *entargs)
2134 unsigned char ch, which_field;
2135 struct usersupp tempUS;
2137 struct CtdlMessage *msg;
2140 if (CC->internal_pgm == 0) {
2141 cprintf("%d This command is for internal programs only.\n",
2146 /* See if there's a recipient, but make sure it's a real one */
2147 extract(recp, entargs, 1);
2148 for (a = 0; a < strlen(recp); ++a)
2149 if (!isprint(recp[a]))
2150 strcpy(&recp[a], &recp[a + 1]);
2151 while (isspace(recp[0]))
2152 strcpy(recp, &recp[1]);
2153 while (isspace(recp[strlen(recp) - 1]))
2154 recp[strlen(recp) - 1] = 0;
2156 /* If we're in Mail, check the recipient */
2157 if (strlen(recp) > 0) {
2158 e = alias(recp); /* alias and mail type */
2159 if ((recp[0] == 0) || (e == MES_ERROR)) {
2160 cprintf("%d Unknown address - cannot send message.\n",
2161 ERROR + NO_SUCH_USER);
2164 if (e == MES_LOCAL) {
2165 a = getuser(&tempUS, recp);
2167 cprintf("%d No such user.\n",
2168 ERROR + NO_SUCH_USER);
2174 /* At this point, message has been approved. */
2175 if (extract_int(entargs, 0) == 0) {
2176 cprintf("%d OK to send\n", OK);
2180 msglen = extract_long(entargs, 2);
2181 msg = mallok(sizeof(struct CtdlMessage));
2183 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2187 memset(msg, 0, sizeof(struct CtdlMessage));
2188 tempbuf = mallok(msglen);
2189 if (tempbuf == NULL) {
2190 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2195 cprintf("%d %ld\n", SEND_BINARY, msglen);
2197 client_read(&ch, 1); /* 0xFF magic number */
2198 msg->cm_magic = CTDLMESSAGE_MAGIC;
2199 client_read(&ch, 1); /* anon type */
2200 msg->cm_anon_type = ch;
2201 client_read(&ch, 1); /* format type */
2202 msg->cm_format_type = ch;
2203 msglen = msglen - 3;
2205 while (msglen > 0) {
2206 client_read(&which_field, 1);
2207 if (!isalpha(which_field)) valid_msg = 0;
2211 client_read(&ch, 1);
2213 a = strlen(tempbuf);
2216 } while ( (ch != 0) && (msglen > 0) );
2218 msg->cm_fields[which_field] = strdoop(tempbuf);
2221 msg->cm_flags = CM_SKIP_HOOKS;
2222 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2223 CtdlFreeMessage(msg);
2229 * API function to delete messages which match a set of criteria
2230 * (returns the actual number of messages deleted)
2232 int CtdlDeleteMessages(char *room_name, /* which room */
2233 long dmsgnum, /* or "0" for any */
2234 char *content_type /* or "" for any */
2238 struct quickroom qrbuf;
2239 struct cdbdata *cdbfr;
2240 long *msglist = NULL;
2243 int num_deleted = 0;
2245 struct SuppMsgInfo smi;
2247 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2248 room_name, dmsgnum, content_type);
2250 /* get room record, obtaining a lock... */
2251 if (lgetroom(&qrbuf, room_name) != 0) {
2252 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2254 return (0); /* room not found */
2256 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2258 if (cdbfr != NULL) {
2259 msglist = mallok(cdbfr->len);
2260 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2261 num_msgs = cdbfr->len / sizeof(long);
2265 for (i = 0; i < num_msgs; ++i) {
2268 /* Set/clear a bit for each criterion */
2270 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2271 delete_this |= 0x01;
2273 if (strlen(content_type) == 0) {
2274 delete_this |= 0x02;
2276 GetSuppMsgInfo(&smi, msglist[i]);
2277 if (!strcasecmp(smi.smi_content_type,
2279 delete_this |= 0x02;
2283 /* Delete message only if all bits are set */
2284 if (delete_this == 0x03) {
2285 AdjRefCount(msglist[i], -1);
2291 num_msgs = sort_msglist(msglist, num_msgs);
2292 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2293 msglist, (num_msgs * sizeof(long)));
2295 qrbuf.QRhighest = msglist[num_msgs - 1];
2299 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2300 return (num_deleted);
2306 * Delete message from current room
2308 void cmd_dele(char *delstr)
2313 getuser(&CC->usersupp, CC->curr_user);
2314 if ((CC->usersupp.axlevel < 6)
2315 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2316 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2317 && (!(CC->internal_pgm))) {
2318 cprintf("%d Higher access required.\n",
2319 ERROR + HIGHER_ACCESS_REQUIRED);
2322 delnum = extract_long(delstr, 0);
2324 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2327 cprintf("%d %d message%s deleted.\n", OK,
2328 num_deleted, ((num_deleted != 1) ? "s" : ""));
2330 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2336 * move or copy a message to another room
2338 void cmd_move(char *args)
2342 struct quickroom qtemp;
2346 num = extract_long(args, 0);
2347 extract(targ, args, 1);
2348 targ[ROOMNAMELEN - 1] = 0;
2349 is_copy = extract_int(args, 2);
2351 getuser(&CC->usersupp, CC->curr_user);
2352 if ((CC->usersupp.axlevel < 6)
2353 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2354 cprintf("%d Higher access required.\n",
2355 ERROR + HIGHER_ACCESS_REQUIRED);
2359 if (getroom(&qtemp, targ) != 0) {
2360 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2364 err = CtdlSaveMsgPointerInRoom(targ, num,
2365 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2367 cprintf("%d Cannot store message in %s: error %d\n",
2372 /* Now delete the message from the source room,
2373 * if this is a 'move' rather than a 'copy' operation.
2375 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2377 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2383 * GetSuppMsgInfo() - Get the supplementary record for a message
2385 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2388 struct cdbdata *cdbsmi;
2391 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2392 smibuf->smi_msgnum = msgnum;
2393 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2395 /* Use the negative of the message number for its supp record index */
2396 TheIndex = (0L - msgnum);
2398 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2399 if (cdbsmi == NULL) {
2400 return; /* record not found; go with defaults */
2402 memcpy(smibuf, cdbsmi->ptr,
2403 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2404 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2411 * PutSuppMsgInfo() - (re)write supplementary record for a message
2413 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2417 /* Use the negative of the message number for its supp record index */
2418 TheIndex = (0L - smibuf->smi_msgnum);
2420 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2421 smibuf->smi_msgnum, smibuf->smi_refcount);
2423 cdb_store(CDB_MSGMAIN,
2424 &TheIndex, sizeof(long),
2425 smibuf, sizeof(struct SuppMsgInfo));
2430 * AdjRefCount - change the reference count for a message;
2431 * delete the message if it reaches zero
2433 void AdjRefCount(long msgnum, int incr)
2436 struct SuppMsgInfo smi;
2439 /* This is a *tight* critical section; please keep it that way, as
2440 * it may get called while nested in other critical sections.
2441 * Complicating this any further will surely cause deadlock!
2443 begin_critical_section(S_SUPPMSGMAIN);
2444 GetSuppMsgInfo(&smi, msgnum);
2445 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2446 msgnum, smi.smi_refcount);
2447 smi.smi_refcount += incr;
2448 PutSuppMsgInfo(&smi);
2449 end_critical_section(S_SUPPMSGMAIN);
2450 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2451 msgnum, smi.smi_refcount);
2453 /* If the reference count is now zero, delete the message
2454 * (and its supplementary record as well).
2456 if (smi.smi_refcount == 0) {
2457 lprintf(9, "Deleting message <%ld>\n", msgnum);
2459 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2460 delnum = (0L - msgnum);
2461 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2466 * Write a generic object to this room
2468 * Note: this could be much more efficient. Right now we use two temporary
2469 * files, and still pull the message into memory as with all others.
2471 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2472 char *content_type, /* MIME type of this object */
2473 char *tempfilename, /* Where to fetch it from */
2474 struct usersupp *is_mailbox, /* Mailbox room? */
2475 int is_binary, /* Is encoding necessary? */
2476 int is_unique, /* Del others of this type? */
2477 unsigned int flags /* Internal save flags */
2482 char filename[PATH_MAX];
2485 struct quickroom qrbuf;
2486 char roomname[ROOMNAMELEN];
2487 struct CtdlMessage *msg;
2490 if (is_mailbox != NULL)
2491 MailboxName(roomname, is_mailbox, req_room);
2493 safestrncpy(roomname, req_room, sizeof(roomname));
2494 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2496 strcpy(filename, tmpnam(NULL));
2497 fp = fopen(filename, "w");
2501 tempfp = fopen(tempfilename, "r");
2502 if (tempfp == NULL) {
2508 fprintf(fp, "Content-type: %s\n", content_type);
2509 lprintf(9, "Content-type: %s\n", content_type);
2511 if (is_binary == 0) {
2512 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2513 while (ch = getc(tempfp), ch > 0)
2519 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2522 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2523 tempfilename, filename);
2527 lprintf(9, "Allocating\n");
2528 msg = mallok(sizeof(struct CtdlMessage));
2529 memset(msg, 0, sizeof(struct CtdlMessage));
2530 msg->cm_magic = CTDLMESSAGE_MAGIC;
2531 msg->cm_anon_type = MES_NORMAL;
2532 msg->cm_format_type = 4;
2533 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2534 msg->cm_fields['O'] = strdoop(req_room);
2535 msg->cm_fields['N'] = strdoop(config.c_nodename);
2536 msg->cm_fields['H'] = strdoop(config.c_humannode);
2537 msg->cm_flags = flags;
2539 lprintf(9, "Loading\n");
2540 fp = fopen(filename, "rb");
2541 fseek(fp, 0L, SEEK_END);
2544 msg->cm_fields['M'] = mallok(len);
2545 fread(msg->cm_fields['M'], len, 1, fp);
2549 /* Create the requested room if we have to. */
2550 if (getroom(&qrbuf, roomname) != 0) {
2551 create_room(roomname,
2552 ( (is_mailbox != NULL) ? 4 : 3 ),
2555 /* If the caller specified this object as unique, delete all
2556 * other objects of this type that are currently in the room.
2559 lprintf(9, "Deleted %d other msgs of this type\n",
2560 CtdlDeleteMessages(roomname, 0L, content_type));
2562 /* Now write the data */
2563 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2564 CtdlFreeMessage(msg);
2572 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2573 config_msgnum = msgnum;
2577 char *CtdlGetSysConfig(char *sysconfname) {
2578 char hold_rm[ROOMNAMELEN];
2581 struct CtdlMessage *msg;
2584 strcpy(hold_rm, CC->quickroom.QRname);
2585 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2586 getroom(&CC->quickroom, hold_rm);
2591 /* We want the last (and probably only) config in this room */
2592 begin_critical_section(S_CONFIG);
2593 config_msgnum = (-1L);
2594 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2595 CtdlGetSysConfigBackend, NULL);
2596 msgnum = config_msgnum;
2597 end_critical_section(S_CONFIG);
2603 msg = CtdlFetchMessage(msgnum);
2605 conf = strdoop(msg->cm_fields['M']);
2606 CtdlFreeMessage(msg);
2613 getroom(&CC->quickroom, hold_rm);
2615 lprintf(9, "eggstracting...\n");
2616 if (conf != NULL) do {
2617 extract_token(buf, conf, 0, '\n');
2618 lprintf(9, "eggstracted <%s>\n", buf);
2619 strcpy(conf, &conf[strlen(buf)+1]);
2620 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2625 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2626 char temp[PATH_MAX];
2629 strcpy(temp, tmpnam(NULL));
2631 fp = fopen(temp, "w");
2632 if (fp == NULL) return;
2633 fprintf(fp, "%s", sysconfdata);
2636 /* this handy API function does all the work for us */
2637 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);