4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
39 #include "dynloader.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
55 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
56 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
57 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
59 extern struct config config;
63 "", "", "", "", "", "", "", "",
64 "", "", "", "", "", "", "", "",
65 "", "", "", "", "", "", "", "",
66 "", "", "", "", "", "", "", "",
67 "", "", "", "", "", "", "", "",
68 "", "", "", "", "", "", "", "",
69 "", "", "", "", "", "", "", "",
70 "", "", "", "", "", "", "", "",
97 * This function is self explanatory.
98 * (What can I say, I'm in a weird mood today...)
100 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
104 for (i = 0; i < strlen(name); ++i) {
105 if (name[i] == '@') {
106 while (isspace(name[i - 1]) && i > 0) {
107 strcpy(&name[i - 1], &name[i]);
110 while (isspace(name[i + 1])) {
111 strcpy(&name[i + 1], &name[i + 2]);
119 * Aliasing for network mail.
120 * (Error messages have been commented out, because this is a server.)
122 int alias(char *name)
123 { /* process alias and routing info for mail */
126 char aaa[SIZ], bbb[SIZ];
127 char *ignetcfg = NULL;
128 char *ignetmap = NULL;
134 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
136 fp = fopen("network/mail.aliases", "r");
138 fp = fopen("/dev/null", "r");
145 while (fgets(aaa, sizeof aaa, fp) != NULL) {
146 while (isspace(name[0]))
147 strcpy(name, &name[1]);
148 aaa[strlen(aaa) - 1] = 0;
150 for (a = 0; a < strlen(aaa); ++a) {
152 strcpy(bbb, &aaa[a + 1]);
156 if (!strcasecmp(name, aaa))
160 lprintf(7, "Mail is being forwarded to %s\n", name);
162 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
163 for (a=0; a<strlen(name); ++a) {
164 if (name[a] == '@') {
165 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
167 lprintf(7, "Changed to <%s>\n", name);
172 /* determine local or remote type, see citadel.h */
174 at = haschar(name, '@');
175 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
176 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
177 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
179 /* figure out the delivery mode */
181 extract_token(node, name, 1, '@');
183 /* If there are one or more dots in the nodename, we assume that it
184 * is an FQDN and will attempt SMTP delivery to the Internet.
186 if (haschar(node, '.') > 0) {
187 return(MES_INTERNET);
190 /* Otherwise we look in the IGnet maps for a valid Citadel node.
191 * Try directly-connected nodes first...
193 ignetcfg = CtdlGetSysConfig(IGNETCFG);
194 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
195 extract_token(buf, ignetcfg, i, '\n');
196 extract_token(testnode, buf, 0, '|');
197 if (!strcasecmp(node, testnode)) {
205 * Then try nodes that are two or more hops away.
207 ignetmap = CtdlGetSysConfig(IGNETMAP);
208 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
209 extract_token(buf, ignetmap, i, '\n');
210 extract_token(testnode, buf, 0, '|');
211 if (!strcasecmp(node, testnode)) {
218 /* If we get to this point it's an invalid node name */
227 fp = fopen("citadel.control", "r");
228 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
234 void simple_listing(long msgnum, void *userdata)
236 cprintf("%ld\n", msgnum);
241 /* Determine if a given message matches the fields in a message template.
242 * Return 0 for a successful match.
244 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
247 /* If there aren't any fields in the template, all messages will
250 if (template == NULL) return(0);
252 /* Null messages are bogus. */
253 if (msg == NULL) return(1);
255 for (i='A'; i<='Z'; ++i) {
256 if (template->cm_fields[i] != NULL) {
257 if (msg->cm_fields[i] == NULL) {
260 if (strcasecmp(msg->cm_fields[i],
261 template->cm_fields[i])) return 1;
265 /* All compares succeeded: we have a match! */
271 * Manipulate the "seen msgs" string.
273 void CtdlSetSeen(long target_msgnum, int target_setting) {
275 struct cdbdata *cdbfr;
285 /* Learn about the user and room in question */
287 getuser(&CC->usersupp, CC->curr_user);
288 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
290 /* Load the message list */
291 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
293 msglist = mallok(cdbfr->len);
294 memcpy(msglist, cdbfr->ptr, cdbfr->len);
295 num_msgs = cdbfr->len / sizeof(long);
298 return; /* No messages at all? No further action. */
301 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
304 for (i=0; i<num_msgs; ++i) {
307 if (msglist[i] == target_msgnum) {
308 is_seen = target_setting;
311 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
317 if (lo < 0L) lo = msglist[i];
320 if ( ((is_seen == 0) && (was_seen == 1))
321 || ((is_seen == 1) && (i == num_msgs-1)) ) {
322 if ( (strlen(newseen) + 20) > SIZ) {
323 strcpy(newseen, &newseen[20]);
326 if (strlen(newseen) > 0) strcat(newseen, ",");
328 sprintf(&newseen[strlen(newseen)], "%ld", lo);
331 sprintf(&newseen[strlen(newseen)], "%ld:%ld",
340 safestrncpy(vbuf.v_seen, newseen, SIZ);
341 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
343 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
348 * API function to perform an operation for each qualifying message in the
349 * current room. (Returns the number of messages processed.)
351 int CtdlForEachMessage(int mode, long ref,
352 int moderation_level,
354 struct CtdlMessage *compare,
355 void (*CallBack) (long, void *),
361 struct cdbdata *cdbfr;
362 long *msglist = NULL;
364 int num_processed = 0;
367 struct CtdlMessage *msg;
370 int printed_lastold = 0;
372 /* Learn about the user and room in question */
374 getuser(&CC->usersupp, CC->curr_user);
375 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
377 /* Load the message list */
378 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
380 msglist = mallok(cdbfr->len);
381 memcpy(msglist, cdbfr->ptr, cdbfr->len);
382 num_msgs = cdbfr->len / sizeof(long);
385 return 0; /* No messages at all? No further action. */
390 * Now begin the traversal.
392 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
393 GetMetaData(&smi, msglist[a]);
395 /* Filter out messages that are moderated below the level
396 * currently being viewed at.
398 if (smi.meta_mod < moderation_level) {
402 /* If the caller is looking for a specific MIME type, filter
403 * out all messages which are not of the type requested.
405 if (content_type != NULL) if (strlen(content_type) > 0) {
406 if (strcasecmp(smi.meta_content_type, content_type)) {
412 num_msgs = sort_msglist(msglist, num_msgs);
414 /* If a template was supplied, filter out the messages which
415 * don't match. (This could induce some delays!)
418 if (compare != NULL) {
419 for (a = 0; a < num_msgs; ++a) {
420 msg = CtdlFetchMessage(msglist[a]);
422 if (CtdlMsgCmp(msg, compare)) {
425 CtdlFreeMessage(msg);
433 * Now iterate through the message list, according to the
434 * criteria supplied by the caller.
437 for (a = 0; a < num_msgs; ++a) {
438 thismsg = msglist[a];
439 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
440 if (is_seen) lastold = thismsg;
445 || ((mode == MSGS_OLD) && (is_seen))
446 || ((mode == MSGS_NEW) && (!is_seen))
447 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
448 || ((mode == MSGS_FIRST) && (a < ref))
449 || ((mode == MSGS_GT) && (thismsg > ref))
450 || ((mode == MSGS_EQ) && (thismsg == ref))
453 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
455 CallBack(lastold, userdata);
459 if (CallBack) CallBack(thismsg, userdata);
463 phree(msglist); /* Clean up */
464 return num_processed;
470 * cmd_msgs() - get list of message #'s in this room
471 * implements the MSGS server command using CtdlForEachMessage()
473 void cmd_msgs(char *cmdbuf)
482 int with_template = 0;
483 struct CtdlMessage *template = NULL;
485 extract(which, cmdbuf, 0);
486 cm_ref = extract_int(cmdbuf, 1);
487 with_template = extract_int(cmdbuf, 2);
491 if (!strncasecmp(which, "OLD", 3))
493 else if (!strncasecmp(which, "NEW", 3))
495 else if (!strncasecmp(which, "FIRST", 5))
497 else if (!strncasecmp(which, "LAST", 4))
499 else if (!strncasecmp(which, "GT", 2))
502 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
503 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
508 cprintf("%d Send template then receive message list\n",
510 template = (struct CtdlMessage *)
511 mallok(sizeof(struct CtdlMessage));
512 memset(template, 0, sizeof(struct CtdlMessage));
513 while(client_gets(buf), strcmp(buf,"000")) {
514 extract(tfield, buf, 0);
515 extract(tvalue, buf, 1);
516 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
517 if (!strcasecmp(tfield, msgkeys[i])) {
518 template->cm_fields[i] =
525 cprintf("%d Message list...\n", LISTING_FOLLOWS);
528 CtdlForEachMessage(mode, cm_ref,
529 CC->usersupp.moderation_filter,
530 NULL, template, simple_listing, NULL);
531 if (template != NULL) CtdlFreeMessage(template);
539 * help_subst() - support routine for help file viewer
541 void help_subst(char *strbuf, char *source, char *dest)
546 while (p = pattern2(strbuf, source), (p >= 0)) {
547 strcpy(workbuf, &strbuf[p + strlen(source)]);
548 strcpy(&strbuf[p], dest);
549 strcat(strbuf, workbuf);
554 void do_help_subst(char *buffer)
558 help_subst(buffer, "^nodename", config.c_nodename);
559 help_subst(buffer, "^humannode", config.c_humannode);
560 help_subst(buffer, "^fqdn", config.c_fqdn);
561 help_subst(buffer, "^username", CC->usersupp.fullname);
562 sprintf(buf2, "%ld", CC->usersupp.usernum);
563 help_subst(buffer, "^usernum", buf2);
564 help_subst(buffer, "^sysadm", config.c_sysadm);
565 help_subst(buffer, "^variantname", CITADEL);
566 sprintf(buf2, "%d", config.c_maxsessions);
567 help_subst(buffer, "^maxsessions", buf2);
573 * memfmout() - Citadel text formatter and paginator.
574 * Although the original purpose of this routine was to format
575 * text to the reader's screen width, all we're really using it
576 * for here is to format text out to 80 columns before sending it
577 * to the client. The client software may reformat it again.
580 int width, /* screen width to use */
581 char *mptr, /* where are we going to get our text from? */
582 char subst, /* nonzero if we should do substitutions */
583 char *nl) /* string to terminate lines with */
595 c = 1; /* c is the current pos */
599 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
601 buffer[strlen(buffer) + 1] = 0;
602 buffer[strlen(buffer)] = ch;
605 if (buffer[0] == '^')
606 do_help_subst(buffer);
608 buffer[strlen(buffer) + 1] = 0;
610 strcpy(buffer, &buffer[1]);
618 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
620 if (((old == 13) || (old == 10)) && (isspace(real))) {
628 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
629 cprintf("%s%s", nl, aaa);
638 if ((strlen(aaa) + c) > (width - 5)) {
647 if ((ch == 13) || (ch == 10)) {
648 cprintf("%s%s", aaa, nl);
655 cprintf("%s%s", aaa, nl);
661 * Callback function for mime parser that simply lists the part
663 void list_this_part(char *name, char *filename, char *partnum, char *disp,
664 void *content, char *cbtype, size_t length, char *encoding,
668 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
669 name, filename, partnum, disp, cbtype, (long)length);
674 * Callback function for mime parser that opens a section for downloading
676 void mime_download(char *name, char *filename, char *partnum, char *disp,
677 void *content, char *cbtype, size_t length, char *encoding,
681 /* Silently go away if there's already a download open... */
682 if (CC->download_fp != NULL)
685 /* ...or if this is not the desired section */
686 if (strcasecmp(desired_section, partnum))
689 CC->download_fp = tmpfile();
690 if (CC->download_fp == NULL)
693 fwrite(content, length, 1, CC->download_fp);
694 fflush(CC->download_fp);
695 rewind(CC->download_fp);
697 OpenCmdResult(filename, cbtype);
703 * Load a message from disk into memory.
704 * This is used by CtdlOutputMsg() and other fetch functions.
706 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
707 * using the CtdlMessageFree() function.
709 struct CtdlMessage *CtdlFetchMessage(long msgnum)
711 struct cdbdata *dmsgtext;
712 struct CtdlMessage *ret = NULL;
715 CIT_UBYTE field_header;
718 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
719 if (dmsgtext == NULL) {
722 mptr = dmsgtext->ptr;
724 /* Parse the three bytes that begin EVERY message on disk.
725 * The first is always 0xFF, the on-disk magic number.
726 * The second is the anonymous/public type byte.
727 * The third is the format type byte (vari, fixed, or MIME).
731 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
735 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
736 memset(ret, 0, sizeof(struct CtdlMessage));
738 ret->cm_magic = CTDLMESSAGE_MAGIC;
739 ret->cm_anon_type = *mptr++; /* Anon type byte */
740 ret->cm_format_type = *mptr++; /* Format type byte */
743 * The rest is zero or more arbitrary fields. Load them in.
744 * We're done when we encounter either a zero-length field or
745 * have just processed the 'M' (message text) field.
748 field_length = strlen(mptr);
749 if (field_length == 0)
751 field_header = *mptr++;
752 ret->cm_fields[field_header] = mallok(field_length);
753 strcpy(ret->cm_fields[field_header], mptr);
755 while (*mptr++ != 0); /* advance to next field */
757 } while ((field_length > 0) && (field_header != 'M'));
761 /* Always make sure there's something in the msg text field */
762 if (ret->cm_fields['M'] == NULL)
763 ret->cm_fields['M'] = strdoop("<no text>\n");
765 /* Perform "before read" hooks (aborting if any return nonzero) */
766 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
767 CtdlFreeMessage(ret);
776 * Returns 1 if the supplied pointer points to a valid Citadel message.
777 * If the pointer is NULL or the magic number check fails, returns 0.
779 int is_valid_message(struct CtdlMessage *msg) {
782 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
783 lprintf(3, "is_valid_message() -- self-check failed\n");
791 * 'Destructor' for struct CtdlMessage
793 void CtdlFreeMessage(struct CtdlMessage *msg)
797 if (is_valid_message(msg) == 0) return;
799 for (i = 0; i < 256; ++i)
800 if (msg->cm_fields[i] != NULL) {
801 phree(msg->cm_fields[i]);
804 msg->cm_magic = 0; /* just in case */
810 * Pre callback function for multipart/alternative
812 * NOTE: this differs from the standard behavior for a reason. Normally when
813 * displaying multipart/alternative you want to show the _last_ usable
814 * format in the message. Here we show the _first_ one, because it's
815 * usually text/plain. Since this set of functions is designed for text
816 * output to non-MIME-aware clients, this is the desired behavior.
819 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
820 void *content, char *cbtype, size_t length, char *encoding,
823 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);
824 if (!strcasecmp(cbtype, "multipart/alternative")) {
832 * Post callback function for multipart/alternative
834 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
835 void *content, char *cbtype, size_t length, char *encoding,
838 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);
839 if (!strcasecmp(cbtype, "multipart/alternative")) {
847 * Inline callback function for mime parser that wants to display text
849 void fixed_output(char *name, char *filename, char *partnum, char *disp,
850 void *content, char *cbtype, size_t length, char *encoding,
858 lprintf(9, "fixed_output() type=<%s>\n", cbtype);
861 * If we're in the middle of a multipart/alternative scope and
862 * we've already printed another section, skip this one.
864 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
865 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
870 if ( (!strcasecmp(cbtype, "text/plain"))
871 || (strlen(cbtype)==0) ) {
877 if (ch==10) cprintf("\r\n");
878 else cprintf("%c", ch);
882 if (ch != '\n') cprintf("\n");
884 else if (!strcasecmp(cbtype, "text/html")) {
885 ptr = html_to_ascii(content, 80, 0);
890 if (ch==10) cprintf("\r\n");
891 else cprintf("%c", ch);
895 else if (strncasecmp(cbtype, "multipart/", 10)) {
896 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
897 partnum, filename, cbtype, (long)length);
903 * Get a message off disk. (returns om_* values found in msgbase.h)
906 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
907 int mode, /* how would you like that message? */
908 int headers_only, /* eschew the message body? */
909 int do_proto, /* do Citadel protocol responses? */
910 int crlf /* Use CRLF newlines instead of LF? */
912 struct CtdlMessage *TheMessage;
915 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
920 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
921 if (do_proto) cprintf("%d Not logged in.\n",
922 ERROR + NOT_LOGGED_IN);
923 return(om_not_logged_in);
926 /* FIXME ... small security issue
927 * We need to check to make sure the requested message is actually
928 * in the current room, and set msg_ok to 1 only if it is. This
929 * functionality is currently missing because I'm in a hurry to replace
930 * broken production code with nonbroken pre-beta code. :( -- ajc
933 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
935 return(om_no_such_msg);
940 * Fetch the message from disk
942 TheMessage = CtdlFetchMessage(msg_num);
943 if (TheMessage == NULL) {
944 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
946 return(om_no_such_msg);
949 retcode = CtdlOutputPreLoadedMsg(
950 TheMessage, msg_num, mode,
951 headers_only, do_proto, crlf);
953 CtdlFreeMessage(TheMessage);
959 * Get a message off disk. (returns om_* values found in msgbase.h)
962 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
964 int mode, /* how would you like that message? */
965 int headers_only, /* eschew the message body? */
966 int do_proto, /* do Citadel protocol responses? */
967 int crlf /* Use CRLF newlines instead of LF? */
973 char display_name[SIZ];
975 char *nl; /* newline string */
977 /* buffers needed for RFC822 translation */
987 sprintf(mid, "%ld", msg_num);
988 nl = (crlf ? "\r\n" : "\n");
990 if (!is_valid_message(TheMessage)) {
991 lprintf(1, "ERROR: invalid preloaded message for output\n");
992 return(om_no_such_msg);
995 /* Are we downloading a MIME component? */
996 if (mode == MT_DOWNLOAD) {
997 if (TheMessage->cm_format_type != FMT_RFC822) {
999 cprintf("%d This is not a MIME message.\n",
1001 } else if (CC->download_fp != NULL) {
1002 if (do_proto) cprintf(
1003 "%d You already have a download open.\n",
1006 /* Parse the message text component */
1007 mptr = TheMessage->cm_fields['M'];
1008 mime_parser(mptr, NULL,
1009 *mime_download, NULL, NULL,
1011 /* If there's no file open by this time, the requested
1012 * section wasn't found, so print an error
1014 if (CC->download_fp == NULL) {
1015 if (do_proto) cprintf(
1016 "%d Section %s not found.\n",
1017 ERROR + FILE_NOT_FOUND,
1021 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1024 /* now for the user-mode message reading loops */
1025 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1027 /* Tell the client which format type we're using. If this is a
1028 * MIME message, *lie* about it and tell the user it's fixed-format.
1030 if (mode == MT_CITADEL) {
1031 if (TheMessage->cm_format_type == FMT_RFC822) {
1032 if (do_proto) cprintf("type=1\n");
1035 if (do_proto) cprintf("type=%d\n",
1036 TheMessage->cm_format_type);
1040 /* nhdr=yes means that we're only displaying headers, no body */
1041 if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1042 if (do_proto) cprintf("nhdr=yes\n");
1045 /* begin header processing loop for Citadel message format */
1047 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1049 strcpy(display_name, "<unknown>");
1050 if (TheMessage->cm_fields['A']) {
1051 strcpy(buf, TheMessage->cm_fields['A']);
1052 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1053 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1054 strcpy(display_name, "****");
1056 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1057 strcpy(display_name, "anonymous");
1060 strcpy(display_name, buf);
1062 if ((is_room_aide())
1063 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1064 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1065 sprintf(&display_name[strlen(display_name)],
1070 strcpy(allkeys, FORDER);
1071 for (i=0; i<strlen(allkeys); ++i) {
1072 k = (int) allkeys[i];
1074 if (TheMessage->cm_fields[k] != NULL) {
1076 if (do_proto) cprintf("%s=%s\n",
1081 if (do_proto) cprintf("%s=%s\n",
1083 TheMessage->cm_fields[k]
1092 /* begin header processing loop for RFC822 transfer format */
1097 strcpy(snode, NODENAME);
1098 strcpy(lnode, HUMANNODE);
1099 if (mode == MT_RFC822) {
1100 cprintf("X-UIDL: %ld%s", msg_num, nl);
1101 for (i = 0; i < 256; ++i) {
1102 if (TheMessage->cm_fields[i]) {
1103 mptr = TheMessage->cm_fields[i];
1106 strcpy(luser, mptr);
1107 strcpy(suser, mptr);
1110 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1111 into thinking that mail messages are newsgroup messages instead. When we
1112 add NNTP support back into Citadel we'll have to add code to only output
1113 this field when appropriate.
1114 else if (i == 'P') {
1115 cprintf("Path: %s%s", mptr, nl);
1119 cprintf("Subject: %s%s", mptr, nl);
1123 strcpy(lnode, mptr);
1125 cprintf("X-Citadel-Room: %s%s",
1128 strcpy(snode, mptr);
1130 cprintf("To: %s%s", mptr, nl);
1131 else if (i == 'T') {
1132 datestring(datestamp, atol(mptr),
1133 DATESTRING_RFC822 );
1134 cprintf("Date: %s%s", datestamp, nl);
1140 for (i=0; i<strlen(suser); ++i) {
1141 suser[i] = tolower(suser[i]);
1142 if (!isalnum(suser[i])) suser[i]='_';
1145 if (mode == MT_RFC822) {
1146 if (!strcasecmp(snode, NODENAME)) {
1147 strcpy(snode, FQDN);
1150 /* Construct a fun message id */
1151 cprintf("Message-ID: <%s", mid);
1152 if (strchr(mid, '@')==NULL) {
1153 cprintf("@%s", snode);
1157 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1159 if (strlen(fuser) > 0) {
1160 cprintf("From: %s (%s)%s", fuser, luser, nl);
1163 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1166 cprintf("Organization: %s%s", lnode, nl);
1169 /* end header processing loop ... at this point, we're in the text */
1171 mptr = TheMessage->cm_fields['M'];
1173 /* Tell the client about the MIME parts in this message */
1174 if (TheMessage->cm_format_type == FMT_RFC822) {
1175 if (mode == MT_CITADEL) {
1176 mime_parser(mptr, NULL,
1177 *list_this_part, NULL, NULL,
1180 else if (mode == MT_MIME) { /* list parts only */
1181 mime_parser(mptr, NULL,
1182 *list_this_part, NULL, NULL,
1184 if (do_proto) cprintf("000\n");
1187 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1188 /* FIXME ... we have to put some code in here to avoid
1189 * printing duplicate header information when both
1190 * Citadel and RFC822 headers exist. Preference should
1191 * probably be given to the RFC822 headers.
1193 while (ch=*(mptr++), ch!=0) {
1195 else if (ch==10) cprintf("%s", nl);
1196 else cprintf("%c", ch);
1198 if (do_proto) cprintf("000\n");
1204 if (do_proto) cprintf("000\n");
1208 /* signify start of msg text */
1209 if (mode == MT_CITADEL)
1210 if (do_proto) cprintf("text\n");
1211 if (mode == MT_RFC822) {
1212 if (TheMessage->cm_fields['U'] == NULL) {
1213 cprintf("Subject: (no subject)%s", nl);
1218 /* If the format type on disk is 1 (fixed-format), then we want
1219 * everything to be output completely literally ... regardless of
1220 * what message transfer format is in use.
1222 if (TheMessage->cm_format_type == FMT_FIXED) {
1224 while (ch = *mptr++, ch > 0) {
1227 if ((ch == 10) || (strlen(buf) > 250)) {
1228 cprintf("%s%s", buf, nl);
1231 buf[strlen(buf) + 1] = 0;
1232 buf[strlen(buf)] = ch;
1235 if (strlen(buf) > 0)
1236 cprintf("%s%s", buf, nl);
1239 /* If the message on disk is format 0 (Citadel vari-format), we
1240 * output using the formatter at 80 columns. This is the final output
1241 * form if the transfer format is RFC822, but if the transfer format
1242 * is Citadel proprietary, it'll still work, because the indentation
1243 * for new paragraphs is correct and the client will reformat the
1244 * message to the reader's screen width.
1246 if (TheMessage->cm_format_type == FMT_CITADEL) {
1247 memfmout(80, mptr, 0, nl);
1250 /* If the message on disk is format 4 (MIME), we've gotta hand it
1251 * off to the MIME parser. The client has already been told that
1252 * this message is format 1 (fixed format), so the callback function
1253 * we use will display those parts as-is.
1255 if (TheMessage->cm_format_type == FMT_RFC822) {
1256 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1257 memset(ma, 0, sizeof(struct ma_info));
1258 mime_parser(mptr, NULL,
1259 *fixed_output, *fixed_output_pre, *fixed_output_post,
1263 /* now we're done */
1264 if (do_proto) cprintf("000\n");
1271 * display a message (mode 0 - Citadel proprietary)
1273 void cmd_msg0(char *cmdbuf)
1276 int headers_only = 0;
1278 msgid = extract_long(cmdbuf, 0);
1279 headers_only = extract_int(cmdbuf, 1);
1281 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1287 * display a message (mode 2 - RFC822)
1289 void cmd_msg2(char *cmdbuf)
1292 int headers_only = 0;
1294 msgid = extract_long(cmdbuf, 0);
1295 headers_only = extract_int(cmdbuf, 1);
1297 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1303 * display a message (mode 3 - IGnet raw format - internal programs only)
1305 void cmd_msg3(char *cmdbuf)
1308 struct CtdlMessage *msg;
1311 if (CC->internal_pgm == 0) {
1312 cprintf("%d This command is for internal programs only.\n",
1317 msgnum = extract_long(cmdbuf, 0);
1318 msg = CtdlFetchMessage(msgnum);
1320 cprintf("%d Message %ld not found.\n",
1325 serialize_message(&smr, msg);
1326 CtdlFreeMessage(msg);
1329 cprintf("%d Unable to serialize message\n",
1330 ERROR+INTERNAL_ERROR);
1334 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1335 client_write(smr.ser, smr.len);
1342 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1344 void cmd_msg4(char *cmdbuf)
1348 msgid = extract_long(cmdbuf, 0);
1349 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1353 * Open a component of a MIME message as a download file
1355 void cmd_opna(char *cmdbuf)
1359 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1361 msgid = extract_long(cmdbuf, 0);
1362 extract(desired_section, cmdbuf, 1);
1364 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1369 * Save a message pointer into a specified room
1370 * (Returns 0 for success, nonzero for failure)
1371 * roomname may be NULL to use the current room
1373 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1375 char hold_rm[ROOMNAMELEN];
1376 struct cdbdata *cdbfr;
1379 long highest_msg = 0L;
1380 struct CtdlMessage *msg = NULL;
1382 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1383 roomname, msgid, flags);
1385 strcpy(hold_rm, CC->quickroom.QRname);
1387 /* We may need to check to see if this message is real */
1388 if ( (flags & SM_VERIFY_GOODNESS)
1389 || (flags & SM_DO_REPL_CHECK)
1391 msg = CtdlFetchMessage(msgid);
1392 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1395 /* Perform replication checks if necessary */
1396 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1398 if (getroom(&CC->quickroom,
1399 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1401 lprintf(9, "No such room <%s>\n", roomname);
1402 if (msg != NULL) CtdlFreeMessage(msg);
1403 return(ERROR + ROOM_NOT_FOUND);
1406 if (ReplicationChecks(msg) != 0) {
1407 getroom(&CC->quickroom, hold_rm);
1408 if (msg != NULL) CtdlFreeMessage(msg);
1409 lprintf(9, "Did replication, and newer exists\n");
1414 /* Now the regular stuff */
1415 if (lgetroom(&CC->quickroom,
1416 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1418 lprintf(9, "No such room <%s>\n", roomname);
1419 if (msg != NULL) CtdlFreeMessage(msg);
1420 return(ERROR + ROOM_NOT_FOUND);
1423 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1424 if (cdbfr == NULL) {
1428 msglist = mallok(cdbfr->len);
1429 if (msglist == NULL)
1430 lprintf(3, "ERROR malloc msglist!\n");
1431 num_msgs = cdbfr->len / sizeof(long);
1432 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1437 /* Make sure the message doesn't already exist in this room. It
1438 * is absolutely taboo to have more than one reference to the same
1439 * message in a room.
1441 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1442 if (msglist[i] == msgid) {
1443 lputroom(&CC->quickroom); /* unlock the room */
1444 getroom(&CC->quickroom, hold_rm);
1445 if (msg != NULL) CtdlFreeMessage(msg);
1446 return(ERROR + ALREADY_EXISTS);
1450 /* Now add the new message */
1452 msglist = reallok(msglist,
1453 (num_msgs * sizeof(long)));
1455 if (msglist == NULL) {
1456 lprintf(3, "ERROR: can't realloc message list!\n");
1458 msglist[num_msgs - 1] = msgid;
1460 /* Sort the message list, so all the msgid's are in order */
1461 num_msgs = sort_msglist(msglist, num_msgs);
1463 /* Determine the highest message number */
1464 highest_msg = msglist[num_msgs - 1];
1466 /* Write it back to disk. */
1467 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1468 msglist, num_msgs * sizeof(long));
1470 /* Free up the memory we used. */
1473 /* Update the highest-message pointer and unlock the room. */
1474 CC->quickroom.QRhighest = highest_msg;
1475 lputroom(&CC->quickroom);
1476 getroom(&CC->quickroom, hold_rm);
1478 /* Bump the reference count for this message. */
1479 if ((flags & SM_DONT_BUMP_REF)==0) {
1480 AdjRefCount(msgid, +1);
1483 /* Return success. */
1484 if (msg != NULL) CtdlFreeMessage(msg);
1491 * Message base operation to send a message to the master file
1492 * (returns new message number)
1494 * This is the back end for CtdlSubmitMsg() and should not be directly
1495 * called by server-side modules.
1498 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1499 FILE *save_a_copy) /* save a copy to disk? */
1506 /* Get a new message number */
1507 newmsgid = get_new_message_number();
1508 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1510 /* Generate an ID if we don't have one already */
1511 if (msg->cm_fields['I']==NULL) {
1512 msg->cm_fields['I'] = strdoop(msgidbuf);
1515 serialize_message(&smr, msg);
1518 cprintf("%d Unable to serialize message\n",
1519 ERROR+INTERNAL_ERROR);
1523 /* Write our little bundle of joy into the message base */
1524 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1525 smr.ser, smr.len) < 0) {
1526 lprintf(2, "Can't store message\n");
1532 /* If the caller specified that a copy should be saved to a particular
1533 * file handle, do that now too.
1535 if (save_a_copy != NULL) {
1536 fwrite(smr.ser, smr.len, 1, save_a_copy);
1539 /* Free the memory we used for the serialized message */
1542 /* Return the *local* message ID to the caller
1543 * (even if we're storing an incoming network message)
1551 * Serialize a struct CtdlMessage into the format used on disk and network.
1553 * This function loads up a "struct ser_ret" (defined in server.h) which
1554 * contains the length of the serialized message and a pointer to the
1555 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1557 void serialize_message(struct ser_ret *ret, /* return values */
1558 struct CtdlMessage *msg) /* unserialized msg */
1562 static char *forder = FORDER;
1564 if (is_valid_message(msg) == 0) return; /* self check */
1567 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1568 ret->len = ret->len +
1569 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1571 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1572 ret->ser = mallok(ret->len);
1573 if (ret->ser == NULL) {
1579 ret->ser[1] = msg->cm_anon_type;
1580 ret->ser[2] = msg->cm_format_type;
1583 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1584 ret->ser[wlen++] = (char)forder[i];
1585 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1586 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1588 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1589 (long)ret->len, (long)wlen);
1597 * Back end for the ReplicationChecks() function
1599 void check_repl(long msgnum, void *userdata) {
1600 struct CtdlMessage *msg;
1601 time_t timestamp = (-1L);
1603 lprintf(9, "check_repl() found message %ld\n", msgnum);
1604 msg = CtdlFetchMessage(msgnum);
1605 if (msg == NULL) return;
1606 if (msg->cm_fields['T'] != NULL) {
1607 timestamp = atol(msg->cm_fields['T']);
1609 CtdlFreeMessage(msg);
1611 if (timestamp > msg_repl->highest) {
1612 msg_repl->highest = timestamp; /* newer! */
1613 lprintf(9, "newer!\n");
1616 lprintf(9, "older!\n");
1618 /* Existing isn't newer? Then delete the old one(s). */
1619 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1624 * Check to see if any messages already exist which carry the same Extended ID
1628 * -> With older timestamps: delete them and return 0. Message will be saved.
1629 * -> With newer timestamps: return 1. Message save will be aborted.
1631 int ReplicationChecks(struct CtdlMessage *msg) {
1632 struct CtdlMessage *template;
1635 lprintf(9, "ReplicationChecks() started\n");
1636 /* No extended id? Don't do anything. */
1637 if (msg->cm_fields['E'] == NULL) return 0;
1638 if (strlen(msg->cm_fields['E']) == 0) return 0;
1639 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1641 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1642 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1643 msg_repl->highest = atol(msg->cm_fields['T']);
1645 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1646 memset(template, 0, sizeof(struct CtdlMessage));
1647 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1649 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1652 /* If a newer message exists with the same Extended ID, abort
1655 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1659 CtdlFreeMessage(template);
1660 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1668 * Save a message to disk and submit it into the delivery system.
1670 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1671 struct recptypes *recps, /* recipients (if mail) */
1672 char *force /* force a particular room? */
1675 char hold_rm[ROOMNAMELEN];
1676 char actual_rm[ROOMNAMELEN];
1677 char force_room[ROOMNAMELEN];
1678 char content_type[SIZ]; /* We have to learn this */
1679 char recipient[SIZ];
1682 struct usersupp userbuf;
1684 struct MetaData smi;
1685 FILE *network_fp = NULL;
1686 static int seqnum = 1;
1687 struct CtdlMessage *imsg = NULL;
1690 char *hold_R, *hold_D;
1692 lprintf(9, "CtdlSubmitMsg() called\n");
1693 if (is_valid_message(msg) == 0) return(-1); /* self check */
1695 /* If this message has no timestamp, we take the liberty of
1696 * giving it one, right now.
1698 if (msg->cm_fields['T'] == NULL) {
1699 lprintf(9, "Generating timestamp\n");
1700 sprintf(aaa, "%ld", (long)time(NULL));
1701 msg->cm_fields['T'] = strdoop(aaa);
1704 /* If this message has no path, we generate one.
1706 if (msg->cm_fields['P'] == NULL) {
1707 lprintf(9, "Generating path\n");
1708 if (msg->cm_fields['A'] != NULL) {
1709 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1710 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1711 if (isspace(msg->cm_fields['P'][a])) {
1712 msg->cm_fields['P'][a] = ' ';
1717 msg->cm_fields['P'] = strdoop("unknown");
1721 strcpy(force_room, force);
1723 /* Learn about what's inside, because it's what's inside that counts */
1724 lprintf(9, "Learning what's inside\n");
1725 if (msg->cm_fields['M'] == NULL) {
1726 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1729 switch (msg->cm_format_type) {
1731 strcpy(content_type, "text/x-citadel-variformat");
1734 strcpy(content_type, "text/plain");
1737 strcpy(content_type, "text/plain");
1738 /* advance past header fields */
1739 mptr = msg->cm_fields['M'];
1742 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1743 safestrncpy(content_type, mptr,
1744 sizeof(content_type));
1745 strcpy(content_type, &content_type[14]);
1746 for (a = 0; a < strlen(content_type); ++a)
1747 if ((content_type[a] == ';')
1748 || (content_type[a] == ' ')
1749 || (content_type[a] == 13)
1750 || (content_type[a] == 10))
1751 content_type[a] = 0;
1758 /* Goto the correct room */
1759 lprintf(9, "Switching rooms\n");
1760 strcpy(hold_rm, CC->quickroom.QRname);
1761 strcpy(actual_rm, CC->quickroom.QRname);
1762 if (recps != NULL) {
1763 strcpy(actual_rm, SENTITEMS);
1766 /* If the user is a twit, move to the twit room for posting */
1767 lprintf(9, "Handling twit stuff\n");
1769 if (CC->usersupp.axlevel == 2) {
1770 strcpy(hold_rm, actual_rm);
1771 strcpy(actual_rm, config.c_twitroom);
1775 /* ...or if this message is destined for Aide> then go there. */
1776 if (strlen(force_room) > 0) {
1777 strcpy(actual_rm, force_room);
1780 lprintf(9, "Possibly relocating\n");
1781 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1782 getroom(&CC->quickroom, actual_rm);
1786 * If this message has no O (room) field, generate one.
1788 if (msg->cm_fields['O'] == NULL) {
1789 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1792 /* Perform "before save" hooks (aborting if any return nonzero) */
1793 lprintf(9, "Performing before-save hooks\n");
1794 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1796 /* If this message has an Extended ID, perform replication checks */
1797 lprintf(9, "Performing replication checks\n");
1798 if (ReplicationChecks(msg) > 0) return(-1);
1800 /* Save it to disk */
1801 lprintf(9, "Saving to disk\n");
1802 newmsgid = send_message(msg, NULL);
1803 if (newmsgid <= 0L) return(-1);
1805 /* Write a supplemental message info record. This doesn't have to
1806 * be a critical section because nobody else knows about this message
1809 lprintf(9, "Creating MetaData record\n");
1810 memset(&smi, 0, sizeof(struct MetaData));
1811 smi.meta_msgnum = newmsgid;
1812 smi.meta_refcount = 0;
1813 safestrncpy(smi.meta_content_type, content_type, 64);
1816 /* Now figure out where to store the pointers */
1817 lprintf(9, "Storing pointers\n");
1819 /* If this is being done by the networker delivering a private
1820 * message, we want to BYPASS saving the sender's copy (because there
1821 * is no local sender; it would otherwise go to the Trashcan).
1823 if ((!CC->internal_pgm) || (recps == NULL)) {
1824 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1825 lprintf(3, "ERROR saving message pointer!\n");
1826 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1830 /* For internet mail, drop a copy in the outbound queue room */
1832 if (recps->num_internet > 0) {
1833 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1836 /* If other rooms are specified, drop them there too. */
1838 if (recps->num_room > 0)
1839 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1840 extract(recipient, recps->recp_room, i);
1841 lprintf(9, "Delivering to local room <%s>\n", recipient);
1842 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1845 /* Bump this user's messages posted counter. */
1846 lprintf(9, "Updating user\n");
1847 lgetuser(&CC->usersupp, CC->curr_user);
1848 CC->usersupp.posted = CC->usersupp.posted + 1;
1849 lputuser(&CC->usersupp);
1851 /* If this is private, local mail, make a copy in the
1852 * recipient's mailbox and bump the reference count.
1855 if (recps->num_local > 0)
1856 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1857 extract(recipient, recps->recp_local, i);
1858 lprintf(9, "Delivering private local mail to <%s>\n",
1860 if (getuser(&userbuf, recipient) == 0) {
1861 MailboxName(actual_rm, &userbuf, MAILROOM);
1862 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1865 lprintf(9, "No user <%s>\n", recipient);
1866 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1870 /* Perform "after save" hooks */
1871 lprintf(9, "Performing after-save hooks\n");
1872 PerformMessageHooks(msg, EVT_AFTERSAVE);
1874 /* For IGnet mail, we have to save a new copy into the spooler for
1875 * each recipient, with the R and D fields set to the recipient and
1876 * destination-node. This has two ugly side effects: all other
1877 * recipients end up being unlisted in this recipient's copy of the
1878 * message, and it has to deliver multiple messages to the same
1879 * node. We'll revisit this again in a year or so when everyone has
1880 * a network spool receiver that can handle the new style messages.
1883 if (recps->num_ignet > 0)
1884 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1885 extract(recipient, recps->recp_ignet, i);
1887 hold_R = msg->cm_fields['R'];
1888 hold_D = msg->cm_fields['D'];
1889 msg->cm_fields['R'] = mallok(SIZ);
1890 msg->cm_fields['D'] = mallok(SIZ);
1891 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1892 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1894 serialize_message(&smr, msg);
1897 "./network/spoolin/netmail.%04lx.%04x.%04x",
1898 (long) getpid(), CC->cs_pid, ++seqnum);
1899 network_fp = fopen(aaa, "wb+");
1900 if (network_fp != NULL) {
1901 fwrite(smr.ser, smr.len, 1, network_fp);
1907 phree(msg->cm_fields['R']);
1908 phree(msg->cm_fields['D']);
1909 msg->cm_fields['R'] = hold_R;
1910 msg->cm_fields['D'] = hold_D;
1913 /* Go back to the room we started from */
1914 lprintf(9, "Returning to original room\n");
1915 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1916 getroom(&CC->quickroom, hold_rm);
1918 /* For internet mail, generate delivery instructions.
1919 * Yes, this is recursive. Deal with it. Infinite recursion does
1920 * not happen because the delivery instructions message does not
1921 * contain a recipient.
1924 if (recps->num_internet > 0) {
1925 lprintf(9, "Generating delivery instructions\n");
1926 instr = mallok(SIZ * 2);
1928 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1930 SPOOLMIME, newmsgid, (long)time(NULL),
1931 msg->cm_fields['A'], msg->cm_fields['N']
1934 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
1935 extract(recipient, recps->recp_internet, i);
1936 sprintf(&instr[strlen(instr)],
1937 "remote|%s|0||\n", recipient);
1940 imsg = mallok(sizeof(struct CtdlMessage));
1941 memset(imsg, 0, sizeof(struct CtdlMessage));
1942 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1943 imsg->cm_anon_type = MES_NORMAL;
1944 imsg->cm_format_type = FMT_RFC822;
1945 imsg->cm_fields['A'] = strdoop("Citadel");
1946 imsg->cm_fields['M'] = instr;
1947 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
1948 CtdlFreeMessage(imsg);
1957 * Convenience function for generating small administrative messages.
1959 void quickie_message(char *from, char *to, char *room, char *text)
1961 struct CtdlMessage *msg;
1963 msg = mallok(sizeof(struct CtdlMessage));
1964 memset(msg, 0, sizeof(struct CtdlMessage));
1965 msg->cm_magic = CTDLMESSAGE_MAGIC;
1966 msg->cm_anon_type = MES_NORMAL;
1967 msg->cm_format_type = 0;
1968 msg->cm_fields['A'] = strdoop(from);
1969 msg->cm_fields['O'] = strdoop(room);
1970 msg->cm_fields['N'] = strdoop(NODENAME);
1972 msg->cm_fields['R'] = strdoop(to);
1973 msg->cm_fields['M'] = strdoop(text);
1975 CtdlSubmitMsg(msg, NULL, room);
1976 CtdlFreeMessage(msg);
1977 syslog(LOG_NOTICE, text);
1983 * Back end function used by make_message() and similar functions
1985 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1986 size_t maxlen, /* maximum message length */
1987 char *exist /* if non-null, append to it;
1988 exist is ALWAYS freed */
1992 size_t message_len = 0;
1993 size_t buffer_len = 0;
1997 if (exist == NULL) {
2001 m = reallok(exist, strlen(exist) + 4096);
2002 if (m == NULL) phree(exist);
2005 /* flush the input if we have nowhere to store it */
2007 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2011 /* otherwise read it into memory */
2017 /* read in the lines of message text one by one */
2018 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2020 /* strip trailing newline type stuff */
2021 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2022 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2024 linelen = strlen(buf);
2026 /* augment the buffer if we have to */
2027 if ((message_len + linelen + 2) > buffer_len) {
2028 lprintf(9, "realloking\n");
2029 ptr = reallok(m, (buffer_len * 2) );
2030 if (ptr == NULL) { /* flush if can't allocate */
2031 while ( (client_gets(buf)>0) &&
2032 strcmp(buf, terminator)) ;;
2035 buffer_len = (buffer_len * 2);
2037 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2041 /* Add the new line to the buffer. NOTE: this loop must avoid
2042 * using functions like strcat() and strlen() because they
2043 * traverse the entire buffer upon every call, and doing that
2044 * for a multi-megabyte message slows it down beyond usability.
2046 strcpy(&m[message_len], buf);
2047 m[message_len + linelen] = '\n';
2048 m[message_len + linelen + 1] = 0;
2049 message_len = message_len + linelen + 1;
2051 /* if we've hit the max msg length, flush the rest */
2052 if (message_len >= maxlen) {
2053 while ( (client_gets(buf)>0)
2054 && strcmp(buf, terminator)) ;;
2065 * Build a binary message to be saved on disk.
2068 static struct CtdlMessage *make_message(
2069 struct usersupp *author, /* author's usersupp structure */
2070 char *recipient, /* NULL if it's not mail */
2071 char *room, /* room where it's going */
2072 int type, /* see MES_ types in header file */
2073 int format_type, /* variformat, plain text, MIME... */
2074 char *fake_name /* who we're masquerading as */
2076 char dest_node[SIZ];
2078 struct CtdlMessage *msg;
2080 msg = mallok(sizeof(struct CtdlMessage));
2081 memset(msg, 0, sizeof(struct CtdlMessage));
2082 msg->cm_magic = CTDLMESSAGE_MAGIC;
2083 msg->cm_anon_type = type;
2084 msg->cm_format_type = format_type;
2086 /* Don't confuse the poor folks if it's not routed mail. */
2087 strcpy(dest_node, "");
2091 sprintf(buf, "cit%ld", author->usernum); /* Path */
2092 msg->cm_fields['P'] = strdoop(buf);
2094 sprintf(buf, "%ld", (long)time(NULL)); /* timestamp */
2095 msg->cm_fields['T'] = strdoop(buf);
2097 if (fake_name[0]) /* author */
2098 msg->cm_fields['A'] = strdoop(fake_name);
2100 msg->cm_fields['A'] = strdoop(author->fullname);
2102 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2103 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2106 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2109 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2110 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2112 if (recipient[0] != 0) {
2113 msg->cm_fields['R'] = strdoop(recipient);
2115 if (dest_node[0] != 0) {
2116 msg->cm_fields['D'] = strdoop(dest_node);
2119 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2120 config.c_maxmsglen, NULL);
2127 * Check to see whether we have permission to post a message in the current
2128 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2129 * returns 0 on success.
2131 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2133 if (!(CC->logged_in)) {
2134 sprintf(errmsgbuf, "Not logged in.");
2135 return (ERROR + NOT_LOGGED_IN);
2138 if ((CC->usersupp.axlevel < 2)
2139 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2140 sprintf(errmsgbuf, "Need to be validated to enter "
2141 "(except in %s> to sysop)", MAILROOM);
2142 return (ERROR + HIGHER_ACCESS_REQUIRED);
2145 if ((CC->usersupp.axlevel < 4)
2146 && (CC->quickroom.QRflags & QR_NETWORK)) {
2147 sprintf(errmsgbuf, "Need net privileges to enter here.");
2148 return (ERROR + HIGHER_ACCESS_REQUIRED);
2151 if ((CC->usersupp.axlevel < 6)
2152 && (CC->quickroom.QRflags & QR_READONLY)) {
2153 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2154 return (ERROR + HIGHER_ACCESS_REQUIRED);
2157 strcpy(errmsgbuf, "Ok");
2163 * Validate recipients, count delivery types and errors, and handle aliasing
2164 * FIXME check for dupes!!!!!
2166 struct recptypes *validate_recipients(char *recipients) {
2167 struct recptypes *ret;
2168 char this_recp[SIZ];
2174 struct usersupp tempUS;
2177 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2178 if (ret == NULL) return(NULL);
2179 memset(ret, 0, sizeof(struct recptypes));
2182 ret->num_internet = 0;
2186 if (recipients == NULL) {
2189 else if (strlen(recipients) == 0) {
2193 /* Change all valid separator characters to commas */
2194 for (i=0; i<strlen(recipients); ++i) {
2195 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2196 recipients[i] = ',';
2201 num_recps = num_tokens(recipients, ',');
2204 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2205 extract_token(this_recp, recipients, i, ',');
2207 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2208 mailtype = alias(this_recp);
2212 if (!strcasecmp(this_recp, "sysop")) {
2214 strcpy(this_recp, AIDEROOM);
2215 if (strlen(ret->recp_room) > 0) {
2216 strcat(ret->recp_room, "|");
2218 strcat(ret->recp_room, this_recp);
2220 else if (getuser(&tempUS, this_recp) == 0) {
2222 strcpy(this_recp, tempUS.fullname);
2223 if (strlen(ret->recp_local) > 0) {
2224 strcat(ret->recp_local, "|");
2226 strcat(ret->recp_local, this_recp);
2234 ++ret->num_internet;
2235 if (strlen(ret->recp_internet) > 0) {
2236 strcat(ret->recp_internet, "|");
2238 strcat(ret->recp_internet, this_recp);
2242 if (strlen(ret->recp_ignet) > 0) {
2243 strcat(ret->recp_ignet, "|");
2245 strcat(ret->recp_ignet, this_recp);
2253 if (strlen(ret->errormsg) == 0) {
2255 "Invalid recipient: %s",
2262 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2263 strcat(ret->errormsg, append);
2267 if (strlen(ret->display_recp) == 0) {
2268 strcpy(append, this_recp);
2271 sprintf(append, ", %s", this_recp);
2273 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2274 strcat(ret->display_recp, append);
2279 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2280 ret->num_room + ret->num_error) == 0) {
2282 strcpy(ret->errormsg, "No recipients specified.");
2291 * message entry - mode 0 (normal)
2293 void cmd_ent0(char *entargs)
2297 char masquerade_as[SIZ];
2299 int format_type = 0;
2300 char newusername[SIZ];
2301 struct CtdlMessage *msg;
2305 struct recptypes *valid = NULL;
2307 post = extract_int(entargs, 0);
2308 extract(recp, entargs, 1);
2309 anon_flag = extract_int(entargs, 2);
2310 format_type = extract_int(entargs, 3);
2312 /* first check to make sure the request is valid. */
2314 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg);
2316 cprintf("%d %s\n", err, errmsg);
2320 /* Check some other permission type things. */
2323 if (CC->usersupp.axlevel < 6) {
2324 cprintf("%d You don't have permission to masquerade.\n",
2325 ERROR + HIGHER_ACCESS_REQUIRED);
2328 extract(newusername, entargs, 4);
2329 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2330 safestrncpy(CC->fake_postname, newusername,
2331 sizeof(CC->fake_postname) );
2332 cprintf("%d ok\n", OK);
2335 CC->cs_flags |= CS_POSTING;
2337 if (CC->quickroom.QRflags & QR_MAILBOX) {
2338 if (CC->usersupp.axlevel < 2) {
2339 strcpy(recp, "sysop");
2342 valid = validate_recipients(recp);
2343 if (valid->num_error > 0) {
2345 ERROR + NO_SUCH_USER, valid->errormsg);
2350 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2351 && (CC->usersupp.axlevel < 4) ) {
2352 cprintf("%d Higher access required for network mail.\n",
2353 ERROR + HIGHER_ACCESS_REQUIRED);
2358 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2359 && ((CC->usersupp.flags & US_INTERNET) == 0)
2360 && (!CC->internal_pgm)) {
2361 cprintf("%d You don't have access to Internet mail.\n",
2362 ERROR + HIGHER_ACCESS_REQUIRED);
2369 /* Is this a room which has anonymous-only or anonymous-option? */
2370 anonymous = MES_NORMAL;
2371 if (CC->quickroom.QRflags & QR_ANONONLY) {
2372 anonymous = MES_ANONONLY;
2374 if (CC->quickroom.QRflags & QR_ANONOPT) {
2375 if (anon_flag == 1) { /* only if the user requested it */
2376 anonymous = MES_ANONOPT;
2380 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2384 /* If we're only checking the validity of the request, return
2385 * success without creating the message.
2388 cprintf("%d %s\n", OK,
2389 ((valid != NULL) ? valid->display_recp : "") );
2394 /* Handle author masquerading */
2395 if (CC->fake_postname[0]) {
2396 strcpy(masquerade_as, CC->fake_postname);
2398 else if (CC->fake_username[0]) {
2399 strcpy(masquerade_as, CC->fake_username);
2402 strcpy(masquerade_as, "");
2405 /* Read in the message from the client. */
2406 cprintf("%d send message\n", SEND_LISTING);
2407 msg = make_message(&CC->usersupp, recp,
2408 CC->quickroom.QRname, anonymous, format_type, masquerade_as);
2411 CtdlSubmitMsg(msg, valid, "");
2412 CtdlFreeMessage(msg);
2414 CC->fake_postname[0] = '\0';
2422 * API function to delete messages which match a set of criteria
2423 * (returns the actual number of messages deleted)
2425 int CtdlDeleteMessages(char *room_name, /* which room */
2426 long dmsgnum, /* or "0" for any */
2427 char *content_type /* or "" for any */
2431 struct quickroom qrbuf;
2432 struct cdbdata *cdbfr;
2433 long *msglist = NULL;
2436 int num_deleted = 0;
2438 struct MetaData smi;
2440 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2441 room_name, dmsgnum, content_type);
2443 /* get room record, obtaining a lock... */
2444 if (lgetroom(&qrbuf, room_name) != 0) {
2445 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2447 return (0); /* room not found */
2449 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2451 if (cdbfr != NULL) {
2452 msglist = mallok(cdbfr->len);
2453 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2454 num_msgs = cdbfr->len / sizeof(long);
2458 for (i = 0; i < num_msgs; ++i) {
2461 /* Set/clear a bit for each criterion */
2463 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2464 delete_this |= 0x01;
2466 if (strlen(content_type) == 0) {
2467 delete_this |= 0x02;
2469 GetMetaData(&smi, msglist[i]);
2470 if (!strcasecmp(smi.meta_content_type,
2472 delete_this |= 0x02;
2476 /* Delete message only if all bits are set */
2477 if (delete_this == 0x03) {
2478 AdjRefCount(msglist[i], -1);
2484 num_msgs = sort_msglist(msglist, num_msgs);
2485 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2486 msglist, (num_msgs * sizeof(long)));
2488 qrbuf.QRhighest = msglist[num_msgs - 1];
2492 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2493 return (num_deleted);
2499 * Check whether the current user has permission to delete messages from
2500 * the current room (returns 1 for yes, 0 for no)
2502 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2503 getuser(&CC->usersupp, CC->curr_user);
2504 if ((CC->usersupp.axlevel < 6)
2505 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2506 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2507 && (!(CC->internal_pgm))) {
2516 * Delete message from current room
2518 void cmd_dele(char *delstr)
2523 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2524 cprintf("%d Higher access required.\n",
2525 ERROR + HIGHER_ACCESS_REQUIRED);
2528 delnum = extract_long(delstr, 0);
2530 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2533 cprintf("%d %d message%s deleted.\n", OK,
2534 num_deleted, ((num_deleted != 1) ? "s" : ""));
2536 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2542 * Back end API function for moves and deletes
2544 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2547 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2548 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2549 if (err != 0) return(err);
2557 * move or copy a message to another room
2559 void cmd_move(char *args)
2563 struct quickroom qtemp;
2567 num = extract_long(args, 0);
2568 extract(targ, args, 1);
2569 targ[ROOMNAMELEN - 1] = 0;
2570 is_copy = extract_int(args, 2);
2572 getuser(&CC->usersupp, CC->curr_user);
2573 if ((CC->usersupp.axlevel < 6)
2574 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2575 cprintf("%d Higher access required.\n",
2576 ERROR + HIGHER_ACCESS_REQUIRED);
2580 if (getroom(&qtemp, targ) != 0) {
2581 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2585 err = CtdlCopyMsgToRoom(num, targ);
2587 cprintf("%d Cannot store message in %s: error %d\n",
2592 /* Now delete the message from the source room,
2593 * if this is a 'move' rather than a 'copy' operation.
2596 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2599 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2605 * GetMetaData() - Get the supplementary record for a message
2607 void GetMetaData(struct MetaData *smibuf, long msgnum)
2610 struct cdbdata *cdbsmi;
2613 memset(smibuf, 0, sizeof(struct MetaData));
2614 smibuf->meta_msgnum = msgnum;
2615 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2617 /* Use the negative of the message number for its supp record index */
2618 TheIndex = (0L - msgnum);
2620 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2621 if (cdbsmi == NULL) {
2622 return; /* record not found; go with defaults */
2624 memcpy(smibuf, cdbsmi->ptr,
2625 ((cdbsmi->len > sizeof(struct MetaData)) ?
2626 sizeof(struct MetaData) : cdbsmi->len));
2633 * PutMetaData() - (re)write supplementary record for a message
2635 void PutMetaData(struct MetaData *smibuf)
2639 /* Use the negative of the message number for the metadata db index */
2640 TheIndex = (0L - smibuf->meta_msgnum);
2642 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2643 smibuf->meta_msgnum, smibuf->meta_refcount);
2645 cdb_store(CDB_MSGMAIN,
2646 &TheIndex, sizeof(long),
2647 smibuf, sizeof(struct MetaData));
2652 * AdjRefCount - change the reference count for a message;
2653 * delete the message if it reaches zero
2655 void AdjRefCount(long msgnum, int incr)
2658 struct MetaData smi;
2661 /* This is a *tight* critical section; please keep it that way, as
2662 * it may get called while nested in other critical sections.
2663 * Complicating this any further will surely cause deadlock!
2665 begin_critical_section(S_SUPPMSGMAIN);
2666 GetMetaData(&smi, msgnum);
2667 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2668 msgnum, smi.meta_refcount);
2669 smi.meta_refcount += incr;
2671 end_critical_section(S_SUPPMSGMAIN);
2672 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2673 msgnum, smi.meta_refcount);
2675 /* If the reference count is now zero, delete the message
2676 * (and its supplementary record as well).
2678 if (smi.meta_refcount == 0) {
2679 lprintf(9, "Deleting message <%ld>\n", msgnum);
2681 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2683 /* We have to delete the metadata record too! */
2684 delnum = (0L - msgnum);
2685 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2690 * Write a generic object to this room
2692 * Note: this could be much more efficient. Right now we use two temporary
2693 * files, and still pull the message into memory as with all others.
2695 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2696 char *content_type, /* MIME type of this object */
2697 char *tempfilename, /* Where to fetch it from */
2698 struct usersupp *is_mailbox, /* Mailbox room? */
2699 int is_binary, /* Is encoding necessary? */
2700 int is_unique, /* Del others of this type? */
2701 unsigned int flags /* Internal save flags */
2706 char filename[PATH_MAX];
2709 struct quickroom qrbuf;
2710 char roomname[ROOMNAMELEN];
2711 struct CtdlMessage *msg;
2714 if (is_mailbox != NULL)
2715 MailboxName(roomname, is_mailbox, req_room);
2717 safestrncpy(roomname, req_room, sizeof(roomname));
2718 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2720 strcpy(filename, tmpnam(NULL));
2721 fp = fopen(filename, "w");
2725 tempfp = fopen(tempfilename, "r");
2726 if (tempfp == NULL) {
2732 fprintf(fp, "Content-type: %s\n", content_type);
2733 lprintf(9, "Content-type: %s\n", content_type);
2735 if (is_binary == 0) {
2736 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2737 while (ch = getc(tempfp), ch > 0)
2743 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2746 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2747 tempfilename, filename);
2751 lprintf(9, "Allocating\n");
2752 msg = mallok(sizeof(struct CtdlMessage));
2753 memset(msg, 0, sizeof(struct CtdlMessage));
2754 msg->cm_magic = CTDLMESSAGE_MAGIC;
2755 msg->cm_anon_type = MES_NORMAL;
2756 msg->cm_format_type = 4;
2757 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2758 msg->cm_fields['O'] = strdoop(req_room);
2759 msg->cm_fields['N'] = strdoop(config.c_nodename);
2760 msg->cm_fields['H'] = strdoop(config.c_humannode);
2761 msg->cm_flags = flags;
2763 lprintf(9, "Loading\n");
2764 fp = fopen(filename, "rb");
2765 fseek(fp, 0L, SEEK_END);
2768 msg->cm_fields['M'] = mallok(len);
2769 fread(msg->cm_fields['M'], len, 1, fp);
2773 /* Create the requested room if we have to. */
2774 if (getroom(&qrbuf, roomname) != 0) {
2775 create_room(roomname,
2776 ( (is_mailbox != NULL) ? 5 : 3 ),
2779 /* If the caller specified this object as unique, delete all
2780 * other objects of this type that are currently in the room.
2783 lprintf(9, "Deleted %d other msgs of this type\n",
2784 CtdlDeleteMessages(roomname, 0L, content_type));
2786 /* Now write the data */
2787 CtdlSubmitMsg(msg, NULL, roomname);
2788 CtdlFreeMessage(msg);
2796 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2797 config_msgnum = msgnum;
2801 char *CtdlGetSysConfig(char *sysconfname) {
2802 char hold_rm[ROOMNAMELEN];
2805 struct CtdlMessage *msg;
2808 strcpy(hold_rm, CC->quickroom.QRname);
2809 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2810 getroom(&CC->quickroom, hold_rm);
2815 /* We want the last (and probably only) config in this room */
2816 begin_critical_section(S_CONFIG);
2817 config_msgnum = (-1L);
2818 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2819 CtdlGetSysConfigBackend, NULL);
2820 msgnum = config_msgnum;
2821 end_critical_section(S_CONFIG);
2827 msg = CtdlFetchMessage(msgnum);
2829 conf = strdoop(msg->cm_fields['M']);
2830 CtdlFreeMessage(msg);
2837 getroom(&CC->quickroom, hold_rm);
2839 if (conf != NULL) do {
2840 extract_token(buf, conf, 0, '\n');
2841 strcpy(conf, &conf[strlen(buf)+1]);
2842 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2847 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2848 char temp[PATH_MAX];
2851 strcpy(temp, tmpnam(NULL));
2853 fp = fopen(temp, "w");
2854 if (fp == NULL) return;
2855 fprintf(fp, "%s", sysconfdata);
2858 /* this handy API function does all the work for us */
2859 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);