4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
37 #include "sysdep_decls.h"
38 #include "citserver.h"
43 #include "dynloader.h"
45 #include "mime_parser.h"
48 #include "internet_addressing.h"
50 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
51 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
52 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
54 extern struct config config;
58 "", "", "", "", "", "", "", "",
59 "", "", "", "", "", "", "", "",
60 "", "", "", "", "", "", "", "",
61 "", "", "", "", "", "", "", "",
62 "", "", "", "", "", "", "", "",
63 "", "", "", "", "", "", "", "",
64 "", "", "", "", "", "", "", "",
65 "", "", "", "", "", "", "", "",
92 * This function is self explanatory.
93 * (What can I say, I'm in a weird mood today...)
95 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
99 for (i = 0; i < strlen(name); ++i) {
100 if (name[i] == '@') {
101 while (isspace(name[i - 1]) && i > 0) {
102 strcpy(&name[i - 1], &name[i]);
105 while (isspace(name[i + 1])) {
106 strcpy(&name[i + 1], &name[i + 2]);
114 * Aliasing for network mail.
115 * (Error messages have been commented out, because this is a server.)
117 int alias(char *name)
118 { /* process alias and routing info for mail */
121 char aaa[300], bbb[300];
123 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
125 fp = fopen("network/mail.aliases", "r");
127 fp = fopen("/dev/null", "r");
132 while (fgets(aaa, sizeof aaa, fp) != NULL) {
133 while (isspace(name[0]))
134 strcpy(name, &name[1]);
135 aaa[strlen(aaa) - 1] = 0;
137 for (a = 0; a < strlen(aaa); ++a) {
139 strcpy(bbb, &aaa[a + 1]);
143 if (!strcasecmp(name, aaa))
147 lprintf(7, "Mail is being forwarded to %s\n", name);
149 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
150 for (a=0; a<strlen(name); ++a) {
151 if (name[a] == '@') {
152 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
154 lprintf(7, "Changed to <%s>\n", name);
159 /* determine local or remote type, see citadel.h */
160 for (a = 0; a < strlen(name); ++a)
162 return (MES_INTERNET);
163 for (a = 0; a < strlen(name); ++a)
165 for (b = a; b < strlen(name); ++b)
167 return (MES_INTERNET);
169 for (a = 0; a < strlen(name); ++a)
173 lprintf(7, "Too many @'s in address\n");
177 for (a = 0; a < strlen(name); ++a)
179 strcpy(bbb, &name[a + 1]);
181 strcpy(bbb, &bbb[1]);
182 fp = fopen("network/mail.sysinfo", "r");
186 a = getstring(fp, aaa);
187 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
188 a = getstring(fp, aaa);
189 if (!strncmp(aaa, "use ", 4)) {
190 strcpy(bbb, &aaa[4]);
195 if (!strncmp(aaa, "uum", 3)) {
197 for (a = 0; a < strlen(bbb); ++a) {
203 while (bbb[strlen(bbb) - 1] == '_')
204 bbb[strlen(bbb) - 1] = 0;
205 sprintf(name, &aaa[4], bbb);
206 lprintf(9, "returning MES_INTERNET\n");
207 return (MES_INTERNET);
209 if (!strncmp(aaa, "bin", 3)) {
212 while (aaa[strlen(aaa) - 1] != '@')
213 aaa[strlen(aaa) - 1] = 0;
214 aaa[strlen(aaa) - 1] = 0;
215 while (aaa[strlen(aaa) - 1] == ' ')
216 aaa[strlen(aaa) - 1] = 0;
217 while (bbb[0] != '@')
218 strcpy(bbb, &bbb[1]);
219 strcpy(bbb, &bbb[1]);
220 while (bbb[0] == ' ')
221 strcpy(bbb, &bbb[1]);
222 sprintf(name, "%s @%s", aaa, bbb);
223 lprintf(9, "returning MES_BINARY\n");
228 lprintf(9, "returning MES_LOCAL\n");
237 fp = fopen("citadel.control", "r");
238 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
244 void simple_listing(long msgnum, void *userdata)
246 cprintf("%ld\n", msgnum);
251 /* Determine if a given message matches the fields in a message template.
252 * Return 0 for a successful match.
254 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
257 /* If there aren't any fields in the template, all messages will
260 if (template == NULL) return(0);
262 /* Null messages are bogus. */
263 if (msg == NULL) return(1);
265 for (i='A'; i<='Z'; ++i) {
266 if (template->cm_fields[i] != NULL) {
267 if (msg->cm_fields[i] == NULL) {
270 if (strcasecmp(msg->cm_fields[i],
271 template->cm_fields[i])) return 1;
275 /* All compares succeeded: we have a match! */
281 * Manipulate the "seen msgs" string.
283 void CtdlSetSeen(long target_msgnum, int target_setting) {
285 struct cdbdata *cdbfr;
295 /* Learn about the user and room in question */
297 getuser(&CC->usersupp, CC->curr_user);
298 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
300 /* Load the message list */
301 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
303 msglist = mallok(cdbfr->len);
304 memcpy(msglist, cdbfr->ptr, cdbfr->len);
305 num_msgs = cdbfr->len / sizeof(long);
308 return; /* No messages at all? No further action. */
311 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
314 for (i=0; i<num_msgs; ++i) {
317 if (msglist[i] == target_msgnum) {
318 is_seen = target_setting;
321 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
327 if (lo < 0L) lo = msglist[i];
330 if ( ((is_seen == 0) && (was_seen == 1))
331 || ((is_seen == 1) && (i == num_msgs-1)) ) {
332 if ( (strlen(newseen) + 20) > SIZ) {
333 strcpy(newseen, &newseen[20]);
336 if (strlen(newseen) > 0) strcat(newseen, ",");
338 sprintf(&newseen[strlen(newseen)], "%ld", lo);
341 sprintf(&newseen[strlen(newseen)], "%ld:%ld",
350 safestrncpy(vbuf.v_seen, newseen, SIZ);
351 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
353 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
358 * API function to perform an operation for each qualifying message in the
359 * current room. (Returns the number of messages processed.)
361 int CtdlForEachMessage(int mode, long ref,
362 int moderation_level,
364 struct CtdlMessage *compare,
365 void (*CallBack) (long, void *),
371 struct cdbdata *cdbfr;
372 long *msglist = NULL;
374 int num_processed = 0;
376 struct SuppMsgInfo smi;
377 struct CtdlMessage *msg;
380 int printed_lastold = 0;
382 /* Learn about the user and room in question */
384 getuser(&CC->usersupp, CC->curr_user);
385 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
387 /* Load the message list */
388 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
390 msglist = mallok(cdbfr->len);
391 memcpy(msglist, cdbfr->ptr, cdbfr->len);
392 num_msgs = cdbfr->len / sizeof(long);
395 return 0; /* No messages at all? No further action. */
400 * Now begin the traversal.
402 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
403 GetSuppMsgInfo(&smi, msglist[a]);
405 /* Filter out messages that are moderated below the level
406 * currently being viewed at.
408 if (smi.smi_mod < moderation_level) {
412 /* If the caller is looking for a specific MIME type, filter
413 * out all messages which are not of the type requested.
415 if (content_type != NULL) if (strlen(content_type) > 0) {
416 if (strcasecmp(smi.smi_content_type, content_type)) {
422 num_msgs = sort_msglist(msglist, num_msgs);
424 /* If a template was supplied, filter out the messages which
425 * don't match. (This could induce some delays!)
428 if (compare != NULL) {
429 for (a = 0; a < num_msgs; ++a) {
430 msg = CtdlFetchMessage(msglist[a]);
432 if (CtdlMsgCmp(msg, compare)) {
435 CtdlFreeMessage(msg);
443 * Now iterate through the message list, according to the
444 * criteria supplied by the caller.
447 for (a = 0; a < num_msgs; ++a) {
448 thismsg = msglist[a];
449 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
450 if (is_seen) lastold = thismsg;
455 || ((mode == MSGS_OLD) && (is_seen))
456 || ((mode == MSGS_NEW) && (!is_seen))
457 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
458 || ((mode == MSGS_FIRST) && (a < ref))
459 || ((mode == MSGS_GT) && (thismsg > ref))
460 || ((mode == MSGS_EQ) && (thismsg == ref))
463 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
465 CallBack(lastold, userdata);
469 if (CallBack) CallBack(thismsg, userdata);
473 phree(msglist); /* Clean up */
474 return num_processed;
480 * cmd_msgs() - get list of message #'s in this room
481 * implements the MSGS server command using CtdlForEachMessage()
483 void cmd_msgs(char *cmdbuf)
492 int with_template = 0;
493 struct CtdlMessage *template = NULL;
495 extract(which, cmdbuf, 0);
496 cm_ref = extract_int(cmdbuf, 1);
497 with_template = extract_int(cmdbuf, 2);
501 if (!strncasecmp(which, "OLD", 3))
503 else if (!strncasecmp(which, "NEW", 3))
505 else if (!strncasecmp(which, "FIRST", 5))
507 else if (!strncasecmp(which, "LAST", 4))
509 else if (!strncasecmp(which, "GT", 2))
512 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
513 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
518 cprintf("%d Send template then receive message list\n",
520 template = (struct CtdlMessage *)
521 mallok(sizeof(struct CtdlMessage));
522 memset(template, 0, sizeof(struct CtdlMessage));
523 while(client_gets(buf), strcmp(buf,"000")) {
524 extract(tfield, buf, 0);
525 extract(tvalue, buf, 1);
526 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
527 if (!strcasecmp(tfield, msgkeys[i])) {
528 template->cm_fields[i] =
535 cprintf("%d Message list...\n", LISTING_FOLLOWS);
538 CtdlForEachMessage(mode, cm_ref,
539 CC->usersupp.moderation_filter,
540 NULL, template, simple_listing, NULL);
541 if (template != NULL) CtdlFreeMessage(template);
549 * help_subst() - support routine for help file viewer
551 void help_subst(char *strbuf, char *source, char *dest)
556 while (p = pattern2(strbuf, source), (p >= 0)) {
557 strcpy(workbuf, &strbuf[p + strlen(source)]);
558 strcpy(&strbuf[p], dest);
559 strcat(strbuf, workbuf);
564 void do_help_subst(char *buffer)
568 help_subst(buffer, "^nodename", config.c_nodename);
569 help_subst(buffer, "^humannode", config.c_humannode);
570 help_subst(buffer, "^fqdn", config.c_fqdn);
571 help_subst(buffer, "^username", CC->usersupp.fullname);
572 sprintf(buf2, "%ld", CC->usersupp.usernum);
573 help_subst(buffer, "^usernum", buf2);
574 help_subst(buffer, "^sysadm", config.c_sysadm);
575 help_subst(buffer, "^variantname", CITADEL);
576 sprintf(buf2, "%d", config.c_maxsessions);
577 help_subst(buffer, "^maxsessions", buf2);
583 * memfmout() - Citadel text formatter and paginator.
584 * Although the original purpose of this routine was to format
585 * text to the reader's screen width, all we're really using it
586 * for here is to format text out to 80 columns before sending it
587 * to the client. The client software may reformat it again.
590 int width, /* screen width to use */
591 char *mptr, /* where are we going to get our text from? */
592 char subst, /* nonzero if we should do substitutions */
593 char *nl) /* string to terminate lines with */
605 c = 1; /* c is the current pos */
609 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
611 buffer[strlen(buffer) + 1] = 0;
612 buffer[strlen(buffer)] = ch;
615 if (buffer[0] == '^')
616 do_help_subst(buffer);
618 buffer[strlen(buffer) + 1] = 0;
620 strcpy(buffer, &buffer[1]);
628 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
630 if (((old == 13) || (old == 10)) && (isspace(real))) {
638 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
639 cprintf("%s%s", nl, aaa);
648 if ((strlen(aaa) + c) > (width - 5)) {
657 if ((ch == 13) || (ch == 10)) {
658 cprintf("%s%s", aaa, nl);
665 cprintf("%s%s", aaa, nl);
671 * Callback function for mime parser that simply lists the part
673 void list_this_part(char *name, char *filename, char *partnum, char *disp,
674 void *content, char *cbtype, size_t length, char *encoding,
678 cprintf("part=%s|%s|%s|%s|%s|%d\n",
679 name, filename, partnum, disp, cbtype, length);
684 * Callback function for mime parser that opens a section for downloading
686 void mime_download(char *name, char *filename, char *partnum, char *disp,
687 void *content, char *cbtype, size_t length, char *encoding,
691 /* Silently go away if there's already a download open... */
692 if (CC->download_fp != NULL)
695 /* ...or if this is not the desired section */
696 if (strcasecmp(desired_section, partnum))
699 CC->download_fp = tmpfile();
700 if (CC->download_fp == NULL)
703 fwrite(content, length, 1, CC->download_fp);
704 fflush(CC->download_fp);
705 rewind(CC->download_fp);
707 OpenCmdResult(filename, cbtype);
713 * Load a message from disk into memory.
714 * This is used by CtdlOutputMsg() and other fetch functions.
716 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
717 * using the CtdlMessageFree() function.
719 struct CtdlMessage *CtdlFetchMessage(long msgnum)
721 struct cdbdata *dmsgtext;
722 struct CtdlMessage *ret = NULL;
725 CIT_UBYTE field_header;
728 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
729 if (dmsgtext == NULL) {
732 mptr = dmsgtext->ptr;
734 /* Parse the three bytes that begin EVERY message on disk.
735 * The first is always 0xFF, the on-disk magic number.
736 * The second is the anonymous/public type byte.
737 * The third is the format type byte (vari, fixed, or MIME).
741 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
745 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
746 memset(ret, 0, sizeof(struct CtdlMessage));
748 ret->cm_magic = CTDLMESSAGE_MAGIC;
749 ret->cm_anon_type = *mptr++; /* Anon type byte */
750 ret->cm_format_type = *mptr++; /* Format type byte */
753 * The rest is zero or more arbitrary fields. Load them in.
754 * We're done when we encounter either a zero-length field or
755 * have just processed the 'M' (message text) field.
758 field_length = strlen(mptr);
759 if (field_length == 0)
761 field_header = *mptr++;
762 ret->cm_fields[field_header] = mallok(field_length);
763 strcpy(ret->cm_fields[field_header], mptr);
765 while (*mptr++ != 0); /* advance to next field */
767 } while ((field_length > 0) && (field_header != 'M'));
771 /* Always make sure there's something in the msg text field */
772 if (ret->cm_fields['M'] == NULL)
773 ret->cm_fields['M'] = strdoop("<no text>\n");
775 /* Perform "before read" hooks (aborting if any return nonzero) */
776 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
777 CtdlFreeMessage(ret);
786 * Returns 1 if the supplied pointer points to a valid Citadel message.
787 * If the pointer is NULL or the magic number check fails, returns 0.
789 int is_valid_message(struct CtdlMessage *msg) {
792 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
793 lprintf(3, "is_valid_message() -- self-check failed\n");
801 * 'Destructor' for struct CtdlMessage
803 void CtdlFreeMessage(struct CtdlMessage *msg)
807 if (is_valid_message(msg) == 0) return;
809 for (i = 0; i < 256; ++i)
810 if (msg->cm_fields[i] != NULL) {
811 phree(msg->cm_fields[i]);
814 msg->cm_magic = 0; /* just in case */
820 * Callback function for mime parser that wants to display text
822 void fixed_output(char *name, char *filename, char *partnum, char *disp,
823 void *content, char *cbtype, size_t length, char *encoding,
831 if (!strcasecmp(cbtype, "multipart/alternative")) {
832 strcpy(ma->prefix, partnum);
833 strcat(ma->prefix, ".");
839 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
841 && (ma->did_print == 1) ) {
842 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
848 if ( (!strcasecmp(cbtype, "text/plain"))
849 || (strlen(cbtype)==0) ) {
855 if (ch==10) cprintf("\r\n");
856 else cprintf("%c", ch);
860 if (ch != '\n') cprintf("\n");
862 else if (!strcasecmp(cbtype, "text/html")) {
863 ptr = html_to_ascii(content, 80, 0);
868 if (ch==10) cprintf("\r\n");
869 else cprintf("%c", ch);
873 else if (strncasecmp(cbtype, "multipart/", 10)) {
874 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
875 partnum, filename, cbtype, length);
881 * Get a message off disk. (returns om_* values found in msgbase.h)
884 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
885 int mode, /* how would you like that message? */
886 int headers_only, /* eschew the message body? */
887 int do_proto, /* do Citadel protocol responses? */
888 int crlf /* Use CRLF newlines instead of LF? */
890 struct CtdlMessage *TheMessage;
893 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
898 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
899 if (do_proto) cprintf("%d Not logged in.\n",
900 ERROR + NOT_LOGGED_IN);
901 return(om_not_logged_in);
904 /* FIXME ... small security issue
905 * We need to check to make sure the requested message is actually
906 * in the current room, and set msg_ok to 1 only if it is. This
907 * functionality is currently missing because I'm in a hurry to replace
908 * broken production code with nonbroken pre-beta code. :( -- ajc
911 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
913 return(om_no_such_msg);
918 * Fetch the message from disk
920 TheMessage = CtdlFetchMessage(msg_num);
921 if (TheMessage == NULL) {
922 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
924 return(om_no_such_msg);
927 retcode = CtdlOutputPreLoadedMsg(
928 TheMessage, msg_num, mode,
929 headers_only, do_proto, crlf);
931 CtdlFreeMessage(TheMessage);
937 * Get a message off disk. (returns om_* values found in msgbase.h)
940 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
942 int mode, /* how would you like that message? */
943 int headers_only, /* eschew the message body? */
944 int do_proto, /* do Citadel protocol responses? */
945 int crlf /* Use CRLF newlines instead of LF? */
951 char display_name[SIZ];
953 char *nl; /* newline string */
955 /* buffers needed for RFC822 translation */
965 sprintf(mid, "%ld", msg_num);
966 nl = (crlf ? "\r\n" : "\n");
968 if (!is_valid_message(TheMessage)) {
969 lprintf(1, "ERROR: invalid preloaded message for output\n");
970 return(om_no_such_msg);
973 /* Are we downloading a MIME component? */
974 if (mode == MT_DOWNLOAD) {
975 if (TheMessage->cm_format_type != FMT_RFC822) {
977 cprintf("%d This is not a MIME message.\n",
979 } else if (CC->download_fp != NULL) {
980 if (do_proto) cprintf(
981 "%d You already have a download open.\n",
984 /* Parse the message text component */
985 mptr = TheMessage->cm_fields['M'];
986 mime_parser(mptr, NULL,
987 *mime_download, NULL, NULL,
989 /* If there's no file open by this time, the requested
990 * section wasn't found, so print an error
992 if (CC->download_fp == NULL) {
993 if (do_proto) cprintf(
994 "%d Section %s not found.\n",
995 ERROR + FILE_NOT_FOUND,
999 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1002 /* now for the user-mode message reading loops */
1003 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1005 /* Tell the client which format type we're using. If this is a
1006 * MIME message, *lie* about it and tell the user it's fixed-format.
1008 if (mode == MT_CITADEL) {
1009 if (TheMessage->cm_format_type == FMT_RFC822) {
1010 if (do_proto) cprintf("type=1\n");
1013 if (do_proto) cprintf("type=%d\n",
1014 TheMessage->cm_format_type);
1018 /* nhdr=yes means that we're only displaying headers, no body */
1019 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
1020 if (do_proto) cprintf("nhdr=yes\n");
1023 /* begin header processing loop for Citadel message format */
1025 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1027 strcpy(display_name, "<unknown>");
1028 if (TheMessage->cm_fields['A']) {
1029 strcpy(buf, TheMessage->cm_fields['A']);
1030 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1031 if (TheMessage->cm_anon_type == MES_ANON)
1032 strcpy(display_name, "****");
1033 else if (TheMessage->cm_anon_type == MES_AN2)
1034 strcpy(display_name, "anonymous");
1036 strcpy(display_name, buf);
1037 if ((is_room_aide())
1038 && ((TheMessage->cm_anon_type == MES_ANON)
1039 || (TheMessage->cm_anon_type == MES_AN2))) {
1040 sprintf(&display_name[strlen(display_name)],
1045 strcpy(allkeys, FORDER);
1046 for (i=0; i<strlen(allkeys); ++i) {
1047 k = (int) allkeys[i];
1049 if (TheMessage->cm_fields[k] != NULL) {
1051 if (do_proto) cprintf("%s=%s\n",
1056 if (do_proto) cprintf("%s=%s\n",
1058 TheMessage->cm_fields[k]
1067 /* begin header processing loop for RFC822 transfer format */
1072 strcpy(snode, NODENAME);
1073 strcpy(lnode, HUMANNODE);
1074 if (mode == MT_RFC822) {
1075 cprintf("X-UIDL: %ld%s", msg_num, nl);
1076 for (i = 0; i < 256; ++i) {
1077 if (TheMessage->cm_fields[i]) {
1078 mptr = TheMessage->cm_fields[i];
1081 strcpy(luser, mptr);
1082 strcpy(suser, mptr);
1085 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1086 into thinking that mail messages are newsgroup messages instead. When we
1087 add NNTP support back into Citadel we'll have to add code to only output
1088 this field when appropriate.
1089 else if (i == 'P') {
1090 cprintf("Path: %s%s", mptr, nl);
1094 cprintf("Subject: %s%s", mptr, nl);
1098 strcpy(lnode, mptr);
1100 cprintf("X-Citadel-Room: %s%s",
1103 strcpy(snode, mptr);
1105 cprintf("To: %s%s", mptr, nl);
1106 else if (i == 'T') {
1107 datestring(datestamp, atol(mptr),
1108 DATESTRING_RFC822 );
1109 cprintf("Date: %s%s", datestamp, nl);
1115 for (i=0; i<strlen(suser); ++i) {
1116 suser[i] = tolower(suser[i]);
1117 if (!isalnum(suser[i])) suser[i]='_';
1120 if (mode == MT_RFC822) {
1121 if (!strcasecmp(snode, NODENAME)) {
1122 strcpy(snode, FQDN);
1125 /* Construct a fun message id */
1126 cprintf("Message-ID: <%s", mid);
1127 if (strchr(mid, '@')==NULL) {
1128 cprintf("@%s", snode);
1132 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1134 if (strlen(fuser) > 0) {
1135 cprintf("From: %s (%s)%s", fuser, luser, nl);
1138 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1141 cprintf("Organization: %s%s", lnode, nl);
1144 /* end header processing loop ... at this point, we're in the text */
1146 mptr = TheMessage->cm_fields['M'];
1148 /* Tell the client about the MIME parts in this message */
1149 if (TheMessage->cm_format_type == FMT_RFC822) {
1150 if (mode == MT_CITADEL) {
1151 mime_parser(mptr, NULL,
1152 *list_this_part, NULL, NULL,
1155 else if (mode == MT_MIME) { /* list parts only */
1156 mime_parser(mptr, NULL,
1157 *list_this_part, NULL, NULL,
1159 if (do_proto) cprintf("000\n");
1162 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1163 /* FIXME ... we have to put some code in here to avoid
1164 * printing duplicate header information when both
1165 * Citadel and RFC822 headers exist. Preference should
1166 * probably be given to the RFC822 headers.
1168 while (ch=*(mptr++), ch!=0) {
1170 else if (ch==10) cprintf("%s", nl);
1171 else cprintf("%c", ch);
1173 if (do_proto) cprintf("000\n");
1179 if (do_proto) cprintf("000\n");
1183 /* signify start of msg text */
1184 if (mode == MT_CITADEL)
1185 if (do_proto) cprintf("text\n");
1186 if (mode == MT_RFC822) {
1187 if (TheMessage->cm_fields['U'] == NULL) {
1188 cprintf("Subject: (no subject)%s", nl);
1193 /* If the format type on disk is 1 (fixed-format), then we want
1194 * everything to be output completely literally ... regardless of
1195 * what message transfer format is in use.
1197 if (TheMessage->cm_format_type == FMT_FIXED) {
1199 while (ch = *mptr++, ch > 0) {
1202 if ((ch == 10) || (strlen(buf) > 250)) {
1203 cprintf("%s%s", buf, nl);
1206 buf[strlen(buf) + 1] = 0;
1207 buf[strlen(buf)] = ch;
1210 if (strlen(buf) > 0)
1211 cprintf("%s%s", buf, nl);
1214 /* If the message on disk is format 0 (Citadel vari-format), we
1215 * output using the formatter at 80 columns. This is the final output
1216 * form if the transfer format is RFC822, but if the transfer format
1217 * is Citadel proprietary, it'll still work, because the indentation
1218 * for new paragraphs is correct and the client will reformat the
1219 * message to the reader's screen width.
1221 if (TheMessage->cm_format_type == FMT_CITADEL) {
1222 memfmout(80, mptr, 0, nl);
1225 /* If the message on disk is format 4 (MIME), we've gotta hand it
1226 * off to the MIME parser. The client has already been told that
1227 * this message is format 1 (fixed format), so the callback function
1228 * we use will display those parts as-is.
1230 if (TheMessage->cm_format_type == FMT_RFC822) {
1231 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1232 memset(ma, 0, sizeof(struct ma_info));
1233 mime_parser(mptr, NULL,
1234 *fixed_output, NULL, NULL,
1238 /* now we're done */
1239 if (do_proto) cprintf("000\n");
1246 * display a message (mode 0 - Citadel proprietary)
1248 void cmd_msg0(char *cmdbuf)
1251 int headers_only = 0;
1253 msgid = extract_long(cmdbuf, 0);
1254 headers_only = extract_int(cmdbuf, 1);
1256 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1262 * display a message (mode 2 - RFC822)
1264 void cmd_msg2(char *cmdbuf)
1267 int headers_only = 0;
1269 msgid = extract_long(cmdbuf, 0);
1270 headers_only = extract_int(cmdbuf, 1);
1272 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1278 * display a message (mode 3 - IGnet raw format - internal programs only)
1280 void cmd_msg3(char *cmdbuf)
1283 struct CtdlMessage *msg;
1286 if (CC->internal_pgm == 0) {
1287 cprintf("%d This command is for internal programs only.\n",
1292 msgnum = extract_long(cmdbuf, 0);
1293 msg = CtdlFetchMessage(msgnum);
1295 cprintf("%d Message %ld not found.\n",
1300 serialize_message(&smr, msg);
1301 CtdlFreeMessage(msg);
1304 cprintf("%d Unable to serialize message\n",
1305 ERROR+INTERNAL_ERROR);
1309 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1310 client_write(smr.ser, smr.len);
1317 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1319 void cmd_msg4(char *cmdbuf)
1323 msgid = extract_long(cmdbuf, 0);
1324 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1328 * Open a component of a MIME message as a download file
1330 void cmd_opna(char *cmdbuf)
1334 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1336 msgid = extract_long(cmdbuf, 0);
1337 extract(desired_section, cmdbuf, 1);
1339 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1344 * Save a message pointer into a specified room
1345 * (Returns 0 for success, nonzero for failure)
1346 * roomname may be NULL to use the current room
1348 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1350 char hold_rm[ROOMNAMELEN];
1351 struct cdbdata *cdbfr;
1354 long highest_msg = 0L;
1355 struct CtdlMessage *msg = NULL;
1357 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1358 roomname, msgid, flags);
1360 strcpy(hold_rm, CC->quickroom.QRname);
1362 /* We may need to check to see if this message is real */
1363 if ( (flags & SM_VERIFY_GOODNESS)
1364 || (flags & SM_DO_REPL_CHECK)
1366 msg = CtdlFetchMessage(msgid);
1367 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1370 /* Perform replication checks if necessary */
1371 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1373 if (getroom(&CC->quickroom,
1374 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1376 lprintf(9, "No such room <%s>\n", roomname);
1377 if (msg != NULL) CtdlFreeMessage(msg);
1378 return(ERROR + ROOM_NOT_FOUND);
1381 if (ReplicationChecks(msg) != 0) {
1382 getroom(&CC->quickroom, hold_rm);
1383 if (msg != NULL) CtdlFreeMessage(msg);
1384 lprintf(9, "Did replication, and newer exists\n");
1389 /* Now the regular stuff */
1390 if (lgetroom(&CC->quickroom,
1391 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1393 lprintf(9, "No such room <%s>\n", roomname);
1394 if (msg != NULL) CtdlFreeMessage(msg);
1395 return(ERROR + ROOM_NOT_FOUND);
1398 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1399 if (cdbfr == NULL) {
1403 msglist = mallok(cdbfr->len);
1404 if (msglist == NULL)
1405 lprintf(3, "ERROR malloc msglist!\n");
1406 num_msgs = cdbfr->len / sizeof(long);
1407 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1412 /* Make sure the message doesn't already exist in this room. It
1413 * is absolutely taboo to have more than one reference to the same
1414 * message in a room.
1416 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1417 if (msglist[i] == msgid) {
1418 lputroom(&CC->quickroom); /* unlock the room */
1419 getroom(&CC->quickroom, hold_rm);
1420 if (msg != NULL) CtdlFreeMessage(msg);
1421 return(ERROR + ALREADY_EXISTS);
1425 /* Now add the new message */
1427 msglist = reallok(msglist,
1428 (num_msgs * sizeof(long)));
1430 if (msglist == NULL) {
1431 lprintf(3, "ERROR: can't realloc message list!\n");
1433 msglist[num_msgs - 1] = msgid;
1435 /* Sort the message list, so all the msgid's are in order */
1436 num_msgs = sort_msglist(msglist, num_msgs);
1438 /* Determine the highest message number */
1439 highest_msg = msglist[num_msgs - 1];
1441 /* Write it back to disk. */
1442 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1443 msglist, num_msgs * sizeof(long));
1445 /* Free up the memory we used. */
1448 /* Update the highest-message pointer and unlock the room. */
1449 CC->quickroom.QRhighest = highest_msg;
1450 lputroom(&CC->quickroom);
1451 getroom(&CC->quickroom, hold_rm);
1453 /* Bump the reference count for this message. */
1454 if ((flags & SM_DONT_BUMP_REF)==0) {
1455 AdjRefCount(msgid, +1);
1458 /* Return success. */
1459 if (msg != NULL) CtdlFreeMessage(msg);
1466 * Message base operation to send a message to the master file
1467 * (returns new message number)
1469 * This is the back end for CtdlSaveMsg() and should not be directly
1470 * called by server-side modules.
1473 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1474 FILE *save_a_copy) /* save a copy to disk? */
1481 /* Get a new message number */
1482 newmsgid = get_new_message_number();
1483 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1485 /* Generate an ID if we don't have one already */
1486 if (msg->cm_fields['I']==NULL) {
1487 msg->cm_fields['I'] = strdoop(msgidbuf);
1490 serialize_message(&smr, msg);
1493 cprintf("%d Unable to serialize message\n",
1494 ERROR+INTERNAL_ERROR);
1498 /* Write our little bundle of joy into the message base */
1499 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1500 smr.ser, smr.len) < 0) {
1501 lprintf(2, "Can't store message\n");
1507 /* If the caller specified that a copy should be saved to a particular
1508 * file handle, do that now too.
1510 if (save_a_copy != NULL) {
1511 fwrite(smr.ser, smr.len, 1, save_a_copy);
1514 /* Free the memory we used for the serialized message */
1517 /* Return the *local* message ID to the caller
1518 * (even if we're storing an incoming network message)
1526 * Serialize a struct CtdlMessage into the format used on disk and network.
1528 * This function loads up a "struct ser_ret" (defined in server.h) which
1529 * contains the length of the serialized message and a pointer to the
1530 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1532 void serialize_message(struct ser_ret *ret, /* return values */
1533 struct CtdlMessage *msg) /* unserialized msg */
1537 static char *forder = FORDER;
1539 if (is_valid_message(msg) == 0) return; /* self check */
1542 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1543 ret->len = ret->len +
1544 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1546 lprintf(9, "calling malloc(%d)\n", ret->len);
1547 ret->ser = mallok(ret->len);
1548 if (ret->ser == NULL) {
1554 ret->ser[1] = msg->cm_anon_type;
1555 ret->ser[2] = msg->cm_format_type;
1558 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1559 ret->ser[wlen++] = (char)forder[i];
1560 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1561 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1563 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1572 * Back end for the ReplicationChecks() function
1574 void check_repl(long msgnum, void *userdata) {
1575 struct CtdlMessage *msg;
1576 time_t timestamp = (-1L);
1578 lprintf(9, "check_repl() found message %ld\n", msgnum);
1579 msg = CtdlFetchMessage(msgnum);
1580 if (msg == NULL) return;
1581 if (msg->cm_fields['T'] != NULL) {
1582 timestamp = atol(msg->cm_fields['T']);
1584 CtdlFreeMessage(msg);
1586 if (timestamp > msg_repl->highest) {
1587 msg_repl->highest = timestamp; /* newer! */
1588 lprintf(9, "newer!\n");
1591 lprintf(9, "older!\n");
1593 /* Existing isn't newer? Then delete the old one(s). */
1594 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1599 * Check to see if any messages already exist which carry the same Extended ID
1603 * -> With older timestamps: delete them and return 0. Message will be saved.
1604 * -> With newer timestamps: return 1. Message save will be aborted.
1606 int ReplicationChecks(struct CtdlMessage *msg) {
1607 struct CtdlMessage *template;
1610 lprintf(9, "ReplicationChecks() started\n");
1611 /* No extended id? Don't do anything. */
1612 if (msg->cm_fields['E'] == NULL) return 0;
1613 if (strlen(msg->cm_fields['E']) == 0) return 0;
1614 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1616 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1617 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1618 msg_repl->highest = atol(msg->cm_fields['T']);
1620 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1621 memset(template, 0, sizeof(struct CtdlMessage));
1622 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1624 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1627 /* If a newer message exists with the same Extended ID, abort
1630 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1634 CtdlFreeMessage(template);
1635 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1643 * Save a message to disk
1645 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1646 char *rec, /* Recipient (mail) */
1647 char *force, /* force a particular room? */
1648 int supplied_mailtype) /* local or remote type */
1651 char hold_rm[ROOMNAMELEN];
1652 char actual_rm[ROOMNAMELEN];
1653 char force_room[ROOMNAMELEN];
1654 char content_type[SIZ]; /* We have to learn this */
1655 char recipient[SIZ];
1658 struct usersupp userbuf;
1660 struct SuppMsgInfo smi;
1661 FILE *network_fp = NULL;
1662 static int seqnum = 1;
1663 struct CtdlMessage *imsg;
1667 lprintf(9, "CtdlSaveMsg() called\n");
1668 if (is_valid_message(msg) == 0) return(-1); /* self check */
1669 mailtype = supplied_mailtype;
1671 /* If this message has no timestamp, we take the liberty of
1672 * giving it one, right now.
1674 if (msg->cm_fields['T'] == NULL) {
1675 lprintf(9, "Generating timestamp\n");
1676 sprintf(aaa, "%ld", time(NULL));
1677 msg->cm_fields['T'] = strdoop(aaa);
1680 /* If this message has no path, we generate one.
1682 if (msg->cm_fields['P'] == NULL) {
1683 lprintf(9, "Generating path\n");
1684 if (msg->cm_fields['A'] != NULL) {
1685 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1686 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1687 if (isspace(msg->cm_fields['P'][a])) {
1688 msg->cm_fields['P'][a] = ' ';
1693 msg->cm_fields['P'] = strdoop("unknown");
1697 strcpy(force_room, force);
1699 /* Strip non-printable characters out of the recipient name */
1700 lprintf(9, "Checking recipient (if present)\n");
1701 strcpy(recipient, rec);
1702 for (a = 0; a < strlen(recipient); ++a)
1703 if (!isprint(recipient[a]))
1704 strcpy(&recipient[a], &recipient[a + 1]);
1706 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1707 for (a=0; a<strlen(recipient); ++a) {
1708 if (recipient[a] == '@') {
1709 if (CtdlHostAlias(&recipient[a+1])
1710 == hostalias_localhost) {
1712 lprintf(7, "Changed to <%s>\n", recipient);
1713 mailtype = MES_LOCAL;
1718 lprintf(9, "Recipient is <%s>\n", recipient);
1720 /* Learn about what's inside, because it's what's inside that counts */
1721 lprintf(9, "Learning what's inside\n");
1722 if (msg->cm_fields['M'] == NULL) {
1723 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1726 switch (msg->cm_format_type) {
1728 strcpy(content_type, "text/x-citadel-variformat");
1731 strcpy(content_type, "text/plain");
1734 strcpy(content_type, "text/plain");
1735 /* advance past header fields */
1736 mptr = msg->cm_fields['M'];
1739 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1740 safestrncpy(content_type, mptr,
1741 sizeof(content_type));
1742 strcpy(content_type, &content_type[14]);
1743 for (a = 0; a < strlen(content_type); ++a)
1744 if ((content_type[a] == ';')
1745 || (content_type[a] == ' ')
1746 || (content_type[a] == 13)
1747 || (content_type[a] == 10))
1748 content_type[a] = 0;
1755 /* Goto the correct room */
1756 lprintf(9, "Switching rooms\n");
1757 strcpy(hold_rm, CC->quickroom.QRname);
1758 strcpy(actual_rm, CC->quickroom.QRname);
1760 /* If the user is a twit, move to the twit room for posting */
1761 lprintf(9, "Handling twit stuff\n");
1763 if (CC->usersupp.axlevel == 2) {
1764 strcpy(hold_rm, actual_rm);
1765 strcpy(actual_rm, config.c_twitroom);
1769 /* ...or if this message is destined for Aide> then go there. */
1770 if (strlen(force_room) > 0) {
1771 strcpy(actual_rm, force_room);
1774 lprintf(9, "Possibly relocating\n");
1775 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1776 getroom(&CC->quickroom, actual_rm);
1780 * If this message has no O (room) field, generate one.
1782 if (msg->cm_fields['O'] == NULL) {
1783 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1786 /* Perform "before save" hooks (aborting if any return nonzero) */
1787 lprintf(9, "Performing before-save hooks\n");
1788 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1790 /* If this message has an Extended ID, perform replication checks */
1791 lprintf(9, "Performing replication checks\n");
1792 if (ReplicationChecks(msg) > 0) return(-1);
1794 /* Network mail - send a copy to the network program. */
1795 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1796 lprintf(9, "Sending network spool\n");
1797 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1798 (long) getpid(), CC->cs_pid, ++seqnum);
1799 lprintf(9, "Saving a copy to %s\n", aaa);
1800 network_fp = fopen(aaa, "ab+");
1801 if (network_fp == NULL)
1802 lprintf(2, "ERROR: %s\n", strerror(errno));
1805 /* Save it to disk */
1806 lprintf(9, "Saving to disk\n");
1807 newmsgid = send_message(msg, network_fp);
1808 if (network_fp != NULL) {
1810 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1813 if (newmsgid <= 0L) return(-1);
1815 /* Write a supplemental message info record. This doesn't have to
1816 * be a critical section because nobody else knows about this message
1819 lprintf(9, "Creating SuppMsgInfo record\n");
1820 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1821 smi.smi_msgnum = newmsgid;
1822 smi.smi_refcount = 0;
1823 safestrncpy(smi.smi_content_type, content_type, 64);
1824 PutSuppMsgInfo(&smi);
1826 /* Now figure out where to store the pointers */
1827 lprintf(9, "Storing pointers\n");
1829 /* If this is being done by the networker delivering a private
1830 * message, we want to BYPASS saving the sender's copy (because there
1831 * is no local sender; it would otherwise go to the Trashcan).
1833 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1834 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1835 lprintf(3, "ERROR saving message pointer!\n");
1836 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1840 /* For internet mail, drop a copy in the outbound queue room */
1841 if (mailtype == MES_INTERNET) {
1842 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, 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.
1854 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1855 if (getuser(&userbuf, recipient) == 0) {
1856 lprintf(9, "Delivering private mail\n");
1857 MailboxName(actual_rm, &userbuf, MAILROOM);
1858 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1861 lprintf(9, "No user <%s>, saving in %s> instead\n",
1862 recipient, AIDEROOM);
1863 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1867 /* Perform "after save" hooks */
1868 lprintf(9, "Performing after-save hooks\n");
1869 PerformMessageHooks(msg, EVT_AFTERSAVE);
1872 lprintf(9, "Returning to original room\n");
1873 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1874 getroom(&CC->quickroom, hold_rm);
1876 /* For internet mail, generate delivery instructions
1877 * (Yes, this is recursive! Deal with it!)
1879 if (mailtype == MES_INTERNET) {
1880 lprintf(9, "Generating delivery instructions\n");
1881 instr = mallok(2048);
1883 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1886 SPOOLMIME, newmsgid, time(NULL),
1887 msg->cm_fields['A'], msg->cm_fields['N'],
1890 imsg = mallok(sizeof(struct CtdlMessage));
1891 memset(imsg, 0, sizeof(struct CtdlMessage));
1892 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1893 imsg->cm_anon_type = MES_NORMAL;
1894 imsg->cm_format_type = FMT_RFC822;
1895 imsg->cm_fields['A'] = strdoop("Citadel");
1896 imsg->cm_fields['M'] = instr;
1897 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1898 CtdlFreeMessage(imsg);
1907 * Convenience function for generating small administrative messages.
1909 void quickie_message(char *from, char *to, char *room, char *text)
1911 struct CtdlMessage *msg;
1913 msg = mallok(sizeof(struct CtdlMessage));
1914 memset(msg, 0, sizeof(struct CtdlMessage));
1915 msg->cm_magic = CTDLMESSAGE_MAGIC;
1916 msg->cm_anon_type = MES_NORMAL;
1917 msg->cm_format_type = 0;
1918 msg->cm_fields['A'] = strdoop(from);
1919 msg->cm_fields['O'] = strdoop(room);
1920 msg->cm_fields['N'] = strdoop(NODENAME);
1922 msg->cm_fields['R'] = strdoop(to);
1923 msg->cm_fields['M'] = strdoop(text);
1925 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1926 CtdlFreeMessage(msg);
1927 syslog(LOG_NOTICE, text);
1933 * Back end function used by make_message() and similar functions
1935 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1936 size_t maxlen, /* maximum message length */
1937 char *exist /* if non-null, append to it;
1938 exist is ALWAYS freed */
1942 size_t message_len = 0;
1943 size_t buffer_len = 0;
1947 if (exist == NULL) {
1951 m = reallok(exist, strlen(exist) + 4096);
1952 if (m == NULL) phree(exist);
1955 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1962 /* read in the lines of message text one by one */
1963 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1965 /* strip trailing newline type stuff */
1966 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1967 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1969 linelen = strlen(buf);
1971 /* augment the buffer if we have to */
1972 if ((message_len + linelen + 2) > buffer_len) {
1973 lprintf(9, "realloking\n");
1974 ptr = reallok(m, (buffer_len * 2) );
1975 if (ptr == NULL) { /* flush if can't allocate */
1976 while ( (client_gets(buf)>0) &&
1977 strcmp(buf, terminator)) ;;
1980 buffer_len = (buffer_len * 2);
1982 lprintf(9, "buffer_len is %d\n", buffer_len);
1986 /* Add the new line to the buffer. We avoid using strcat()
1987 * because that would involve traversing the entire message
1988 * after each line, and this function needs to run fast.
1990 strcpy(&m[message_len], buf);
1991 m[message_len + linelen] = '\n';
1992 m[message_len + linelen + 1] = 0;
1993 message_len = message_len + linelen + 1;
1995 /* if we've hit the max msg length, flush the rest */
1996 if (message_len >= maxlen) {
1997 while ( (client_gets(buf)>0)
1998 && strcmp(buf, terminator)) ;;
2009 * Build a binary message to be saved on disk.
2012 struct CtdlMessage *make_message(
2013 struct usersupp *author, /* author's usersupp structure */
2014 char *recipient, /* NULL if it's not mail */
2015 char *room, /* room where it's going */
2016 int type, /* see MES_ types in header file */
2017 int net_type, /* see MES_ types in header file */
2018 int format_type, /* local or remote (see citadel.h) */
2019 char *fake_name) /* who we're masquerading as */
2025 struct CtdlMessage *msg;
2027 msg = mallok(sizeof(struct CtdlMessage));
2028 memset(msg, 0, sizeof(struct CtdlMessage));
2029 msg->cm_magic = CTDLMESSAGE_MAGIC;
2030 msg->cm_anon_type = type;
2031 msg->cm_format_type = format_type;
2033 /* Don't confuse the poor folks if it's not routed mail. */
2034 strcpy(dest_node, "");
2036 /* If net_type is MES_BINARY, split out the destination node. */
2037 if (net_type == MES_BINARY) {
2038 strcpy(dest_node, NODENAME);
2039 for (a = 0; a < strlen(recipient); ++a) {
2040 if (recipient[a] == '@') {
2042 strcpy(dest_node, &recipient[a + 1]);
2047 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
2048 if (net_type == MES_INTERNET) {
2049 strcpy(dest_node, "internet");
2052 while (isspace(recipient[strlen(recipient) - 1]))
2053 recipient[strlen(recipient) - 1] = 0;
2055 sprintf(buf, "cit%ld", author->usernum); /* Path */
2056 msg->cm_fields['P'] = strdoop(buf);
2058 sprintf(buf, "%ld", time(NULL)); /* timestamp */
2059 msg->cm_fields['T'] = strdoop(buf);
2061 if (fake_name[0]) /* author */
2062 msg->cm_fields['A'] = strdoop(fake_name);
2064 msg->cm_fields['A'] = strdoop(author->fullname);
2066 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
2067 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2069 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2071 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2072 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2074 if (recipient[0] != 0)
2075 msg->cm_fields['R'] = strdoop(recipient);
2076 if (dest_node[0] != 0)
2077 msg->cm_fields['D'] = strdoop(dest_node);
2080 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2081 config.c_maxmsglen, NULL);
2089 * Check to see whether we have permission to post a message in the current
2090 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2091 * returns 0 on success.
2093 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2095 if (!(CC->logged_in)) {
2096 sprintf(errmsgbuf, "Not logged in.");
2097 return (ERROR + NOT_LOGGED_IN);
2100 if ((CC->usersupp.axlevel < 2)
2101 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2102 sprintf(errmsgbuf, "Need to be validated to enter "
2103 "(except in %s> to sysop)", MAILROOM);
2104 return (ERROR + HIGHER_ACCESS_REQUIRED);
2107 if ((CC->usersupp.axlevel < 4)
2108 && (CC->quickroom.QRflags & QR_NETWORK)) {
2109 sprintf(errmsgbuf, "Need net privileges to enter here.");
2110 return (ERROR + HIGHER_ACCESS_REQUIRED);
2113 if ((CC->usersupp.axlevel < 6)
2114 && (CC->quickroom.QRflags & QR_READONLY)) {
2115 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2116 return (ERROR + HIGHER_ACCESS_REQUIRED);
2119 strcpy(errmsgbuf, "Ok");
2127 * message entry - mode 0 (normal)
2129 void cmd_ent0(char *entargs)
2132 char recipient[SIZ];
2134 int format_type = 0;
2135 char newusername[SIZ];
2136 struct CtdlMessage *msg;
2140 struct usersupp tempUS;
2144 post = extract_int(entargs, 0);
2145 extract(recipient, entargs, 1);
2146 anon_flag = extract_int(entargs, 2);
2147 format_type = extract_int(entargs, 3);
2149 /* first check to make sure the request is valid. */
2151 err = CtdlDoIHavePermissionToPostInThisRoom(buf);
2153 cprintf("%d %s\n", err, buf);
2157 /* Check some other permission type things. */
2160 if (CC->usersupp.axlevel < 6) {
2161 cprintf("%d You don't have permission to masquerade.\n",
2162 ERROR + HIGHER_ACCESS_REQUIRED);
2165 extract(newusername, entargs, 4);
2166 memset(CC->fake_postname, 0, 32);
2167 strcpy(CC->fake_postname, newusername);
2168 cprintf("%d Ok\n", OK);
2171 CC->cs_flags |= CS_POSTING;
2174 if (CC->quickroom.QRflags & QR_MAILBOX) {
2175 if (CC->usersupp.axlevel >= 2) {
2176 strcpy(buf, recipient);
2178 strcpy(buf, "sysop");
2179 e = alias(buf); /* alias and mail type */
2180 if ((buf[0] == 0) || (e == MES_ERROR)) {
2181 cprintf("%d Unknown address - cannot send message.\n",
2182 ERROR + NO_SUCH_USER);
2185 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2186 cprintf("%d Net privileges required for network mail.\n",
2187 ERROR + HIGHER_ACCESS_REQUIRED);
2190 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2191 && ((CC->usersupp.flags & US_INTERNET) == 0)
2192 && (!CC->internal_pgm)) {
2193 cprintf("%d You don't have access to Internet mail.\n",
2194 ERROR + HIGHER_ACCESS_REQUIRED);
2197 if (!strcasecmp(buf, "sysop")) {
2200 else if (e == MES_LOCAL) { /* don't search local file */
2201 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2202 cprintf("%d Can't send mail to yourself!\n",
2203 ERROR + NO_SUCH_USER);
2206 /* Check to make sure the user exists; also get the correct
2207 * upper/lower casing of the name.
2209 a = getuser(&tempUS, buf);
2211 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2214 strcpy(buf, tempUS.fullname);
2219 if (CC->quickroom.QRflags & QR_ANONONLY)
2221 if (CC->quickroom.QRflags & QR_ANONOPT) {
2225 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2228 /* If we're only checking the validity of the request, return
2229 * success without creating the message.
2232 cprintf("%d %s\n", OK, buf);
2236 cprintf("%d send message\n", SEND_LISTING);
2238 /* Read in the message from the client. */
2239 if (CC->fake_postname[0])
2240 msg = make_message(&CC->usersupp, buf,
2241 CC->quickroom.QRname, b, e, format_type,
2243 else if (CC->fake_username[0])
2244 msg = make_message(&CC->usersupp, buf,
2245 CC->quickroom.QRname, b, e, format_type,
2248 msg = make_message(&CC->usersupp, buf,
2249 CC->quickroom.QRname, b, e, format_type, "");
2252 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2253 CtdlFreeMessage(msg);
2254 CC->fake_postname[0] = '\0';
2261 * message entry - mode 3 (raw)
2263 void cmd_ent3(char *entargs)
2269 unsigned char ch, which_field;
2270 struct usersupp tempUS;
2272 struct CtdlMessage *msg;
2275 if (CC->internal_pgm == 0) {
2276 cprintf("%d This command is for internal programs only.\n",
2281 /* See if there's a recipient, but make sure it's a real one */
2282 extract(recp, entargs, 1);
2283 for (a = 0; a < strlen(recp); ++a)
2284 if (!isprint(recp[a]))
2285 strcpy(&recp[a], &recp[a + 1]);
2286 while (isspace(recp[0]))
2287 strcpy(recp, &recp[1]);
2288 while (isspace(recp[strlen(recp) - 1]))
2289 recp[strlen(recp) - 1] = 0;
2291 /* If we're in Mail, check the recipient */
2292 if (strlen(recp) > 0) {
2293 e = alias(recp); /* alias and mail type */
2294 if ((recp[0] == 0) || (e == MES_ERROR)) {
2295 cprintf("%d Unknown address - cannot send message.\n",
2296 ERROR + NO_SUCH_USER);
2299 if (e == MES_LOCAL) {
2300 a = getuser(&tempUS, recp);
2302 cprintf("%d No such user.\n",
2303 ERROR + NO_SUCH_USER);
2309 /* At this point, message has been approved. */
2310 if (extract_int(entargs, 0) == 0) {
2311 cprintf("%d OK to send\n", OK);
2315 msglen = extract_long(entargs, 2);
2316 msg = mallok(sizeof(struct CtdlMessage));
2318 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2322 memset(msg, 0, sizeof(struct CtdlMessage));
2323 tempbuf = mallok(msglen);
2324 if (tempbuf == NULL) {
2325 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2330 cprintf("%d %ld\n", SEND_BINARY, msglen);
2332 client_read(&ch, 1); /* 0xFF magic number */
2333 msg->cm_magic = CTDLMESSAGE_MAGIC;
2334 client_read(&ch, 1); /* anon type */
2335 msg->cm_anon_type = ch;
2336 client_read(&ch, 1); /* format type */
2337 msg->cm_format_type = ch;
2338 msglen = msglen - 3;
2340 while (msglen > 0) {
2341 client_read(&which_field, 1);
2342 if (!isalpha(which_field)) valid_msg = 0;
2346 client_read(&ch, 1);
2348 a = strlen(tempbuf);
2351 } while ( (ch != 0) && (msglen > 0) );
2353 msg->cm_fields[which_field] = strdoop(tempbuf);
2356 msg->cm_flags = CM_SKIP_HOOKS;
2357 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2358 CtdlFreeMessage(msg);
2364 * API function to delete messages which match a set of criteria
2365 * (returns the actual number of messages deleted)
2367 int CtdlDeleteMessages(char *room_name, /* which room */
2368 long dmsgnum, /* or "0" for any */
2369 char *content_type /* or "" for any */
2373 struct quickroom qrbuf;
2374 struct cdbdata *cdbfr;
2375 long *msglist = NULL;
2378 int num_deleted = 0;
2380 struct SuppMsgInfo smi;
2382 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2383 room_name, dmsgnum, content_type);
2385 /* get room record, obtaining a lock... */
2386 if (lgetroom(&qrbuf, room_name) != 0) {
2387 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2389 return (0); /* room not found */
2391 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2393 if (cdbfr != NULL) {
2394 msglist = mallok(cdbfr->len);
2395 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2396 num_msgs = cdbfr->len / sizeof(long);
2400 for (i = 0; i < num_msgs; ++i) {
2403 /* Set/clear a bit for each criterion */
2405 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2406 delete_this |= 0x01;
2408 if (strlen(content_type) == 0) {
2409 delete_this |= 0x02;
2411 GetSuppMsgInfo(&smi, msglist[i]);
2412 if (!strcasecmp(smi.smi_content_type,
2414 delete_this |= 0x02;
2418 /* Delete message only if all bits are set */
2419 if (delete_this == 0x03) {
2420 AdjRefCount(msglist[i], -1);
2426 num_msgs = sort_msglist(msglist, num_msgs);
2427 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2428 msglist, (num_msgs * sizeof(long)));
2430 qrbuf.QRhighest = msglist[num_msgs - 1];
2434 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2435 return (num_deleted);
2441 * Check whether the current user has permission to delete messages from
2442 * the current room (returns 1 for yes, 0 for no)
2444 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2445 getuser(&CC->usersupp, CC->curr_user);
2446 if ((CC->usersupp.axlevel < 6)
2447 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2448 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2449 && (!(CC->internal_pgm))) {
2458 * Delete message from current room
2460 void cmd_dele(char *delstr)
2465 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2466 cprintf("%d Higher access required.\n",
2467 ERROR + HIGHER_ACCESS_REQUIRED);
2470 delnum = extract_long(delstr, 0);
2472 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2475 cprintf("%d %d message%s deleted.\n", OK,
2476 num_deleted, ((num_deleted != 1) ? "s" : ""));
2478 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2484 * Back end API function for moves and deletes
2486 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2489 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2490 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2491 if (err != 0) return(err);
2499 * move or copy a message to another room
2501 void cmd_move(char *args)
2505 struct quickroom qtemp;
2509 num = extract_long(args, 0);
2510 extract(targ, args, 1);
2511 targ[ROOMNAMELEN - 1] = 0;
2512 is_copy = extract_int(args, 2);
2514 getuser(&CC->usersupp, CC->curr_user);
2515 if ((CC->usersupp.axlevel < 6)
2516 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2517 cprintf("%d Higher access required.\n",
2518 ERROR + HIGHER_ACCESS_REQUIRED);
2522 if (getroom(&qtemp, targ) != 0) {
2523 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2527 err = CtdlCopyMsgToRoom(num, targ);
2529 cprintf("%d Cannot store message in %s: error %d\n",
2534 /* Now delete the message from the source room,
2535 * if this is a 'move' rather than a 'copy' operation.
2537 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2539 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2545 * GetSuppMsgInfo() - Get the supplementary record for a message
2547 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2550 struct cdbdata *cdbsmi;
2553 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2554 smibuf->smi_msgnum = msgnum;
2555 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2557 /* Use the negative of the message number for its supp record index */
2558 TheIndex = (0L - msgnum);
2560 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2561 if (cdbsmi == NULL) {
2562 return; /* record not found; go with defaults */
2564 memcpy(smibuf, cdbsmi->ptr,
2565 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2566 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2573 * PutSuppMsgInfo() - (re)write supplementary record for a message
2575 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2579 /* Use the negative of the message number for its supp record index */
2580 TheIndex = (0L - smibuf->smi_msgnum);
2582 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2583 smibuf->smi_msgnum, smibuf->smi_refcount);
2585 cdb_store(CDB_MSGMAIN,
2586 &TheIndex, sizeof(long),
2587 smibuf, sizeof(struct SuppMsgInfo));
2592 * AdjRefCount - change the reference count for a message;
2593 * delete the message if it reaches zero
2595 void AdjRefCount(long msgnum, int incr)
2598 struct SuppMsgInfo smi;
2601 /* This is a *tight* critical section; please keep it that way, as
2602 * it may get called while nested in other critical sections.
2603 * Complicating this any further will surely cause deadlock!
2605 begin_critical_section(S_SUPPMSGMAIN);
2606 GetSuppMsgInfo(&smi, msgnum);
2607 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2608 msgnum, smi.smi_refcount);
2609 smi.smi_refcount += incr;
2610 PutSuppMsgInfo(&smi);
2611 end_critical_section(S_SUPPMSGMAIN);
2612 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2613 msgnum, smi.smi_refcount);
2615 /* If the reference count is now zero, delete the message
2616 * (and its supplementary record as well).
2618 if (smi.smi_refcount == 0) {
2619 lprintf(9, "Deleting message <%ld>\n", msgnum);
2621 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2622 delnum = (0L - msgnum);
2623 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2628 * Write a generic object to this room
2630 * Note: this could be much more efficient. Right now we use two temporary
2631 * files, and still pull the message into memory as with all others.
2633 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2634 char *content_type, /* MIME type of this object */
2635 char *tempfilename, /* Where to fetch it from */
2636 struct usersupp *is_mailbox, /* Mailbox room? */
2637 int is_binary, /* Is encoding necessary? */
2638 int is_unique, /* Del others of this type? */
2639 unsigned int flags /* Internal save flags */
2644 char filename[PATH_MAX];
2647 struct quickroom qrbuf;
2648 char roomname[ROOMNAMELEN];
2649 struct CtdlMessage *msg;
2652 if (is_mailbox != NULL)
2653 MailboxName(roomname, is_mailbox, req_room);
2655 safestrncpy(roomname, req_room, sizeof(roomname));
2656 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2658 strcpy(filename, tmpnam(NULL));
2659 fp = fopen(filename, "w");
2663 tempfp = fopen(tempfilename, "r");
2664 if (tempfp == NULL) {
2670 fprintf(fp, "Content-type: %s\n", content_type);
2671 lprintf(9, "Content-type: %s\n", content_type);
2673 if (is_binary == 0) {
2674 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2675 while (ch = getc(tempfp), ch > 0)
2681 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2684 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2685 tempfilename, filename);
2689 lprintf(9, "Allocating\n");
2690 msg = mallok(sizeof(struct CtdlMessage));
2691 memset(msg, 0, sizeof(struct CtdlMessage));
2692 msg->cm_magic = CTDLMESSAGE_MAGIC;
2693 msg->cm_anon_type = MES_NORMAL;
2694 msg->cm_format_type = 4;
2695 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2696 msg->cm_fields['O'] = strdoop(req_room);
2697 msg->cm_fields['N'] = strdoop(config.c_nodename);
2698 msg->cm_fields['H'] = strdoop(config.c_humannode);
2699 msg->cm_flags = flags;
2701 lprintf(9, "Loading\n");
2702 fp = fopen(filename, "rb");
2703 fseek(fp, 0L, SEEK_END);
2706 msg->cm_fields['M'] = mallok(len);
2707 fread(msg->cm_fields['M'], len, 1, fp);
2711 /* Create the requested room if we have to. */
2712 if (getroom(&qrbuf, roomname) != 0) {
2713 create_room(roomname,
2714 ( (is_mailbox != NULL) ? 5 : 3 ),
2717 /* If the caller specified this object as unique, delete all
2718 * other objects of this type that are currently in the room.
2721 lprintf(9, "Deleted %d other msgs of this type\n",
2722 CtdlDeleteMessages(roomname, 0L, content_type));
2724 /* Now write the data */
2725 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2726 CtdlFreeMessage(msg);
2734 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2735 config_msgnum = msgnum;
2739 char *CtdlGetSysConfig(char *sysconfname) {
2740 char hold_rm[ROOMNAMELEN];
2743 struct CtdlMessage *msg;
2746 strcpy(hold_rm, CC->quickroom.QRname);
2747 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2748 getroom(&CC->quickroom, hold_rm);
2753 /* We want the last (and probably only) config in this room */
2754 begin_critical_section(S_CONFIG);
2755 config_msgnum = (-1L);
2756 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2757 CtdlGetSysConfigBackend, NULL);
2758 msgnum = config_msgnum;
2759 end_critical_section(S_CONFIG);
2765 msg = CtdlFetchMessage(msgnum);
2767 conf = strdoop(msg->cm_fields['M']);
2768 CtdlFreeMessage(msg);
2775 getroom(&CC->quickroom, hold_rm);
2777 lprintf(9, "eggstracting...\n");
2778 if (conf != NULL) do {
2779 extract_token(buf, conf, 0, '\n');
2780 lprintf(9, "eggstracted <%s>\n", buf);
2781 strcpy(conf, &conf[strlen(buf)+1]);
2782 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2787 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2788 char temp[PATH_MAX];
2791 strcpy(temp, tmpnam(NULL));
2793 fp = fopen(temp, "w");
2794 if (fp == NULL) return;
2795 fprintf(fp, "%s", sysconfdata);
2798 /* this handy API function does all the work for us */
2799 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);