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! */
270 * Manipulate the "seen msgs" string.
272 void CtdlSetSeen(long target_msgnum, int target_setting) {
274 struct cdbdata *cdbfr;
284 /* Learn about the user and room in question */
286 getuser(&CC->usersupp, CC->curr_user);
287 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
289 /* Load the message list */
290 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
292 msglist = mallok(cdbfr->len);
293 memcpy(msglist, cdbfr->ptr, cdbfr->len);
294 num_msgs = cdbfr->len / sizeof(long);
297 return; /* No messages at all? No further action. */
300 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
303 for (i=0; i<num_msgs; ++i) {
306 if (msglist[i] == target_msgnum) {
307 is_seen = target_setting;
310 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
316 if (lo < 0L) lo = msglist[i];
319 if ( ((is_seen == 0) && (was_seen == 1))
320 || ((is_seen == 1) && (i == num_msgs-1)) ) {
321 if ( (strlen(newseen) + 20) > SIZ) {
322 strcpy(newseen, &newseen[20]);
325 if (strlen(newseen) > 0) strcat(newseen, ",");
327 sprintf(&newseen[strlen(newseen)], "%ld", lo);
330 sprintf(&newseen[strlen(newseen)], "%ld:%ld",
339 safestrncpy(vbuf.v_seen, newseen, SIZ);
340 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
342 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
347 * API function to perform an operation for each qualifying message in the
348 * current room. (Returns the number of messages processed.)
350 int CtdlForEachMessage(int mode, long ref,
351 int moderation_level,
353 struct CtdlMessage *compare,
354 void (*CallBack) (long, void *),
360 struct cdbdata *cdbfr;
361 long *msglist = NULL;
363 int num_processed = 0;
365 struct SuppMsgInfo smi;
366 struct CtdlMessage *msg;
369 /* Learn about the user and room in question */
371 getuser(&CC->usersupp, CC->curr_user);
372 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
374 /* Load the message list */
375 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
377 msglist = mallok(cdbfr->len);
378 memcpy(msglist, cdbfr->ptr, cdbfr->len);
379 num_msgs = cdbfr->len / sizeof(long);
382 return 0; /* No messages at all? No further action. */
387 * Now begin the traversal.
389 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
390 GetSuppMsgInfo(&smi, msglist[a]);
392 /* Filter out messages that are moderated below the level
393 * currently being viewed at.
395 if (smi.smi_mod < moderation_level) {
399 /* If the caller is looking for a specific MIME type, filter
400 * out all messages which are not of the type requested.
402 if (content_type != NULL) if (strlen(content_type) > 0) {
403 if (strcasecmp(smi.smi_content_type, content_type)) {
409 num_msgs = sort_msglist(msglist, num_msgs);
411 /* If a template was supplied, filter out the messages which
412 * don't match. (This could induce some delays!)
415 if (compare != NULL) {
416 for (a = 0; a < num_msgs; ++a) {
417 msg = CtdlFetchMessage(msglist[a]);
419 if (CtdlMsgCmp(msg, compare)) {
422 CtdlFreeMessage(msg);
430 * Now iterate through the message list, according to the
431 * criteria supplied by the caller.
434 for (a = 0; a < num_msgs; ++a) {
435 thismsg = msglist[a];
436 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
441 || ((mode == MSGS_OLD) && (is_seen))
442 || ((mode == MSGS_NEW) && (!is_seen))
443 /* FIXME handle lastold mode */
444 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
445 || ((mode == MSGS_FIRST) && (a < ref))
446 || ((mode == MSGS_GT) && (thismsg > ref))
447 || ((mode == MSGS_EQ) && (thismsg == ref))
450 if (CallBack) CallBack(thismsg, userdata);
454 phree(msglist); /* Clean up */
455 return num_processed;
461 * cmd_msgs() - get list of message #'s in this room
462 * implements the MSGS server command using CtdlForEachMessage()
464 void cmd_msgs(char *cmdbuf)
473 int with_template = 0;
474 struct CtdlMessage *template = NULL;
476 extract(which, cmdbuf, 0);
477 cm_ref = extract_int(cmdbuf, 1);
478 with_template = extract_int(cmdbuf, 2);
482 if (!strncasecmp(which, "OLD", 3))
484 else if (!strncasecmp(which, "NEW", 3))
486 else if (!strncasecmp(which, "FIRST", 5))
488 else if (!strncasecmp(which, "LAST", 4))
490 else if (!strncasecmp(which, "GT", 2))
493 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
494 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
499 cprintf("%d Send template then receive message list\n",
501 template = (struct CtdlMessage *)
502 mallok(sizeof(struct CtdlMessage));
503 memset(template, 0, sizeof(struct CtdlMessage));
504 while(client_gets(buf), strcmp(buf,"000")) {
505 extract(tfield, buf, 0);
506 extract(tvalue, buf, 1);
507 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
508 if (!strcasecmp(tfield, msgkeys[i])) {
509 template->cm_fields[i] =
516 cprintf("%d Message list...\n", LISTING_FOLLOWS);
519 CtdlForEachMessage(mode, cm_ref,
520 CC->usersupp.moderation_filter,
521 NULL, template, simple_listing, NULL);
522 if (template != NULL) CtdlFreeMessage(template);
530 * help_subst() - support routine for help file viewer
532 void help_subst(char *strbuf, char *source, char *dest)
537 while (p = pattern2(strbuf, source), (p >= 0)) {
538 strcpy(workbuf, &strbuf[p + strlen(source)]);
539 strcpy(&strbuf[p], dest);
540 strcat(strbuf, workbuf);
545 void do_help_subst(char *buffer)
549 help_subst(buffer, "^nodename", config.c_nodename);
550 help_subst(buffer, "^humannode", config.c_humannode);
551 help_subst(buffer, "^fqdn", config.c_fqdn);
552 help_subst(buffer, "^username", CC->usersupp.fullname);
553 sprintf(buf2, "%ld", CC->usersupp.usernum);
554 help_subst(buffer, "^usernum", buf2);
555 help_subst(buffer, "^sysadm", config.c_sysadm);
556 help_subst(buffer, "^variantname", CITADEL);
557 sprintf(buf2, "%d", config.c_maxsessions);
558 help_subst(buffer, "^maxsessions", buf2);
564 * memfmout() - Citadel text formatter and paginator.
565 * Although the original purpose of this routine was to format
566 * text to the reader's screen width, all we're really using it
567 * for here is to format text out to 80 columns before sending it
568 * to the client. The client software may reformat it again.
571 int width, /* screen width to use */
572 char *mptr, /* where are we going to get our text from? */
573 char subst, /* nonzero if we should do substitutions */
574 char *nl) /* string to terminate lines with */
586 c = 1; /* c is the current pos */
590 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
592 buffer[strlen(buffer) + 1] = 0;
593 buffer[strlen(buffer)] = ch;
596 if (buffer[0] == '^')
597 do_help_subst(buffer);
599 buffer[strlen(buffer) + 1] = 0;
601 strcpy(buffer, &buffer[1]);
609 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
611 if (((old == 13) || (old == 10)) && (isspace(real))) {
619 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
620 cprintf("%s%s", nl, aaa);
629 if ((strlen(aaa) + c) > (width - 5)) {
638 if ((ch == 13) || (ch == 10)) {
639 cprintf("%s%s", aaa, nl);
646 cprintf("%s%s", aaa, nl);
652 * Callback function for mime parser that simply lists the part
654 void list_this_part(char *name, char *filename, char *partnum, char *disp,
655 void *content, char *cbtype, size_t length, char *encoding,
659 cprintf("part=%s|%s|%s|%s|%s|%d\n",
660 name, filename, partnum, disp, cbtype, length);
665 * Callback function for mime parser that opens a section for downloading
667 void mime_download(char *name, char *filename, char *partnum, char *disp,
668 void *content, char *cbtype, size_t length, char *encoding,
672 /* Silently go away if there's already a download open... */
673 if (CC->download_fp != NULL)
676 /* ...or if this is not the desired section */
677 if (strcasecmp(desired_section, partnum))
680 CC->download_fp = tmpfile();
681 if (CC->download_fp == NULL)
684 fwrite(content, length, 1, CC->download_fp);
685 fflush(CC->download_fp);
686 rewind(CC->download_fp);
688 OpenCmdResult(filename, cbtype);
694 * Load a message from disk into memory.
695 * This is used by CtdlOutputMsg() and other fetch functions.
697 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
698 * using the CtdlMessageFree() function.
700 struct CtdlMessage *CtdlFetchMessage(long msgnum)
702 struct cdbdata *dmsgtext;
703 struct CtdlMessage *ret = NULL;
706 CIT_UBYTE field_header;
709 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
710 if (dmsgtext == NULL) {
713 mptr = dmsgtext->ptr;
715 /* Parse the three bytes that begin EVERY message on disk.
716 * The first is always 0xFF, the on-disk magic number.
717 * The second is the anonymous/public type byte.
718 * The third is the format type byte (vari, fixed, or MIME).
722 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
726 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
727 memset(ret, 0, sizeof(struct CtdlMessage));
729 ret->cm_magic = CTDLMESSAGE_MAGIC;
730 ret->cm_anon_type = *mptr++; /* Anon type byte */
731 ret->cm_format_type = *mptr++; /* Format type byte */
734 * The rest is zero or more arbitrary fields. Load them in.
735 * We're done when we encounter either a zero-length field or
736 * have just processed the 'M' (message text) field.
739 field_length = strlen(mptr);
740 if (field_length == 0)
742 field_header = *mptr++;
743 ret->cm_fields[field_header] = mallok(field_length);
744 strcpy(ret->cm_fields[field_header], mptr);
746 while (*mptr++ != 0); /* advance to next field */
748 } while ((field_length > 0) && (field_header != 'M'));
752 /* Always make sure there's something in the msg text field */
753 if (ret->cm_fields['M'] == NULL)
754 ret->cm_fields['M'] = strdoop("<no text>\n");
756 /* Perform "before read" hooks (aborting if any return nonzero) */
757 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
758 CtdlFreeMessage(ret);
767 * Returns 1 if the supplied pointer points to a valid Citadel message.
768 * If the pointer is NULL or the magic number check fails, returns 0.
770 int is_valid_message(struct CtdlMessage *msg) {
773 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
774 lprintf(3, "is_valid_message() -- self-check failed\n");
782 * 'Destructor' for struct CtdlMessage
784 void CtdlFreeMessage(struct CtdlMessage *msg)
788 if (is_valid_message(msg) == 0) return;
790 for (i = 0; i < 256; ++i)
791 if (msg->cm_fields[i] != NULL) {
792 phree(msg->cm_fields[i]);
795 msg->cm_magic = 0; /* just in case */
801 * Callback function for mime parser that wants to display text
803 void fixed_output(char *name, char *filename, char *partnum, char *disp,
804 void *content, char *cbtype, size_t length, char *encoding,
812 if (!strcasecmp(cbtype, "multipart/alternative")) {
813 strcpy(ma->prefix, partnum);
814 strcat(ma->prefix, ".");
820 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
822 && (ma->did_print == 1) ) {
823 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
829 if ( (!strcasecmp(cbtype, "text/plain"))
830 || (strlen(cbtype)==0) ) {
835 if (ch==10) cprintf("\r\n");
836 else cprintf("%c", ch);
839 else if (!strcasecmp(cbtype, "text/html")) {
840 ptr = html_to_ascii(content, 80, 0);
845 if (ch==10) cprintf("\r\n");
846 else cprintf("%c", ch);
850 else if (strncasecmp(cbtype, "multipart/", 10)) {
851 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
852 partnum, filename, cbtype, length);
858 * Get a message off disk. (returns om_* values found in msgbase.h)
861 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
862 int mode, /* how would you like that message? */
863 int headers_only, /* eschew the message body? */
864 int do_proto, /* do Citadel protocol responses? */
865 int crlf /* Use CRLF newlines instead of LF? */
867 struct CtdlMessage *TheMessage;
870 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
875 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
876 if (do_proto) cprintf("%d Not logged in.\n",
877 ERROR + NOT_LOGGED_IN);
878 return(om_not_logged_in);
881 /* FIXME ... small security issue
882 * We need to check to make sure the requested message is actually
883 * in the current room, and set msg_ok to 1 only if it is. This
884 * functionality is currently missing because I'm in a hurry to replace
885 * broken production code with nonbroken pre-beta code. :( -- ajc
888 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
890 return(om_no_such_msg);
895 * Fetch the message from disk
897 TheMessage = CtdlFetchMessage(msg_num);
898 if (TheMessage == NULL) {
899 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
901 return(om_no_such_msg);
904 retcode = CtdlOutputPreLoadedMsg(
905 TheMessage, msg_num, mode,
906 headers_only, do_proto, crlf);
908 CtdlFreeMessage(TheMessage);
914 * Get a message off disk. (returns om_* values found in msgbase.h)
917 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
919 int mode, /* how would you like that message? */
920 int headers_only, /* eschew the message body? */
921 int do_proto, /* do Citadel protocol responses? */
922 int crlf /* Use CRLF newlines instead of LF? */
928 char display_name[SIZ];
930 char *nl; /* newline string */
932 /* buffers needed for RFC822 translation */
942 sprintf(mid, "%ld", msg_num);
943 nl = (crlf ? "\r\n" : "\n");
945 if (!is_valid_message(TheMessage)) {
946 lprintf(1, "ERROR: invalid preloaded message for output\n");
947 return(om_no_such_msg);
950 /* Are we downloading a MIME component? */
951 if (mode == MT_DOWNLOAD) {
952 if (TheMessage->cm_format_type != FMT_RFC822) {
954 cprintf("%d This is not a MIME message.\n",
956 } else if (CC->download_fp != NULL) {
957 if (do_proto) cprintf(
958 "%d You already have a download open.\n",
961 /* Parse the message text component */
962 mptr = TheMessage->cm_fields['M'];
963 mime_parser(mptr, NULL,
964 *mime_download, NULL, NULL,
966 /* If there's no file open by this time, the requested
967 * section wasn't found, so print an error
969 if (CC->download_fp == NULL) {
970 if (do_proto) cprintf(
971 "%d Section %s not found.\n",
972 ERROR + FILE_NOT_FOUND,
976 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
979 /* now for the user-mode message reading loops */
980 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
982 /* Tell the client which format type we're using. If this is a
983 * MIME message, *lie* about it and tell the user it's fixed-format.
985 if (mode == MT_CITADEL) {
986 if (TheMessage->cm_format_type == FMT_RFC822) {
987 if (do_proto) cprintf("type=1\n");
990 if (do_proto) cprintf("type=%d\n",
991 TheMessage->cm_format_type);
995 /* nhdr=yes means that we're only displaying headers, no body */
996 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
997 if (do_proto) cprintf("nhdr=yes\n");
1000 /* begin header processing loop for Citadel message format */
1002 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1004 strcpy(display_name, "<unknown>");
1005 if (TheMessage->cm_fields['A']) {
1006 strcpy(buf, TheMessage->cm_fields['A']);
1007 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1008 if (TheMessage->cm_anon_type == MES_ANON)
1009 strcpy(display_name, "****");
1010 else if (TheMessage->cm_anon_type == MES_AN2)
1011 strcpy(display_name, "anonymous");
1013 strcpy(display_name, buf);
1014 if ((is_room_aide())
1015 && ((TheMessage->cm_anon_type == MES_ANON)
1016 || (TheMessage->cm_anon_type == MES_AN2))) {
1017 sprintf(&display_name[strlen(display_name)],
1022 strcpy(allkeys, FORDER);
1023 for (i=0; i<strlen(allkeys); ++i) {
1024 k = (int) allkeys[i];
1026 if (TheMessage->cm_fields[k] != NULL) {
1028 if (do_proto) cprintf("%s=%s\n",
1033 if (do_proto) cprintf("%s=%s\n",
1035 TheMessage->cm_fields[k]
1044 /* begin header processing loop for RFC822 transfer format */
1049 strcpy(snode, NODENAME);
1050 strcpy(lnode, HUMANNODE);
1051 if (mode == MT_RFC822) {
1052 cprintf("X-UIDL: %ld%s", msg_num, nl);
1053 for (i = 0; i < 256; ++i) {
1054 if (TheMessage->cm_fields[i]) {
1055 mptr = TheMessage->cm_fields[i];
1058 strcpy(luser, mptr);
1059 strcpy(suser, mptr);
1062 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1063 into thinking that mail messages are newsgroup messages instead. When we
1064 add NNTP support back into Citadel we'll have to add code to only output
1065 this field when appropriate.
1066 else if (i == 'P') {
1067 cprintf("Path: %s%s", mptr, nl);
1071 cprintf("Subject: %s%s", mptr, nl);
1075 strcpy(lnode, mptr);
1077 cprintf("X-Citadel-Room: %s%s",
1080 strcpy(snode, mptr);
1082 cprintf("To: %s%s", mptr, nl);
1083 else if (i == 'T') {
1084 datestring(datestamp, atol(mptr),
1085 DATESTRING_RFC822 );
1086 cprintf("Date: %s%s", datestamp, nl);
1092 for (i=0; i<strlen(suser); ++i) {
1093 suser[i] = tolower(suser[i]);
1094 if (!isalnum(suser[i])) suser[i]='_';
1097 if (mode == MT_RFC822) {
1098 if (!strcasecmp(snode, NODENAME)) {
1099 strcpy(snode, FQDN);
1102 /* Construct a fun message id */
1103 cprintf("Message-ID: <%s", mid);
1104 if (strchr(mid, '@')==NULL) {
1105 cprintf("@%s", snode);
1109 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1111 if (strlen(fuser) > 0) {
1112 cprintf("From: %s (%s)%s", fuser, luser, nl);
1115 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1118 cprintf("Organization: %s%s", lnode, nl);
1121 /* end header processing loop ... at this point, we're in the text */
1123 mptr = TheMessage->cm_fields['M'];
1125 /* Tell the client about the MIME parts in this message */
1126 if (TheMessage->cm_format_type == FMT_RFC822) {
1127 if (mode == MT_CITADEL) {
1128 mime_parser(mptr, NULL,
1129 *list_this_part, NULL, NULL,
1132 else if (mode == MT_MIME) { /* list parts only */
1133 mime_parser(mptr, NULL,
1134 *list_this_part, NULL, NULL,
1136 if (do_proto) cprintf("000\n");
1139 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1140 /* FIXME ... we have to put some code in here to avoid
1141 * printing duplicate header information when both
1142 * Citadel and RFC822 headers exist. Preference should
1143 * probably be given to the RFC822 headers.
1145 while (ch=*(mptr++), ch!=0) {
1147 else if (ch==10) cprintf("%s", nl);
1148 else cprintf("%c", ch);
1150 if (do_proto) cprintf("000\n");
1156 if (do_proto) cprintf("000\n");
1160 /* signify start of msg text */
1161 if (mode == MT_CITADEL)
1162 if (do_proto) cprintf("text\n");
1163 if (mode == MT_RFC822) {
1164 if (TheMessage->cm_fields['U'] == NULL) {
1165 cprintf("Subject: (no subject)%s", nl);
1170 /* If the format type on disk is 1 (fixed-format), then we want
1171 * everything to be output completely literally ... regardless of
1172 * what message transfer format is in use.
1174 if (TheMessage->cm_format_type == FMT_FIXED) {
1176 while (ch = *mptr++, ch > 0) {
1179 if ((ch == 10) || (strlen(buf) > 250)) {
1180 cprintf("%s%s", buf, nl);
1183 buf[strlen(buf) + 1] = 0;
1184 buf[strlen(buf)] = ch;
1187 if (strlen(buf) > 0)
1188 cprintf("%s%s", buf, nl);
1191 /* If the message on disk is format 0 (Citadel vari-format), we
1192 * output using the formatter at 80 columns. This is the final output
1193 * form if the transfer format is RFC822, but if the transfer format
1194 * is Citadel proprietary, it'll still work, because the indentation
1195 * for new paragraphs is correct and the client will reformat the
1196 * message to the reader's screen width.
1198 if (TheMessage->cm_format_type == FMT_CITADEL) {
1199 memfmout(80, mptr, 0, nl);
1202 /* If the message on disk is format 4 (MIME), we've gotta hand it
1203 * off to the MIME parser. The client has already been told that
1204 * this message is format 1 (fixed format), so the callback function
1205 * we use will display those parts as-is.
1207 if (TheMessage->cm_format_type == FMT_RFC822) {
1208 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1209 memset(ma, 0, sizeof(struct ma_info));
1210 mime_parser(mptr, NULL,
1211 *fixed_output, NULL, NULL,
1215 /* now we're done */
1216 if (do_proto) cprintf("000\n");
1223 * display a message (mode 0 - Citadel proprietary)
1225 void cmd_msg0(char *cmdbuf)
1228 int headers_only = 0;
1230 msgid = extract_long(cmdbuf, 0);
1231 headers_only = extract_int(cmdbuf, 1);
1233 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1239 * display a message (mode 2 - RFC822)
1241 void cmd_msg2(char *cmdbuf)
1244 int headers_only = 0;
1246 msgid = extract_long(cmdbuf, 0);
1247 headers_only = extract_int(cmdbuf, 1);
1249 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1255 * display a message (mode 3 - IGnet raw format - internal programs only)
1257 void cmd_msg3(char *cmdbuf)
1260 struct CtdlMessage *msg;
1263 if (CC->internal_pgm == 0) {
1264 cprintf("%d This command is for internal programs only.\n",
1269 msgnum = extract_long(cmdbuf, 0);
1270 msg = CtdlFetchMessage(msgnum);
1272 cprintf("%d Message %ld not found.\n",
1277 serialize_message(&smr, msg);
1278 CtdlFreeMessage(msg);
1281 cprintf("%d Unable to serialize message\n",
1282 ERROR+INTERNAL_ERROR);
1286 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1287 client_write(smr.ser, smr.len);
1294 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1296 void cmd_msg4(char *cmdbuf)
1300 msgid = extract_long(cmdbuf, 0);
1301 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1305 * Open a component of a MIME message as a download file
1307 void cmd_opna(char *cmdbuf)
1311 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1313 msgid = extract_long(cmdbuf, 0);
1314 extract(desired_section, cmdbuf, 1);
1316 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1321 * Save a message pointer into a specified room
1322 * (Returns 0 for success, nonzero for failure)
1323 * roomname may be NULL to use the current room
1325 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1327 char hold_rm[ROOMNAMELEN];
1328 struct cdbdata *cdbfr;
1331 long highest_msg = 0L;
1332 struct CtdlMessage *msg = NULL;
1334 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1335 roomname, msgid, flags);
1337 strcpy(hold_rm, CC->quickroom.QRname);
1339 /* We may need to check to see if this message is real */
1340 if ( (flags & SM_VERIFY_GOODNESS)
1341 || (flags & SM_DO_REPL_CHECK)
1343 msg = CtdlFetchMessage(msgid);
1344 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1347 /* Perform replication checks if necessary */
1348 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1350 if (getroom(&CC->quickroom,
1351 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1353 lprintf(9, "No such room <%s>\n", roomname);
1354 if (msg != NULL) CtdlFreeMessage(msg);
1355 return(ERROR + ROOM_NOT_FOUND);
1358 if (ReplicationChecks(msg) != 0) {
1359 getroom(&CC->quickroom, hold_rm);
1360 if (msg != NULL) CtdlFreeMessage(msg);
1361 lprintf(9, "Did replication, and newer exists\n");
1366 /* Now the regular stuff */
1367 if (lgetroom(&CC->quickroom,
1368 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1370 lprintf(9, "No such room <%s>\n", roomname);
1371 if (msg != NULL) CtdlFreeMessage(msg);
1372 return(ERROR + ROOM_NOT_FOUND);
1375 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1376 if (cdbfr == NULL) {
1380 msglist = mallok(cdbfr->len);
1381 if (msglist == NULL)
1382 lprintf(3, "ERROR malloc msglist!\n");
1383 num_msgs = cdbfr->len / sizeof(long);
1384 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1389 /* Make sure the message doesn't already exist in this room. It
1390 * is absolutely taboo to have more than one reference to the same
1391 * message in a room.
1393 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1394 if (msglist[i] == msgid) {
1395 lputroom(&CC->quickroom); /* unlock the room */
1396 getroom(&CC->quickroom, hold_rm);
1397 if (msg != NULL) CtdlFreeMessage(msg);
1398 return(ERROR + ALREADY_EXISTS);
1402 /* Now add the new message */
1404 msglist = reallok(msglist,
1405 (num_msgs * sizeof(long)));
1407 if (msglist == NULL) {
1408 lprintf(3, "ERROR: can't realloc message list!\n");
1410 msglist[num_msgs - 1] = msgid;
1412 /* Sort the message list, so all the msgid's are in order */
1413 num_msgs = sort_msglist(msglist, num_msgs);
1415 /* Determine the highest message number */
1416 highest_msg = msglist[num_msgs - 1];
1418 /* Write it back to disk. */
1419 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1420 msglist, num_msgs * sizeof(long));
1422 /* Free up the memory we used. */
1425 /* Update the highest-message pointer and unlock the room. */
1426 CC->quickroom.QRhighest = highest_msg;
1427 lputroom(&CC->quickroom);
1428 getroom(&CC->quickroom, hold_rm);
1430 /* Bump the reference count for this message. */
1431 if ((flags & SM_DONT_BUMP_REF)==0) {
1432 AdjRefCount(msgid, +1);
1435 /* Return success. */
1436 if (msg != NULL) CtdlFreeMessage(msg);
1443 * Message base operation to send a message to the master file
1444 * (returns new message number)
1446 * This is the back end for CtdlSaveMsg() and should not be directly
1447 * called by server-side modules.
1450 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1451 FILE *save_a_copy) /* save a copy to disk? */
1458 /* Get a new message number */
1459 newmsgid = get_new_message_number();
1460 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1462 /* Generate an ID if we don't have one already */
1463 if (msg->cm_fields['I']==NULL) {
1464 msg->cm_fields['I'] = strdoop(msgidbuf);
1467 serialize_message(&smr, msg);
1470 cprintf("%d Unable to serialize message\n",
1471 ERROR+INTERNAL_ERROR);
1475 /* Write our little bundle of joy into the message base */
1476 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1477 smr.ser, smr.len) < 0) {
1478 lprintf(2, "Can't store message\n");
1484 /* If the caller specified that a copy should be saved to a particular
1485 * file handle, do that now too.
1487 if (save_a_copy != NULL) {
1488 fwrite(smr.ser, smr.len, 1, save_a_copy);
1491 /* Free the memory we used for the serialized message */
1494 /* Return the *local* message ID to the caller
1495 * (even if we're storing an incoming network message)
1503 * Serialize a struct CtdlMessage into the format used on disk and network.
1505 * This function loads up a "struct ser_ret" (defined in server.h) which
1506 * contains the length of the serialized message and a pointer to the
1507 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1509 void serialize_message(struct ser_ret *ret, /* return values */
1510 struct CtdlMessage *msg) /* unserialized msg */
1514 static char *forder = FORDER;
1516 if (is_valid_message(msg) == 0) return; /* self check */
1519 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1520 ret->len = ret->len +
1521 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1523 lprintf(9, "calling malloc(%d)\n", ret->len);
1524 ret->ser = mallok(ret->len);
1525 if (ret->ser == NULL) {
1531 ret->ser[1] = msg->cm_anon_type;
1532 ret->ser[2] = msg->cm_format_type;
1535 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1536 ret->ser[wlen++] = (char)forder[i];
1537 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1538 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1540 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1549 * Back end for the ReplicationChecks() function
1551 void check_repl(long msgnum, void *userdata) {
1552 struct CtdlMessage *msg;
1553 time_t timestamp = (-1L);
1555 lprintf(9, "check_repl() found message %ld\n", msgnum);
1556 msg = CtdlFetchMessage(msgnum);
1557 if (msg == NULL) return;
1558 if (msg->cm_fields['T'] != NULL) {
1559 timestamp = atol(msg->cm_fields['T']);
1561 CtdlFreeMessage(msg);
1563 if (timestamp > msg_repl->highest) {
1564 msg_repl->highest = timestamp; /* newer! */
1565 lprintf(9, "newer!\n");
1568 lprintf(9, "older!\n");
1570 /* Existing isn't newer? Then delete the old one(s). */
1571 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1576 * Check to see if any messages already exist which carry the same Extended ID
1580 * -> With older timestamps: delete them and return 0. Message will be saved.
1581 * -> With newer timestamps: return 1. Message save will be aborted.
1583 int ReplicationChecks(struct CtdlMessage *msg) {
1584 struct CtdlMessage *template;
1587 lprintf(9, "ReplicationChecks() started\n");
1588 /* No extended id? Don't do anything. */
1589 if (msg->cm_fields['E'] == NULL) return 0;
1590 if (strlen(msg->cm_fields['E']) == 0) return 0;
1591 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1593 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1594 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1595 msg_repl->highest = atol(msg->cm_fields['T']);
1597 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1598 memset(template, 0, sizeof(struct CtdlMessage));
1599 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1601 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1604 /* If a newer message exists with the same Extended ID, abort
1607 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1611 CtdlFreeMessage(template);
1612 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1620 * Save a message to disk
1622 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1623 char *rec, /* Recipient (mail) */
1624 char *force, /* force a particular room? */
1625 int supplied_mailtype) /* local or remote type */
1628 char hold_rm[ROOMNAMELEN];
1629 char actual_rm[ROOMNAMELEN];
1630 char force_room[ROOMNAMELEN];
1631 char content_type[SIZ]; /* We have to learn this */
1632 char recipient[SIZ];
1635 struct usersupp userbuf;
1637 struct SuppMsgInfo smi;
1638 FILE *network_fp = NULL;
1639 static int seqnum = 1;
1640 struct CtdlMessage *imsg;
1644 lprintf(9, "CtdlSaveMsg() called\n");
1645 if (is_valid_message(msg) == 0) return(-1); /* self check */
1646 mailtype = supplied_mailtype;
1648 /* If this message has no timestamp, we take the liberty of
1649 * giving it one, right now.
1651 if (msg->cm_fields['T'] == NULL) {
1652 lprintf(9, "Generating timestamp\n");
1653 sprintf(aaa, "%ld", time(NULL));
1654 msg->cm_fields['T'] = strdoop(aaa);
1657 /* If this message has no path, we generate one.
1659 if (msg->cm_fields['P'] == NULL) {
1660 lprintf(9, "Generating path\n");
1661 if (msg->cm_fields['A'] != NULL) {
1662 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1663 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1664 if (isspace(msg->cm_fields['P'][a])) {
1665 msg->cm_fields['P'][a] = ' ';
1670 msg->cm_fields['P'] = strdoop("unknown");
1674 strcpy(force_room, force);
1676 /* Strip non-printable characters out of the recipient name */
1677 lprintf(9, "Checking recipient (if present)\n");
1678 strcpy(recipient, rec);
1679 for (a = 0; a < strlen(recipient); ++a)
1680 if (!isprint(recipient[a]))
1681 strcpy(&recipient[a], &recipient[a + 1]);
1683 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1684 for (a=0; a<strlen(recipient); ++a) {
1685 if (recipient[a] == '@') {
1686 if (CtdlHostAlias(&recipient[a+1])
1687 == hostalias_localhost) {
1689 lprintf(7, "Changed to <%s>\n", recipient);
1690 mailtype = MES_LOCAL;
1695 lprintf(9, "Recipient is <%s>\n", recipient);
1697 /* Learn about what's inside, because it's what's inside that counts */
1698 lprintf(9, "Learning what's inside\n");
1699 if (msg->cm_fields['M'] == NULL) {
1700 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1703 switch (msg->cm_format_type) {
1705 strcpy(content_type, "text/x-citadel-variformat");
1708 strcpy(content_type, "text/plain");
1711 strcpy(content_type, "text/plain");
1712 /* advance past header fields */
1713 mptr = msg->cm_fields['M'];
1716 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1717 safestrncpy(content_type, mptr,
1718 sizeof(content_type));
1719 strcpy(content_type, &content_type[14]);
1720 for (a = 0; a < strlen(content_type); ++a)
1721 if ((content_type[a] == ';')
1722 || (content_type[a] == ' ')
1723 || (content_type[a] == 13)
1724 || (content_type[a] == 10))
1725 content_type[a] = 0;
1732 /* Goto the correct room */
1733 lprintf(9, "Switching rooms\n");
1734 strcpy(hold_rm, CC->quickroom.QRname);
1735 strcpy(actual_rm, CC->quickroom.QRname);
1737 /* If the user is a twit, move to the twit room for posting */
1738 lprintf(9, "Handling twit stuff\n");
1740 if (CC->usersupp.axlevel == 2) {
1741 strcpy(hold_rm, actual_rm);
1742 strcpy(actual_rm, config.c_twitroom);
1746 /* ...or if this message is destined for Aide> then go there. */
1747 if (strlen(force_room) > 0) {
1748 strcpy(actual_rm, force_room);
1751 lprintf(9, "Possibly relocating\n");
1752 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1753 getroom(&CC->quickroom, actual_rm);
1757 * If this message has no O (room) field, generate one.
1759 if (msg->cm_fields['O'] == NULL) {
1760 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1763 /* Perform "before save" hooks (aborting if any return nonzero) */
1764 lprintf(9, "Performing before-save hooks\n");
1765 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1767 /* If this message has an Extended ID, perform replication checks */
1768 lprintf(9, "Performing replication checks\n");
1769 if (ReplicationChecks(msg) > 0) return(-1);
1771 /* Network mail - send a copy to the network program. */
1772 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1773 lprintf(9, "Sending network spool\n");
1774 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1775 (long) getpid(), CC->cs_pid, ++seqnum);
1776 lprintf(9, "Saving a copy to %s\n", aaa);
1777 network_fp = fopen(aaa, "ab+");
1778 if (network_fp == NULL)
1779 lprintf(2, "ERROR: %s\n", strerror(errno));
1782 /* Save it to disk */
1783 lprintf(9, "Saving to disk\n");
1784 newmsgid = send_message(msg, network_fp);
1785 if (network_fp != NULL) {
1787 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1790 if (newmsgid <= 0L) return(-1);
1792 /* Write a supplemental message info record. This doesn't have to
1793 * be a critical section because nobody else knows about this message
1796 lprintf(9, "Creating SuppMsgInfo record\n");
1797 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1798 smi.smi_msgnum = newmsgid;
1799 smi.smi_refcount = 0;
1800 safestrncpy(smi.smi_content_type, content_type, 64);
1801 PutSuppMsgInfo(&smi);
1803 /* Now figure out where to store the pointers */
1804 lprintf(9, "Storing pointers\n");
1806 /* If this is being done by the networker delivering a private
1807 * message, we want to BYPASS saving the sender's copy (because there
1808 * is no local sender; it would otherwise go to the Trashcan).
1810 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1811 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1812 lprintf(3, "ERROR saving message pointer!\n");
1813 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1817 /* For internet mail, drop a copy in the outbound queue room */
1818 if (mailtype == MES_INTERNET) {
1819 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1822 /* Bump this user's messages posted counter. */
1823 lprintf(9, "Updating user\n");
1824 lgetuser(&CC->usersupp, CC->curr_user);
1825 CC->usersupp.posted = CC->usersupp.posted + 1;
1826 lputuser(&CC->usersupp);
1828 /* If this is private, local mail, make a copy in the
1829 * recipient's mailbox and bump the reference count.
1831 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1832 if (getuser(&userbuf, recipient) == 0) {
1833 lprintf(9, "Delivering private mail\n");
1834 MailboxName(actual_rm, &userbuf, MAILROOM);
1835 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1838 lprintf(9, "No user <%s>, saving in %s> instead\n",
1839 recipient, AIDEROOM);
1840 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1844 /* Perform "after save" hooks */
1845 lprintf(9, "Performing after-save hooks\n");
1846 PerformMessageHooks(msg, EVT_AFTERSAVE);
1849 lprintf(9, "Returning to original room\n");
1850 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1851 getroom(&CC->quickroom, hold_rm);
1853 /* For internet mail, generate delivery instructions
1854 * (Yes, this is recursive! Deal with it!)
1856 if (mailtype == MES_INTERNET) {
1857 lprintf(9, "Generating delivery instructions\n");
1858 instr = mallok(2048);
1860 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1863 SPOOLMIME, newmsgid, time(NULL),
1864 msg->cm_fields['A'], msg->cm_fields['N'],
1867 imsg = mallok(sizeof(struct CtdlMessage));
1868 memset(imsg, 0, sizeof(struct CtdlMessage));
1869 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1870 imsg->cm_anon_type = MES_NORMAL;
1871 imsg->cm_format_type = FMT_RFC822;
1872 imsg->cm_fields['A'] = strdoop("Citadel");
1873 imsg->cm_fields['M'] = instr;
1874 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1875 CtdlFreeMessage(imsg);
1884 * Convenience function for generating small administrative messages.
1886 void quickie_message(char *from, char *to, char *room, char *text)
1888 struct CtdlMessage *msg;
1890 msg = mallok(sizeof(struct CtdlMessage));
1891 memset(msg, 0, sizeof(struct CtdlMessage));
1892 msg->cm_magic = CTDLMESSAGE_MAGIC;
1893 msg->cm_anon_type = MES_NORMAL;
1894 msg->cm_format_type = 0;
1895 msg->cm_fields['A'] = strdoop(from);
1896 msg->cm_fields['O'] = strdoop(room);
1897 msg->cm_fields['N'] = strdoop(NODENAME);
1899 msg->cm_fields['R'] = strdoop(to);
1900 msg->cm_fields['M'] = strdoop(text);
1902 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1903 CtdlFreeMessage(msg);
1904 syslog(LOG_NOTICE, text);
1910 * Back end function used by make_message() and similar functions
1912 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1913 size_t maxlen, /* maximum message length */
1914 char *exist /* if non-null, append to it;
1915 exist is ALWAYS freed */
1919 size_t message_len = 0;
1920 size_t buffer_len = 0;
1924 if (exist == NULL) {
1928 m = reallok(exist, strlen(exist) + 4096);
1929 if (m == NULL) phree(exist);
1932 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1939 /* read in the lines of message text one by one */
1940 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1942 /* strip trailing newline type stuff */
1943 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1944 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1946 linelen = strlen(buf);
1948 /* augment the buffer if we have to */
1949 if ((message_len + linelen + 2) > buffer_len) {
1950 lprintf(9, "realloking\n");
1951 ptr = reallok(m, (buffer_len * 2) );
1952 if (ptr == NULL) { /* flush if can't allocate */
1953 while ( (client_gets(buf)>0) &&
1954 strcmp(buf, terminator)) ;;
1957 buffer_len = (buffer_len * 2);
1959 lprintf(9, "buffer_len is %d\n", buffer_len);
1963 /* Add the new line to the buffer. We avoid using strcat()
1964 * because that would involve traversing the entire message
1965 * after each line, and this function needs to run fast.
1967 strcpy(&m[message_len], buf);
1968 m[message_len + linelen] = '\n';
1969 m[message_len + linelen + 1] = 0;
1970 message_len = message_len + linelen + 1;
1972 /* if we've hit the max msg length, flush the rest */
1973 if (message_len >= maxlen) {
1974 while ( (client_gets(buf)>0)
1975 && strcmp(buf, terminator)) ;;
1986 * Build a binary message to be saved on disk.
1989 struct CtdlMessage *make_message(
1990 struct usersupp *author, /* author's usersupp structure */
1991 char *recipient, /* NULL if it's not mail */
1992 char *room, /* room where it's going */
1993 int type, /* see MES_ types in header file */
1994 int net_type, /* see MES_ types in header file */
1995 int format_type, /* local or remote (see citadel.h) */
1996 char *fake_name) /* who we're masquerading as */
2002 struct CtdlMessage *msg;
2004 msg = mallok(sizeof(struct CtdlMessage));
2005 memset(msg, 0, sizeof(struct CtdlMessage));
2006 msg->cm_magic = CTDLMESSAGE_MAGIC;
2007 msg->cm_anon_type = type;
2008 msg->cm_format_type = format_type;
2010 /* Don't confuse the poor folks if it's not routed mail. */
2011 strcpy(dest_node, "");
2013 /* If net_type is MES_BINARY, split out the destination node. */
2014 if (net_type == MES_BINARY) {
2015 strcpy(dest_node, NODENAME);
2016 for (a = 0; a < strlen(recipient); ++a) {
2017 if (recipient[a] == '@') {
2019 strcpy(dest_node, &recipient[a + 1]);
2024 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
2025 if (net_type == MES_INTERNET) {
2026 strcpy(dest_node, "internet");
2029 while (isspace(recipient[strlen(recipient) - 1]))
2030 recipient[strlen(recipient) - 1] = 0;
2032 sprintf(buf, "cit%ld", author->usernum); /* Path */
2033 msg->cm_fields['P'] = strdoop(buf);
2035 sprintf(buf, "%ld", time(NULL)); /* timestamp */
2036 msg->cm_fields['T'] = strdoop(buf);
2038 if (fake_name[0]) /* author */
2039 msg->cm_fields['A'] = strdoop(fake_name);
2041 msg->cm_fields['A'] = strdoop(author->fullname);
2043 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
2044 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2046 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2048 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2049 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2051 if (recipient[0] != 0)
2052 msg->cm_fields['R'] = strdoop(recipient);
2053 if (dest_node[0] != 0)
2054 msg->cm_fields['D'] = strdoop(dest_node);
2057 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2058 config.c_maxmsglen, NULL);
2066 * Check to see whether we have permission to post a message in the current
2067 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2068 * returns 0 on success.
2070 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2072 if (!(CC->logged_in)) {
2073 sprintf(errmsgbuf, "Not logged in.");
2074 return (ERROR + NOT_LOGGED_IN);
2077 if ((CC->usersupp.axlevel < 2)
2078 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2079 sprintf(errmsgbuf, "Need to be validated to enter "
2080 "(except in %s> to sysop)", MAILROOM);
2081 return (ERROR + HIGHER_ACCESS_REQUIRED);
2084 if ((CC->usersupp.axlevel < 4)
2085 && (CC->quickroom.QRflags & QR_NETWORK)) {
2086 sprintf(errmsgbuf, "Need net privileges to enter here.");
2087 return (ERROR + HIGHER_ACCESS_REQUIRED);
2090 if ((CC->usersupp.axlevel < 6)
2091 && (CC->quickroom.QRflags & QR_READONLY)) {
2092 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2093 return (ERROR + HIGHER_ACCESS_REQUIRED);
2096 strcpy(errmsgbuf, "Ok");
2104 * message entry - mode 0 (normal)
2106 void cmd_ent0(char *entargs)
2109 char recipient[SIZ];
2111 int format_type = 0;
2112 char newusername[SIZ];
2113 struct CtdlMessage *msg;
2117 struct usersupp tempUS;
2121 post = extract_int(entargs, 0);
2122 extract(recipient, entargs, 1);
2123 anon_flag = extract_int(entargs, 2);
2124 format_type = extract_int(entargs, 3);
2126 /* first check to make sure the request is valid. */
2128 err = CtdlDoIHavePermissionToPostInThisRoom(buf);
2130 cprintf("%d %s\n", err, buf);
2134 /* Check some other permission type things. */
2137 if (CC->usersupp.axlevel < 6) {
2138 cprintf("%d You don't have permission to masquerade.\n",
2139 ERROR + HIGHER_ACCESS_REQUIRED);
2142 extract(newusername, entargs, 4);
2143 memset(CC->fake_postname, 0, 32);
2144 strcpy(CC->fake_postname, newusername);
2145 cprintf("%d Ok\n", OK);
2148 CC->cs_flags |= CS_POSTING;
2151 if (CC->quickroom.QRflags & QR_MAILBOX) {
2152 if (CC->usersupp.axlevel >= 2) {
2153 strcpy(buf, recipient);
2155 strcpy(buf, "sysop");
2156 e = alias(buf); /* alias and mail type */
2157 if ((buf[0] == 0) || (e == MES_ERROR)) {
2158 cprintf("%d Unknown address - cannot send message.\n",
2159 ERROR + NO_SUCH_USER);
2162 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2163 cprintf("%d Net privileges required for network mail.\n",
2164 ERROR + HIGHER_ACCESS_REQUIRED);
2167 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2168 && ((CC->usersupp.flags & US_INTERNET) == 0)
2169 && (!CC->internal_pgm)) {
2170 cprintf("%d You don't have access to Internet mail.\n",
2171 ERROR + HIGHER_ACCESS_REQUIRED);
2174 if (!strcasecmp(buf, "sysop")) {
2177 else if (e == MES_LOCAL) { /* don't search local file */
2178 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2179 cprintf("%d Can't send mail to yourself!\n",
2180 ERROR + NO_SUCH_USER);
2183 /* Check to make sure the user exists; also get the correct
2184 * upper/lower casing of the name.
2186 a = getuser(&tempUS, buf);
2188 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2191 strcpy(buf, tempUS.fullname);
2196 if (CC->quickroom.QRflags & QR_ANONONLY)
2198 if (CC->quickroom.QRflags & QR_ANONOPT) {
2202 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2205 /* If we're only checking the validity of the request, return
2206 * success without creating the message.
2209 cprintf("%d %s\n", OK, buf);
2213 cprintf("%d send message\n", SEND_LISTING);
2215 /* Read in the message from the client. */
2216 if (CC->fake_postname[0])
2217 msg = make_message(&CC->usersupp, buf,
2218 CC->quickroom.QRname, b, e, format_type,
2220 else if (CC->fake_username[0])
2221 msg = make_message(&CC->usersupp, buf,
2222 CC->quickroom.QRname, b, e, format_type,
2225 msg = make_message(&CC->usersupp, buf,
2226 CC->quickroom.QRname, b, e, format_type, "");
2229 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2230 CtdlFreeMessage(msg);
2231 CC->fake_postname[0] = '\0';
2238 * message entry - mode 3 (raw)
2240 void cmd_ent3(char *entargs)
2246 unsigned char ch, which_field;
2247 struct usersupp tempUS;
2249 struct CtdlMessage *msg;
2252 if (CC->internal_pgm == 0) {
2253 cprintf("%d This command is for internal programs only.\n",
2258 /* See if there's a recipient, but make sure it's a real one */
2259 extract(recp, entargs, 1);
2260 for (a = 0; a < strlen(recp); ++a)
2261 if (!isprint(recp[a]))
2262 strcpy(&recp[a], &recp[a + 1]);
2263 while (isspace(recp[0]))
2264 strcpy(recp, &recp[1]);
2265 while (isspace(recp[strlen(recp) - 1]))
2266 recp[strlen(recp) - 1] = 0;
2268 /* If we're in Mail, check the recipient */
2269 if (strlen(recp) > 0) {
2270 e = alias(recp); /* alias and mail type */
2271 if ((recp[0] == 0) || (e == MES_ERROR)) {
2272 cprintf("%d Unknown address - cannot send message.\n",
2273 ERROR + NO_SUCH_USER);
2276 if (e == MES_LOCAL) {
2277 a = getuser(&tempUS, recp);
2279 cprintf("%d No such user.\n",
2280 ERROR + NO_SUCH_USER);
2286 /* At this point, message has been approved. */
2287 if (extract_int(entargs, 0) == 0) {
2288 cprintf("%d OK to send\n", OK);
2292 msglen = extract_long(entargs, 2);
2293 msg = mallok(sizeof(struct CtdlMessage));
2295 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2299 memset(msg, 0, sizeof(struct CtdlMessage));
2300 tempbuf = mallok(msglen);
2301 if (tempbuf == NULL) {
2302 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2307 cprintf("%d %ld\n", SEND_BINARY, msglen);
2309 client_read(&ch, 1); /* 0xFF magic number */
2310 msg->cm_magic = CTDLMESSAGE_MAGIC;
2311 client_read(&ch, 1); /* anon type */
2312 msg->cm_anon_type = ch;
2313 client_read(&ch, 1); /* format type */
2314 msg->cm_format_type = ch;
2315 msglen = msglen - 3;
2317 while (msglen > 0) {
2318 client_read(&which_field, 1);
2319 if (!isalpha(which_field)) valid_msg = 0;
2323 client_read(&ch, 1);
2325 a = strlen(tempbuf);
2328 } while ( (ch != 0) && (msglen > 0) );
2330 msg->cm_fields[which_field] = strdoop(tempbuf);
2333 msg->cm_flags = CM_SKIP_HOOKS;
2334 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2335 CtdlFreeMessage(msg);
2341 * API function to delete messages which match a set of criteria
2342 * (returns the actual number of messages deleted)
2344 int CtdlDeleteMessages(char *room_name, /* which room */
2345 long dmsgnum, /* or "0" for any */
2346 char *content_type /* or "" for any */
2350 struct quickroom qrbuf;
2351 struct cdbdata *cdbfr;
2352 long *msglist = NULL;
2355 int num_deleted = 0;
2357 struct SuppMsgInfo smi;
2359 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2360 room_name, dmsgnum, content_type);
2362 /* get room record, obtaining a lock... */
2363 if (lgetroom(&qrbuf, room_name) != 0) {
2364 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2366 return (0); /* room not found */
2368 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2370 if (cdbfr != NULL) {
2371 msglist = mallok(cdbfr->len);
2372 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2373 num_msgs = cdbfr->len / sizeof(long);
2377 for (i = 0; i < num_msgs; ++i) {
2380 /* Set/clear a bit for each criterion */
2382 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2383 delete_this |= 0x01;
2385 if (strlen(content_type) == 0) {
2386 delete_this |= 0x02;
2388 GetSuppMsgInfo(&smi, msglist[i]);
2389 if (!strcasecmp(smi.smi_content_type,
2391 delete_this |= 0x02;
2395 /* Delete message only if all bits are set */
2396 if (delete_this == 0x03) {
2397 AdjRefCount(msglist[i], -1);
2403 num_msgs = sort_msglist(msglist, num_msgs);
2404 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2405 msglist, (num_msgs * sizeof(long)));
2407 qrbuf.QRhighest = msglist[num_msgs - 1];
2411 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2412 return (num_deleted);
2418 * Check whether the current user has permission to delete messages from
2419 * the current room (returns 1 for yes, 0 for no)
2421 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2422 getuser(&CC->usersupp, CC->curr_user);
2423 if ((CC->usersupp.axlevel < 6)
2424 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2425 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2426 && (!(CC->internal_pgm))) {
2435 * Delete message from current room
2437 void cmd_dele(char *delstr)
2442 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2443 cprintf("%d Higher access required.\n",
2444 ERROR + HIGHER_ACCESS_REQUIRED);
2447 delnum = extract_long(delstr, 0);
2449 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2452 cprintf("%d %d message%s deleted.\n", OK,
2453 num_deleted, ((num_deleted != 1) ? "s" : ""));
2455 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2461 * Back end API function for moves and deletes
2463 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2466 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2467 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2468 if (err != 0) return(err);
2476 * move or copy a message to another room
2478 void cmd_move(char *args)
2482 struct quickroom qtemp;
2486 num = extract_long(args, 0);
2487 extract(targ, args, 1);
2488 targ[ROOMNAMELEN - 1] = 0;
2489 is_copy = extract_int(args, 2);
2491 getuser(&CC->usersupp, CC->curr_user);
2492 if ((CC->usersupp.axlevel < 6)
2493 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2494 cprintf("%d Higher access required.\n",
2495 ERROR + HIGHER_ACCESS_REQUIRED);
2499 if (getroom(&qtemp, targ) != 0) {
2500 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2504 err = CtdlCopyMsgToRoom(num, targ);
2506 cprintf("%d Cannot store message in %s: error %d\n",
2511 /* Now delete the message from the source room,
2512 * if this is a 'move' rather than a 'copy' operation.
2514 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2516 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2522 * GetSuppMsgInfo() - Get the supplementary record for a message
2524 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2527 struct cdbdata *cdbsmi;
2530 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2531 smibuf->smi_msgnum = msgnum;
2532 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2534 /* Use the negative of the message number for its supp record index */
2535 TheIndex = (0L - msgnum);
2537 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2538 if (cdbsmi == NULL) {
2539 return; /* record not found; go with defaults */
2541 memcpy(smibuf, cdbsmi->ptr,
2542 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2543 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2550 * PutSuppMsgInfo() - (re)write supplementary record for a message
2552 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2556 /* Use the negative of the message number for its supp record index */
2557 TheIndex = (0L - smibuf->smi_msgnum);
2559 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2560 smibuf->smi_msgnum, smibuf->smi_refcount);
2562 cdb_store(CDB_MSGMAIN,
2563 &TheIndex, sizeof(long),
2564 smibuf, sizeof(struct SuppMsgInfo));
2569 * AdjRefCount - change the reference count for a message;
2570 * delete the message if it reaches zero
2572 void AdjRefCount(long msgnum, int incr)
2575 struct SuppMsgInfo smi;
2578 /* This is a *tight* critical section; please keep it that way, as
2579 * it may get called while nested in other critical sections.
2580 * Complicating this any further will surely cause deadlock!
2582 begin_critical_section(S_SUPPMSGMAIN);
2583 GetSuppMsgInfo(&smi, msgnum);
2584 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2585 msgnum, smi.smi_refcount);
2586 smi.smi_refcount += incr;
2587 PutSuppMsgInfo(&smi);
2588 end_critical_section(S_SUPPMSGMAIN);
2589 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2590 msgnum, smi.smi_refcount);
2592 /* If the reference count is now zero, delete the message
2593 * (and its supplementary record as well).
2595 if (smi.smi_refcount == 0) {
2596 lprintf(9, "Deleting message <%ld>\n", msgnum);
2598 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2599 delnum = (0L - msgnum);
2600 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2605 * Write a generic object to this room
2607 * Note: this could be much more efficient. Right now we use two temporary
2608 * files, and still pull the message into memory as with all others.
2610 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2611 char *content_type, /* MIME type of this object */
2612 char *tempfilename, /* Where to fetch it from */
2613 struct usersupp *is_mailbox, /* Mailbox room? */
2614 int is_binary, /* Is encoding necessary? */
2615 int is_unique, /* Del others of this type? */
2616 unsigned int flags /* Internal save flags */
2621 char filename[PATH_MAX];
2624 struct quickroom qrbuf;
2625 char roomname[ROOMNAMELEN];
2626 struct CtdlMessage *msg;
2629 if (is_mailbox != NULL)
2630 MailboxName(roomname, is_mailbox, req_room);
2632 safestrncpy(roomname, req_room, sizeof(roomname));
2633 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2635 strcpy(filename, tmpnam(NULL));
2636 fp = fopen(filename, "w");
2640 tempfp = fopen(tempfilename, "r");
2641 if (tempfp == NULL) {
2647 fprintf(fp, "Content-type: %s\n", content_type);
2648 lprintf(9, "Content-type: %s\n", content_type);
2650 if (is_binary == 0) {
2651 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2652 while (ch = getc(tempfp), ch > 0)
2658 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2661 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2662 tempfilename, filename);
2666 lprintf(9, "Allocating\n");
2667 msg = mallok(sizeof(struct CtdlMessage));
2668 memset(msg, 0, sizeof(struct CtdlMessage));
2669 msg->cm_magic = CTDLMESSAGE_MAGIC;
2670 msg->cm_anon_type = MES_NORMAL;
2671 msg->cm_format_type = 4;
2672 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2673 msg->cm_fields['O'] = strdoop(req_room);
2674 msg->cm_fields['N'] = strdoop(config.c_nodename);
2675 msg->cm_fields['H'] = strdoop(config.c_humannode);
2676 msg->cm_flags = flags;
2678 lprintf(9, "Loading\n");
2679 fp = fopen(filename, "rb");
2680 fseek(fp, 0L, SEEK_END);
2683 msg->cm_fields['M'] = mallok(len);
2684 fread(msg->cm_fields['M'], len, 1, fp);
2688 /* Create the requested room if we have to. */
2689 if (getroom(&qrbuf, roomname) != 0) {
2690 create_room(roomname,
2691 ( (is_mailbox != NULL) ? 5 : 3 ),
2694 /* If the caller specified this object as unique, delete all
2695 * other objects of this type that are currently in the room.
2698 lprintf(9, "Deleted %d other msgs of this type\n",
2699 CtdlDeleteMessages(roomname, 0L, content_type));
2701 /* Now write the data */
2702 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2703 CtdlFreeMessage(msg);
2711 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2712 config_msgnum = msgnum;
2716 char *CtdlGetSysConfig(char *sysconfname) {
2717 char hold_rm[ROOMNAMELEN];
2720 struct CtdlMessage *msg;
2723 strcpy(hold_rm, CC->quickroom.QRname);
2724 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2725 getroom(&CC->quickroom, hold_rm);
2730 /* We want the last (and probably only) config in this room */
2731 begin_critical_section(S_CONFIG);
2732 config_msgnum = (-1L);
2733 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2734 CtdlGetSysConfigBackend, NULL);
2735 msgnum = config_msgnum;
2736 end_critical_section(S_CONFIG);
2742 msg = CtdlFetchMessage(msgnum);
2744 conf = strdoop(msg->cm_fields['M']);
2745 CtdlFreeMessage(msg);
2752 getroom(&CC->quickroom, hold_rm);
2754 lprintf(9, "eggstracting...\n");
2755 if (conf != NULL) do {
2756 extract_token(buf, conf, 0, '\n');
2757 lprintf(9, "eggstracted <%s>\n", buf);
2758 strcpy(conf, &conf[strlen(buf)+1]);
2759 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2764 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2765 char temp[PATH_MAX];
2768 strcpy(temp, tmpnam(NULL));
2770 fp = fopen(temp, "w");
2771 if (fp == NULL) return;
2772 fprintf(fp, "%s", sysconfdata);
2775 /* this handy API function does all the work for us */
2776 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);