4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
38 #include "dynloader.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
49 #include "mime_parser.h"
52 #include "internet_addressing.h"
54 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
55 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
56 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
58 extern struct config config;
62 "", "", "", "", "", "", "", "",
63 "", "", "", "", "", "", "", "",
64 "", "", "", "", "", "", "", "",
65 "", "", "", "", "", "", "", "",
66 "", "", "", "", "", "", "", "",
67 "", "", "", "", "", "", "", "",
68 "", "", "", "", "", "", "", "",
69 "", "", "", "", "", "", "", "",
96 * This function is self explanatory.
97 * (What can I say, I'm in a weird mood today...)
99 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
103 for (i = 0; i < strlen(name); ++i) {
104 if (name[i] == '@') {
105 while (isspace(name[i - 1]) && i > 0) {
106 strcpy(&name[i - 1], &name[i]);
109 while (isspace(name[i + 1])) {
110 strcpy(&name[i + 1], &name[i + 2]);
118 * Aliasing for network mail.
119 * (Error messages have been commented out, because this is a server.)
121 int alias(char *name)
122 { /* process alias and routing info for mail */
125 char aaa[300], bbb[300];
127 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
129 fp = fopen("network/mail.aliases", "r");
131 fp = fopen("/dev/null", "r");
136 while (fgets(aaa, sizeof aaa, fp) != NULL) {
137 while (isspace(name[0]))
138 strcpy(name, &name[1]);
139 aaa[strlen(aaa) - 1] = 0;
141 for (a = 0; a < strlen(aaa); ++a) {
143 strcpy(bbb, &aaa[a + 1]);
147 if (!strcasecmp(name, aaa))
151 lprintf(7, "Mail is being forwarded to %s\n", name);
153 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
154 for (a=0; a<strlen(name); ++a) {
155 if (name[a] == '@') {
156 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
158 lprintf(7, "Changed to <%s>\n", name);
163 /* determine local or remote type, see citadel.h */
164 for (a = 0; a < strlen(name); ++a)
166 return (MES_INTERNET);
167 for (a = 0; a < strlen(name); ++a)
169 for (b = a; b < strlen(name); ++b)
171 return (MES_INTERNET);
173 for (a = 0; a < strlen(name); ++a)
177 lprintf(7, "Too many @'s in address\n");
181 for (a = 0; a < strlen(name); ++a)
183 strcpy(bbb, &name[a + 1]);
185 strcpy(bbb, &bbb[1]);
186 fp = fopen("network/mail.sysinfo", "r");
190 a = getstring(fp, aaa);
191 } while ((a >= 0) && (strcasecmp(aaa, bbb)));
192 a = getstring(fp, aaa);
193 if (!strncmp(aaa, "use ", 4)) {
194 strcpy(bbb, &aaa[4]);
199 if (!strncmp(aaa, "uum", 3)) {
201 for (a = 0; a < strlen(bbb); ++a) {
207 while (bbb[strlen(bbb) - 1] == '_')
208 bbb[strlen(bbb) - 1] = 0;
209 sprintf(name, &aaa[4], bbb);
210 lprintf(9, "returning MES_INTERNET\n");
211 return (MES_INTERNET);
213 if (!strncmp(aaa, "bin", 3)) {
216 while (aaa[strlen(aaa) - 1] != '@')
217 aaa[strlen(aaa) - 1] = 0;
218 aaa[strlen(aaa) - 1] = 0;
219 while (aaa[strlen(aaa) - 1] == ' ')
220 aaa[strlen(aaa) - 1] = 0;
221 while (bbb[0] != '@')
222 strcpy(bbb, &bbb[1]);
223 strcpy(bbb, &bbb[1]);
224 while (bbb[0] == ' ')
225 strcpy(bbb, &bbb[1]);
226 sprintf(name, "%s @%s", aaa, bbb);
227 lprintf(9, "returning MES_BINARY\n");
232 lprintf(9, "returning MES_LOCAL\n");
241 fp = fopen("citadel.control", "r");
242 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
248 void simple_listing(long msgnum, void *userdata)
250 cprintf("%ld\n", msgnum);
255 /* Determine if a given message matches the fields in a message template.
256 * Return 0 for a successful match.
258 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
261 /* If there aren't any fields in the template, all messages will
264 if (template == NULL) return(0);
266 /* Null messages are bogus. */
267 if (msg == NULL) return(1);
269 for (i='A'; i<='Z'; ++i) {
270 if (template->cm_fields[i] != NULL) {
271 if (msg->cm_fields[i] == NULL) {
274 if (strcasecmp(msg->cm_fields[i],
275 template->cm_fields[i])) return 1;
279 /* All compares succeeded: we have a match! */
285 * Manipulate the "seen msgs" string.
287 void CtdlSetSeen(long target_msgnum, int target_setting) {
289 struct cdbdata *cdbfr;
299 /* Learn about the user and room in question */
301 getuser(&CC->usersupp, CC->curr_user);
302 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
304 /* Load the message list */
305 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
307 msglist = mallok(cdbfr->len);
308 memcpy(msglist, cdbfr->ptr, cdbfr->len);
309 num_msgs = cdbfr->len / sizeof(long);
312 return; /* No messages at all? No further action. */
315 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
318 for (i=0; i<num_msgs; ++i) {
321 if (msglist[i] == target_msgnum) {
322 is_seen = target_setting;
325 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
331 if (lo < 0L) lo = msglist[i];
334 if ( ((is_seen == 0) && (was_seen == 1))
335 || ((is_seen == 1) && (i == num_msgs-1)) ) {
336 if ( (strlen(newseen) + 20) > SIZ) {
337 strcpy(newseen, &newseen[20]);
340 if (strlen(newseen) > 0) strcat(newseen, ",");
342 sprintf(&newseen[strlen(newseen)], "%ld", lo);
345 sprintf(&newseen[strlen(newseen)], "%ld:%ld",
354 safestrncpy(vbuf.v_seen, newseen, SIZ);
355 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
357 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
362 * API function to perform an operation for each qualifying message in the
363 * current room. (Returns the number of messages processed.)
365 int CtdlForEachMessage(int mode, long ref,
366 int moderation_level,
368 struct CtdlMessage *compare,
369 void (*CallBack) (long, void *),
375 struct cdbdata *cdbfr;
376 long *msglist = NULL;
378 int num_processed = 0;
380 struct SuppMsgInfo smi;
381 struct CtdlMessage *msg;
384 int printed_lastold = 0;
386 /* Learn about the user and room in question */
388 getuser(&CC->usersupp, CC->curr_user);
389 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
391 /* Load the message list */
392 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
394 msglist = mallok(cdbfr->len);
395 memcpy(msglist, cdbfr->ptr, cdbfr->len);
396 num_msgs = cdbfr->len / sizeof(long);
399 return 0; /* No messages at all? No further action. */
404 * Now begin the traversal.
406 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
407 GetSuppMsgInfo(&smi, msglist[a]);
409 /* Filter out messages that are moderated below the level
410 * currently being viewed at.
412 if (smi.smi_mod < moderation_level) {
416 /* If the caller is looking for a specific MIME type, filter
417 * out all messages which are not of the type requested.
419 if (content_type != NULL) if (strlen(content_type) > 0) {
420 if (strcasecmp(smi.smi_content_type, content_type)) {
426 num_msgs = sort_msglist(msglist, num_msgs);
428 /* If a template was supplied, filter out the messages which
429 * don't match. (This could induce some delays!)
432 if (compare != NULL) {
433 for (a = 0; a < num_msgs; ++a) {
434 msg = CtdlFetchMessage(msglist[a]);
436 if (CtdlMsgCmp(msg, compare)) {
439 CtdlFreeMessage(msg);
447 * Now iterate through the message list, according to the
448 * criteria supplied by the caller.
451 for (a = 0; a < num_msgs; ++a) {
452 thismsg = msglist[a];
453 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
454 if (is_seen) lastold = thismsg;
459 || ((mode == MSGS_OLD) && (is_seen))
460 || ((mode == MSGS_NEW) && (!is_seen))
461 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
462 || ((mode == MSGS_FIRST) && (a < ref))
463 || ((mode == MSGS_GT) && (thismsg > ref))
464 || ((mode == MSGS_EQ) && (thismsg == ref))
467 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
469 CallBack(lastold, userdata);
473 if (CallBack) CallBack(thismsg, userdata);
477 phree(msglist); /* Clean up */
478 return num_processed;
484 * cmd_msgs() - get list of message #'s in this room
485 * implements the MSGS server command using CtdlForEachMessage()
487 void cmd_msgs(char *cmdbuf)
496 int with_template = 0;
497 struct CtdlMessage *template = NULL;
499 extract(which, cmdbuf, 0);
500 cm_ref = extract_int(cmdbuf, 1);
501 with_template = extract_int(cmdbuf, 2);
505 if (!strncasecmp(which, "OLD", 3))
507 else if (!strncasecmp(which, "NEW", 3))
509 else if (!strncasecmp(which, "FIRST", 5))
511 else if (!strncasecmp(which, "LAST", 4))
513 else if (!strncasecmp(which, "GT", 2))
516 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
517 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
522 cprintf("%d Send template then receive message list\n",
524 template = (struct CtdlMessage *)
525 mallok(sizeof(struct CtdlMessage));
526 memset(template, 0, sizeof(struct CtdlMessage));
527 while(client_gets(buf), strcmp(buf,"000")) {
528 extract(tfield, buf, 0);
529 extract(tvalue, buf, 1);
530 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
531 if (!strcasecmp(tfield, msgkeys[i])) {
532 template->cm_fields[i] =
539 cprintf("%d Message list...\n", LISTING_FOLLOWS);
542 CtdlForEachMessage(mode, cm_ref,
543 CC->usersupp.moderation_filter,
544 NULL, template, simple_listing, NULL);
545 if (template != NULL) CtdlFreeMessage(template);
553 * help_subst() - support routine for help file viewer
555 void help_subst(char *strbuf, char *source, char *dest)
560 while (p = pattern2(strbuf, source), (p >= 0)) {
561 strcpy(workbuf, &strbuf[p + strlen(source)]);
562 strcpy(&strbuf[p], dest);
563 strcat(strbuf, workbuf);
568 void do_help_subst(char *buffer)
572 help_subst(buffer, "^nodename", config.c_nodename);
573 help_subst(buffer, "^humannode", config.c_humannode);
574 help_subst(buffer, "^fqdn", config.c_fqdn);
575 help_subst(buffer, "^username", CC->usersupp.fullname);
576 sprintf(buf2, "%ld", CC->usersupp.usernum);
577 help_subst(buffer, "^usernum", buf2);
578 help_subst(buffer, "^sysadm", config.c_sysadm);
579 help_subst(buffer, "^variantname", CITADEL);
580 sprintf(buf2, "%d", config.c_maxsessions);
581 help_subst(buffer, "^maxsessions", buf2);
587 * memfmout() - Citadel text formatter and paginator.
588 * Although the original purpose of this routine was to format
589 * text to the reader's screen width, all we're really using it
590 * for here is to format text out to 80 columns before sending it
591 * to the client. The client software may reformat it again.
594 int width, /* screen width to use */
595 char *mptr, /* where are we going to get our text from? */
596 char subst, /* nonzero if we should do substitutions */
597 char *nl) /* string to terminate lines with */
609 c = 1; /* c is the current pos */
613 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
615 buffer[strlen(buffer) + 1] = 0;
616 buffer[strlen(buffer)] = ch;
619 if (buffer[0] == '^')
620 do_help_subst(buffer);
622 buffer[strlen(buffer) + 1] = 0;
624 strcpy(buffer, &buffer[1]);
632 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
634 if (((old == 13) || (old == 10)) && (isspace(real))) {
642 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
643 cprintf("%s%s", nl, aaa);
652 if ((strlen(aaa) + c) > (width - 5)) {
661 if ((ch == 13) || (ch == 10)) {
662 cprintf("%s%s", aaa, nl);
669 cprintf("%s%s", aaa, nl);
675 * Callback function for mime parser that simply lists the part
677 void list_this_part(char *name, char *filename, char *partnum, char *disp,
678 void *content, char *cbtype, size_t length, char *encoding,
682 cprintf("part=%s|%s|%s|%s|%s|%d\n",
683 name, filename, partnum, disp, cbtype, length);
688 * Callback function for mime parser that opens a section for downloading
690 void mime_download(char *name, char *filename, char *partnum, char *disp,
691 void *content, char *cbtype, size_t length, char *encoding,
695 /* Silently go away if there's already a download open... */
696 if (CC->download_fp != NULL)
699 /* ...or if this is not the desired section */
700 if (strcasecmp(desired_section, partnum))
703 CC->download_fp = tmpfile();
704 if (CC->download_fp == NULL)
707 fwrite(content, length, 1, CC->download_fp);
708 fflush(CC->download_fp);
709 rewind(CC->download_fp);
711 OpenCmdResult(filename, cbtype);
717 * Load a message from disk into memory.
718 * This is used by CtdlOutputMsg() and other fetch functions.
720 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
721 * using the CtdlMessageFree() function.
723 struct CtdlMessage *CtdlFetchMessage(long msgnum)
725 struct cdbdata *dmsgtext;
726 struct CtdlMessage *ret = NULL;
729 CIT_UBYTE field_header;
732 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
733 if (dmsgtext == NULL) {
736 mptr = dmsgtext->ptr;
738 /* Parse the three bytes that begin EVERY message on disk.
739 * The first is always 0xFF, the on-disk magic number.
740 * The second is the anonymous/public type byte.
741 * The third is the format type byte (vari, fixed, or MIME).
745 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
749 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
750 memset(ret, 0, sizeof(struct CtdlMessage));
752 ret->cm_magic = CTDLMESSAGE_MAGIC;
753 ret->cm_anon_type = *mptr++; /* Anon type byte */
754 ret->cm_format_type = *mptr++; /* Format type byte */
757 * The rest is zero or more arbitrary fields. Load them in.
758 * We're done when we encounter either a zero-length field or
759 * have just processed the 'M' (message text) field.
762 field_length = strlen(mptr);
763 if (field_length == 0)
765 field_header = *mptr++;
766 ret->cm_fields[field_header] = mallok(field_length);
767 strcpy(ret->cm_fields[field_header], mptr);
769 while (*mptr++ != 0); /* advance to next field */
771 } while ((field_length > 0) && (field_header != 'M'));
775 /* Always make sure there's something in the msg text field */
776 if (ret->cm_fields['M'] == NULL)
777 ret->cm_fields['M'] = strdoop("<no text>\n");
779 /* Perform "before read" hooks (aborting if any return nonzero) */
780 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
781 CtdlFreeMessage(ret);
790 * Returns 1 if the supplied pointer points to a valid Citadel message.
791 * If the pointer is NULL or the magic number check fails, returns 0.
793 int is_valid_message(struct CtdlMessage *msg) {
796 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
797 lprintf(3, "is_valid_message() -- self-check failed\n");
805 * 'Destructor' for struct CtdlMessage
807 void CtdlFreeMessage(struct CtdlMessage *msg)
811 if (is_valid_message(msg) == 0) return;
813 for (i = 0; i < 256; ++i)
814 if (msg->cm_fields[i] != NULL) {
815 phree(msg->cm_fields[i]);
818 msg->cm_magic = 0; /* just in case */
824 * Callback function for mime parser that wants to display text
826 void fixed_output(char *name, char *filename, char *partnum, char *disp,
827 void *content, char *cbtype, size_t length, char *encoding,
835 if (!strcasecmp(cbtype, "multipart/alternative")) {
836 strcpy(ma->prefix, partnum);
837 strcat(ma->prefix, ".");
843 if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix)))
845 && (ma->did_print == 1) ) {
846 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
852 if ( (!strcasecmp(cbtype, "text/plain"))
853 || (strlen(cbtype)==0) ) {
859 if (ch==10) cprintf("\r\n");
860 else cprintf("%c", ch);
864 if (ch != '\n') cprintf("\n");
866 else if (!strcasecmp(cbtype, "text/html")) {
867 ptr = html_to_ascii(content, 80, 0);
872 if (ch==10) cprintf("\r\n");
873 else cprintf("%c", ch);
877 else if (strncasecmp(cbtype, "multipart/", 10)) {
878 cprintf("Part %s: %s (%s) (%d bytes)\r\n",
879 partnum, filename, cbtype, length);
885 * Get a message off disk. (returns om_* values found in msgbase.h)
888 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
889 int mode, /* how would you like that message? */
890 int headers_only, /* eschew the message body? */
891 int do_proto, /* do Citadel protocol responses? */
892 int crlf /* Use CRLF newlines instead of LF? */
894 struct CtdlMessage *TheMessage;
897 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
902 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
903 if (do_proto) cprintf("%d Not logged in.\n",
904 ERROR + NOT_LOGGED_IN);
905 return(om_not_logged_in);
908 /* FIXME ... small security issue
909 * We need to check to make sure the requested message is actually
910 * in the current room, and set msg_ok to 1 only if it is. This
911 * functionality is currently missing because I'm in a hurry to replace
912 * broken production code with nonbroken pre-beta code. :( -- ajc
915 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
917 return(om_no_such_msg);
922 * Fetch the message from disk
924 TheMessage = CtdlFetchMessage(msg_num);
925 if (TheMessage == NULL) {
926 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
928 return(om_no_such_msg);
931 retcode = CtdlOutputPreLoadedMsg(
932 TheMessage, msg_num, mode,
933 headers_only, do_proto, crlf);
935 CtdlFreeMessage(TheMessage);
941 * Get a message off disk. (returns om_* values found in msgbase.h)
944 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
946 int mode, /* how would you like that message? */
947 int headers_only, /* eschew the message body? */
948 int do_proto, /* do Citadel protocol responses? */
949 int crlf /* Use CRLF newlines instead of LF? */
955 char display_name[SIZ];
957 char *nl; /* newline string */
959 /* buffers needed for RFC822 translation */
969 sprintf(mid, "%ld", msg_num);
970 nl = (crlf ? "\r\n" : "\n");
972 if (!is_valid_message(TheMessage)) {
973 lprintf(1, "ERROR: invalid preloaded message for output\n");
974 return(om_no_such_msg);
977 /* Are we downloading a MIME component? */
978 if (mode == MT_DOWNLOAD) {
979 if (TheMessage->cm_format_type != FMT_RFC822) {
981 cprintf("%d This is not a MIME message.\n",
983 } else if (CC->download_fp != NULL) {
984 if (do_proto) cprintf(
985 "%d You already have a download open.\n",
988 /* Parse the message text component */
989 mptr = TheMessage->cm_fields['M'];
990 mime_parser(mptr, NULL,
991 *mime_download, NULL, NULL,
993 /* If there's no file open by this time, the requested
994 * section wasn't found, so print an error
996 if (CC->download_fp == NULL) {
997 if (do_proto) cprintf(
998 "%d Section %s not found.\n",
999 ERROR + FILE_NOT_FOUND,
1003 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1006 /* now for the user-mode message reading loops */
1007 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1009 /* Tell the client which format type we're using. If this is a
1010 * MIME message, *lie* about it and tell the user it's fixed-format.
1012 if (mode == MT_CITADEL) {
1013 if (TheMessage->cm_format_type == FMT_RFC822) {
1014 if (do_proto) cprintf("type=1\n");
1017 if (do_proto) cprintf("type=%d\n",
1018 TheMessage->cm_format_type);
1022 /* nhdr=yes means that we're only displaying headers, no body */
1023 if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) {
1024 if (do_proto) cprintf("nhdr=yes\n");
1027 /* begin header processing loop for Citadel message format */
1029 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1031 strcpy(display_name, "<unknown>");
1032 if (TheMessage->cm_fields['A']) {
1033 strcpy(buf, TheMessage->cm_fields['A']);
1034 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1035 if (TheMessage->cm_anon_type == MES_ANON)
1036 strcpy(display_name, "****");
1037 else if (TheMessage->cm_anon_type == MES_AN2)
1038 strcpy(display_name, "anonymous");
1040 strcpy(display_name, buf);
1041 if ((is_room_aide())
1042 && ((TheMessage->cm_anon_type == MES_ANON)
1043 || (TheMessage->cm_anon_type == MES_AN2))) {
1044 sprintf(&display_name[strlen(display_name)],
1049 strcpy(allkeys, FORDER);
1050 for (i=0; i<strlen(allkeys); ++i) {
1051 k = (int) allkeys[i];
1053 if (TheMessage->cm_fields[k] != NULL) {
1055 if (do_proto) cprintf("%s=%s\n",
1060 if (do_proto) cprintf("%s=%s\n",
1062 TheMessage->cm_fields[k]
1071 /* begin header processing loop for RFC822 transfer format */
1076 strcpy(snode, NODENAME);
1077 strcpy(lnode, HUMANNODE);
1078 if (mode == MT_RFC822) {
1079 cprintf("X-UIDL: %ld%s", msg_num, nl);
1080 for (i = 0; i < 256; ++i) {
1081 if (TheMessage->cm_fields[i]) {
1082 mptr = TheMessage->cm_fields[i];
1085 strcpy(luser, mptr);
1086 strcpy(suser, mptr);
1089 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1090 into thinking that mail messages are newsgroup messages instead. When we
1091 add NNTP support back into Citadel we'll have to add code to only output
1092 this field when appropriate.
1093 else if (i == 'P') {
1094 cprintf("Path: %s%s", mptr, nl);
1098 cprintf("Subject: %s%s", mptr, nl);
1102 strcpy(lnode, mptr);
1104 cprintf("X-Citadel-Room: %s%s",
1107 strcpy(snode, mptr);
1109 cprintf("To: %s%s", mptr, nl);
1110 else if (i == 'T') {
1111 datestring(datestamp, atol(mptr),
1112 DATESTRING_RFC822 );
1113 cprintf("Date: %s%s", datestamp, nl);
1119 for (i=0; i<strlen(suser); ++i) {
1120 suser[i] = tolower(suser[i]);
1121 if (!isalnum(suser[i])) suser[i]='_';
1124 if (mode == MT_RFC822) {
1125 if (!strcasecmp(snode, NODENAME)) {
1126 strcpy(snode, FQDN);
1129 /* Construct a fun message id */
1130 cprintf("Message-ID: <%s", mid);
1131 if (strchr(mid, '@')==NULL) {
1132 cprintf("@%s", snode);
1136 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1138 if (strlen(fuser) > 0) {
1139 cprintf("From: %s (%s)%s", fuser, luser, nl);
1142 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1145 cprintf("Organization: %s%s", lnode, nl);
1148 /* end header processing loop ... at this point, we're in the text */
1150 mptr = TheMessage->cm_fields['M'];
1152 /* Tell the client about the MIME parts in this message */
1153 if (TheMessage->cm_format_type == FMT_RFC822) {
1154 if (mode == MT_CITADEL) {
1155 mime_parser(mptr, NULL,
1156 *list_this_part, NULL, NULL,
1159 else if (mode == MT_MIME) { /* list parts only */
1160 mime_parser(mptr, NULL,
1161 *list_this_part, NULL, NULL,
1163 if (do_proto) cprintf("000\n");
1166 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1167 /* FIXME ... we have to put some code in here to avoid
1168 * printing duplicate header information when both
1169 * Citadel and RFC822 headers exist. Preference should
1170 * probably be given to the RFC822 headers.
1172 while (ch=*(mptr++), ch!=0) {
1174 else if (ch==10) cprintf("%s", nl);
1175 else cprintf("%c", ch);
1177 if (do_proto) cprintf("000\n");
1183 if (do_proto) cprintf("000\n");
1187 /* signify start of msg text */
1188 if (mode == MT_CITADEL)
1189 if (do_proto) cprintf("text\n");
1190 if (mode == MT_RFC822) {
1191 if (TheMessage->cm_fields['U'] == NULL) {
1192 cprintf("Subject: (no subject)%s", nl);
1197 /* If the format type on disk is 1 (fixed-format), then we want
1198 * everything to be output completely literally ... regardless of
1199 * what message transfer format is in use.
1201 if (TheMessage->cm_format_type == FMT_FIXED) {
1203 while (ch = *mptr++, ch > 0) {
1206 if ((ch == 10) || (strlen(buf) > 250)) {
1207 cprintf("%s%s", buf, nl);
1210 buf[strlen(buf) + 1] = 0;
1211 buf[strlen(buf)] = ch;
1214 if (strlen(buf) > 0)
1215 cprintf("%s%s", buf, nl);
1218 /* If the message on disk is format 0 (Citadel vari-format), we
1219 * output using the formatter at 80 columns. This is the final output
1220 * form if the transfer format is RFC822, but if the transfer format
1221 * is Citadel proprietary, it'll still work, because the indentation
1222 * for new paragraphs is correct and the client will reformat the
1223 * message to the reader's screen width.
1225 if (TheMessage->cm_format_type == FMT_CITADEL) {
1226 memfmout(80, mptr, 0, nl);
1229 /* If the message on disk is format 4 (MIME), we've gotta hand it
1230 * off to the MIME parser. The client has already been told that
1231 * this message is format 1 (fixed format), so the callback function
1232 * we use will display those parts as-is.
1234 if (TheMessage->cm_format_type == FMT_RFC822) {
1235 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1236 memset(ma, 0, sizeof(struct ma_info));
1237 mime_parser(mptr, NULL,
1238 *fixed_output, NULL, NULL,
1242 /* now we're done */
1243 if (do_proto) cprintf("000\n");
1250 * display a message (mode 0 - Citadel proprietary)
1252 void cmd_msg0(char *cmdbuf)
1255 int headers_only = 0;
1257 msgid = extract_long(cmdbuf, 0);
1258 headers_only = extract_int(cmdbuf, 1);
1260 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1266 * display a message (mode 2 - RFC822)
1268 void cmd_msg2(char *cmdbuf)
1271 int headers_only = 0;
1273 msgid = extract_long(cmdbuf, 0);
1274 headers_only = extract_int(cmdbuf, 1);
1276 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1282 * display a message (mode 3 - IGnet raw format - internal programs only)
1284 void cmd_msg3(char *cmdbuf)
1287 struct CtdlMessage *msg;
1290 if (CC->internal_pgm == 0) {
1291 cprintf("%d This command is for internal programs only.\n",
1296 msgnum = extract_long(cmdbuf, 0);
1297 msg = CtdlFetchMessage(msgnum);
1299 cprintf("%d Message %ld not found.\n",
1304 serialize_message(&smr, msg);
1305 CtdlFreeMessage(msg);
1308 cprintf("%d Unable to serialize message\n",
1309 ERROR+INTERNAL_ERROR);
1313 cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len);
1314 client_write(smr.ser, smr.len);
1321 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1323 void cmd_msg4(char *cmdbuf)
1327 msgid = extract_long(cmdbuf, 0);
1328 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1332 * Open a component of a MIME message as a download file
1334 void cmd_opna(char *cmdbuf)
1338 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1340 msgid = extract_long(cmdbuf, 0);
1341 extract(desired_section, cmdbuf, 1);
1343 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1348 * Save a message pointer into a specified room
1349 * (Returns 0 for success, nonzero for failure)
1350 * roomname may be NULL to use the current room
1352 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1354 char hold_rm[ROOMNAMELEN];
1355 struct cdbdata *cdbfr;
1358 long highest_msg = 0L;
1359 struct CtdlMessage *msg = NULL;
1361 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1362 roomname, msgid, flags);
1364 strcpy(hold_rm, CC->quickroom.QRname);
1366 /* We may need to check to see if this message is real */
1367 if ( (flags & SM_VERIFY_GOODNESS)
1368 || (flags & SM_DO_REPL_CHECK)
1370 msg = CtdlFetchMessage(msgid);
1371 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1374 /* Perform replication checks if necessary */
1375 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1377 if (getroom(&CC->quickroom,
1378 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1380 lprintf(9, "No such room <%s>\n", roomname);
1381 if (msg != NULL) CtdlFreeMessage(msg);
1382 return(ERROR + ROOM_NOT_FOUND);
1385 if (ReplicationChecks(msg) != 0) {
1386 getroom(&CC->quickroom, hold_rm);
1387 if (msg != NULL) CtdlFreeMessage(msg);
1388 lprintf(9, "Did replication, and newer exists\n");
1393 /* Now the regular stuff */
1394 if (lgetroom(&CC->quickroom,
1395 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1397 lprintf(9, "No such room <%s>\n", roomname);
1398 if (msg != NULL) CtdlFreeMessage(msg);
1399 return(ERROR + ROOM_NOT_FOUND);
1402 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1403 if (cdbfr == NULL) {
1407 msglist = mallok(cdbfr->len);
1408 if (msglist == NULL)
1409 lprintf(3, "ERROR malloc msglist!\n");
1410 num_msgs = cdbfr->len / sizeof(long);
1411 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1416 /* Make sure the message doesn't already exist in this room. It
1417 * is absolutely taboo to have more than one reference to the same
1418 * message in a room.
1420 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1421 if (msglist[i] == msgid) {
1422 lputroom(&CC->quickroom); /* unlock the room */
1423 getroom(&CC->quickroom, hold_rm);
1424 if (msg != NULL) CtdlFreeMessage(msg);
1425 return(ERROR + ALREADY_EXISTS);
1429 /* Now add the new message */
1431 msglist = reallok(msglist,
1432 (num_msgs * sizeof(long)));
1434 if (msglist == NULL) {
1435 lprintf(3, "ERROR: can't realloc message list!\n");
1437 msglist[num_msgs - 1] = msgid;
1439 /* Sort the message list, so all the msgid's are in order */
1440 num_msgs = sort_msglist(msglist, num_msgs);
1442 /* Determine the highest message number */
1443 highest_msg = msglist[num_msgs - 1];
1445 /* Write it back to disk. */
1446 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1447 msglist, num_msgs * sizeof(long));
1449 /* Free up the memory we used. */
1452 /* Update the highest-message pointer and unlock the room. */
1453 CC->quickroom.QRhighest = highest_msg;
1454 lputroom(&CC->quickroom);
1455 getroom(&CC->quickroom, hold_rm);
1457 /* Bump the reference count for this message. */
1458 if ((flags & SM_DONT_BUMP_REF)==0) {
1459 AdjRefCount(msgid, +1);
1462 /* Return success. */
1463 if (msg != NULL) CtdlFreeMessage(msg);
1470 * Message base operation to send a message to the master file
1471 * (returns new message number)
1473 * This is the back end for CtdlSaveMsg() and should not be directly
1474 * called by server-side modules.
1477 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1478 FILE *save_a_copy) /* save a copy to disk? */
1485 /* Get a new message number */
1486 newmsgid = get_new_message_number();
1487 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1489 /* Generate an ID if we don't have one already */
1490 if (msg->cm_fields['I']==NULL) {
1491 msg->cm_fields['I'] = strdoop(msgidbuf);
1494 serialize_message(&smr, msg);
1497 cprintf("%d Unable to serialize message\n",
1498 ERROR+INTERNAL_ERROR);
1502 /* Write our little bundle of joy into the message base */
1503 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1504 smr.ser, smr.len) < 0) {
1505 lprintf(2, "Can't store message\n");
1511 /* If the caller specified that a copy should be saved to a particular
1512 * file handle, do that now too.
1514 if (save_a_copy != NULL) {
1515 fwrite(smr.ser, smr.len, 1, save_a_copy);
1518 /* Free the memory we used for the serialized message */
1521 /* Return the *local* message ID to the caller
1522 * (even if we're storing an incoming network message)
1530 * Serialize a struct CtdlMessage into the format used on disk and network.
1532 * This function loads up a "struct ser_ret" (defined in server.h) which
1533 * contains the length of the serialized message and a pointer to the
1534 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1536 void serialize_message(struct ser_ret *ret, /* return values */
1537 struct CtdlMessage *msg) /* unserialized msg */
1541 static char *forder = FORDER;
1543 if (is_valid_message(msg) == 0) return; /* self check */
1546 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1547 ret->len = ret->len +
1548 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1550 lprintf(9, "calling malloc(%d)\n", ret->len);
1551 ret->ser = mallok(ret->len);
1552 if (ret->ser == NULL) {
1558 ret->ser[1] = msg->cm_anon_type;
1559 ret->ser[2] = msg->cm_format_type;
1562 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1563 ret->ser[wlen++] = (char)forder[i];
1564 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1565 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1567 if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n",
1576 * Back end for the ReplicationChecks() function
1578 void check_repl(long msgnum, void *userdata) {
1579 struct CtdlMessage *msg;
1580 time_t timestamp = (-1L);
1582 lprintf(9, "check_repl() found message %ld\n", msgnum);
1583 msg = CtdlFetchMessage(msgnum);
1584 if (msg == NULL) return;
1585 if (msg->cm_fields['T'] != NULL) {
1586 timestamp = atol(msg->cm_fields['T']);
1588 CtdlFreeMessage(msg);
1590 if (timestamp > msg_repl->highest) {
1591 msg_repl->highest = timestamp; /* newer! */
1592 lprintf(9, "newer!\n");
1595 lprintf(9, "older!\n");
1597 /* Existing isn't newer? Then delete the old one(s). */
1598 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1603 * Check to see if any messages already exist which carry the same Extended ID
1607 * -> With older timestamps: delete them and return 0. Message will be saved.
1608 * -> With newer timestamps: return 1. Message save will be aborted.
1610 int ReplicationChecks(struct CtdlMessage *msg) {
1611 struct CtdlMessage *template;
1614 lprintf(9, "ReplicationChecks() started\n");
1615 /* No extended id? Don't do anything. */
1616 if (msg->cm_fields['E'] == NULL) return 0;
1617 if (strlen(msg->cm_fields['E']) == 0) return 0;
1618 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1620 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1621 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1622 msg_repl->highest = atol(msg->cm_fields['T']);
1624 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1625 memset(template, 0, sizeof(struct CtdlMessage));
1626 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1628 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1631 /* If a newer message exists with the same Extended ID, abort
1634 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1638 CtdlFreeMessage(template);
1639 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1647 * Save a message to disk
1649 long CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */
1650 char *rec, /* Recipient (mail) */
1651 char *force, /* force a particular room? */
1652 int supplied_mailtype) /* local or remote type */
1655 char hold_rm[ROOMNAMELEN];
1656 char actual_rm[ROOMNAMELEN];
1657 char force_room[ROOMNAMELEN];
1658 char content_type[SIZ]; /* We have to learn this */
1659 char recipient[SIZ];
1662 struct usersupp userbuf;
1664 struct SuppMsgInfo smi;
1665 FILE *network_fp = NULL;
1666 static int seqnum = 1;
1667 struct CtdlMessage *imsg;
1671 lprintf(9, "CtdlSaveMsg() called\n");
1672 if (is_valid_message(msg) == 0) return(-1); /* self check */
1673 mailtype = supplied_mailtype;
1675 /* If this message has no timestamp, we take the liberty of
1676 * giving it one, right now.
1678 if (msg->cm_fields['T'] == NULL) {
1679 lprintf(9, "Generating timestamp\n");
1680 sprintf(aaa, "%ld", time(NULL));
1681 msg->cm_fields['T'] = strdoop(aaa);
1684 /* If this message has no path, we generate one.
1686 if (msg->cm_fields['P'] == NULL) {
1687 lprintf(9, "Generating path\n");
1688 if (msg->cm_fields['A'] != NULL) {
1689 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1690 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1691 if (isspace(msg->cm_fields['P'][a])) {
1692 msg->cm_fields['P'][a] = ' ';
1697 msg->cm_fields['P'] = strdoop("unknown");
1701 strcpy(force_room, force);
1703 /* Strip non-printable characters out of the recipient name */
1704 lprintf(9, "Checking recipient (if present)\n");
1705 strcpy(recipient, rec);
1706 for (a = 0; a < strlen(recipient); ++a)
1707 if (!isprint(recipient[a]))
1708 strcpy(&recipient[a], &recipient[a + 1]);
1710 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
1711 for (a=0; a<strlen(recipient); ++a) {
1712 if (recipient[a] == '@') {
1713 if (CtdlHostAlias(&recipient[a+1])
1714 == hostalias_localhost) {
1716 lprintf(7, "Changed to <%s>\n", recipient);
1717 mailtype = MES_LOCAL;
1722 lprintf(9, "Recipient is <%s>\n", recipient);
1724 /* Learn about what's inside, because it's what's inside that counts */
1725 lprintf(9, "Learning what's inside\n");
1726 if (msg->cm_fields['M'] == NULL) {
1727 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1730 switch (msg->cm_format_type) {
1732 strcpy(content_type, "text/x-citadel-variformat");
1735 strcpy(content_type, "text/plain");
1738 strcpy(content_type, "text/plain");
1739 /* advance past header fields */
1740 mptr = msg->cm_fields['M'];
1743 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1744 safestrncpy(content_type, mptr,
1745 sizeof(content_type));
1746 strcpy(content_type, &content_type[14]);
1747 for (a = 0; a < strlen(content_type); ++a)
1748 if ((content_type[a] == ';')
1749 || (content_type[a] == ' ')
1750 || (content_type[a] == 13)
1751 || (content_type[a] == 10))
1752 content_type[a] = 0;
1759 /* Goto the correct room */
1760 lprintf(9, "Switching rooms\n");
1761 strcpy(hold_rm, CC->quickroom.QRname);
1762 strcpy(actual_rm, CC->quickroom.QRname);
1764 /* If the user is a twit, move to the twit room for posting */
1765 lprintf(9, "Handling twit stuff\n");
1767 if (CC->usersupp.axlevel == 2) {
1768 strcpy(hold_rm, actual_rm);
1769 strcpy(actual_rm, config.c_twitroom);
1773 /* ...or if this message is destined for Aide> then go there. */
1774 if (strlen(force_room) > 0) {
1775 strcpy(actual_rm, force_room);
1778 lprintf(9, "Possibly relocating\n");
1779 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1780 getroom(&CC->quickroom, actual_rm);
1784 * If this message has no O (room) field, generate one.
1786 if (msg->cm_fields['O'] == NULL) {
1787 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1790 /* Perform "before save" hooks (aborting if any return nonzero) */
1791 lprintf(9, "Performing before-save hooks\n");
1792 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1794 /* If this message has an Extended ID, perform replication checks */
1795 lprintf(9, "Performing replication checks\n");
1796 if (ReplicationChecks(msg) > 0) return(-1);
1798 /* Network mail - send a copy to the network program. */
1799 if ((strlen(recipient) > 0) && (mailtype == MES_BINARY)) {
1800 lprintf(9, "Sending network spool\n");
1801 sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x",
1802 (long) getpid(), CC->cs_pid, ++seqnum);
1803 lprintf(9, "Saving a copy to %s\n", aaa);
1804 network_fp = fopen(aaa, "ab+");
1805 if (network_fp == NULL)
1806 lprintf(2, "ERROR: %s\n", strerror(errno));
1809 /* Save it to disk */
1810 lprintf(9, "Saving to disk\n");
1811 newmsgid = send_message(msg, network_fp);
1812 if (network_fp != NULL) {
1814 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
1817 if (newmsgid <= 0L) return(-1);
1819 /* Write a supplemental message info record. This doesn't have to
1820 * be a critical section because nobody else knows about this message
1823 lprintf(9, "Creating SuppMsgInfo record\n");
1824 memset(&smi, 0, sizeof(struct SuppMsgInfo));
1825 smi.smi_msgnum = newmsgid;
1826 smi.smi_refcount = 0;
1827 safestrncpy(smi.smi_content_type, content_type, 64);
1828 PutSuppMsgInfo(&smi);
1830 /* Now figure out where to store the pointers */
1831 lprintf(9, "Storing pointers\n");
1833 /* If this is being done by the networker delivering a private
1834 * message, we want to BYPASS saving the sender's copy (because there
1835 * is no local sender; it would otherwise go to the Trashcan).
1837 if ((!CC->internal_pgm) || (strlen(recipient) == 0)) {
1838 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1839 lprintf(3, "ERROR saving message pointer!\n");
1840 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1844 /* For internet mail, drop a copy in the outbound queue room */
1845 if (mailtype == MES_INTERNET) {
1846 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1849 /* Bump this user's messages posted counter. */
1850 lprintf(9, "Updating user\n");
1851 lgetuser(&CC->usersupp, CC->curr_user);
1852 CC->usersupp.posted = CC->usersupp.posted + 1;
1853 lputuser(&CC->usersupp);
1855 /* If this is private, local mail, make a copy in the
1856 * recipient's mailbox and bump the reference count.
1858 if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) {
1859 if (getuser(&userbuf, recipient) == 0) {
1860 lprintf(9, "Delivering private mail\n");
1861 MailboxName(actual_rm, &userbuf, MAILROOM);
1862 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1865 lprintf(9, "No user <%s>, saving in %s> instead\n",
1866 recipient, AIDEROOM);
1867 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1871 /* Perform "after save" hooks */
1872 lprintf(9, "Performing after-save hooks\n");
1873 PerformMessageHooks(msg, EVT_AFTERSAVE);
1876 lprintf(9, "Returning to original room\n");
1877 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1878 getroom(&CC->quickroom, hold_rm);
1880 /* For internet mail, generate delivery instructions
1881 * (Yes, this is recursive! Deal with it!)
1883 if (mailtype == MES_INTERNET) {
1884 lprintf(9, "Generating delivery instructions\n");
1885 instr = mallok(2048);
1887 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1890 SPOOLMIME, newmsgid, time(NULL),
1891 msg->cm_fields['A'], msg->cm_fields['N'],
1894 imsg = mallok(sizeof(struct CtdlMessage));
1895 memset(imsg, 0, sizeof(struct CtdlMessage));
1896 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1897 imsg->cm_anon_type = MES_NORMAL;
1898 imsg->cm_format_type = FMT_RFC822;
1899 imsg->cm_fields['A'] = strdoop("Citadel");
1900 imsg->cm_fields['M'] = instr;
1901 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL);
1902 CtdlFreeMessage(imsg);
1911 * Convenience function for generating small administrative messages.
1913 void quickie_message(char *from, char *to, char *room, char *text)
1915 struct CtdlMessage *msg;
1917 msg = mallok(sizeof(struct CtdlMessage));
1918 memset(msg, 0, sizeof(struct CtdlMessage));
1919 msg->cm_magic = CTDLMESSAGE_MAGIC;
1920 msg->cm_anon_type = MES_NORMAL;
1921 msg->cm_format_type = 0;
1922 msg->cm_fields['A'] = strdoop(from);
1923 msg->cm_fields['O'] = strdoop(room);
1924 msg->cm_fields['N'] = strdoop(NODENAME);
1926 msg->cm_fields['R'] = strdoop(to);
1927 msg->cm_fields['M'] = strdoop(text);
1929 CtdlSaveMsg(msg, "", room, MES_LOCAL);
1930 CtdlFreeMessage(msg);
1931 syslog(LOG_NOTICE, text);
1937 * Back end function used by make_message() and similar functions
1939 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1940 size_t maxlen, /* maximum message length */
1941 char *exist /* if non-null, append to it;
1942 exist is ALWAYS freed */
1946 size_t message_len = 0;
1947 size_t buffer_len = 0;
1951 if (exist == NULL) {
1955 m = reallok(exist, strlen(exist) + 4096);
1956 if (m == NULL) phree(exist);
1959 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
1966 /* read in the lines of message text one by one */
1967 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
1969 /* strip trailing newline type stuff */
1970 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
1971 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
1973 linelen = strlen(buf);
1975 /* augment the buffer if we have to */
1976 if ((message_len + linelen + 2) > buffer_len) {
1977 lprintf(9, "realloking\n");
1978 ptr = reallok(m, (buffer_len * 2) );
1979 if (ptr == NULL) { /* flush if can't allocate */
1980 while ( (client_gets(buf)>0) &&
1981 strcmp(buf, terminator)) ;;
1984 buffer_len = (buffer_len * 2);
1986 lprintf(9, "buffer_len is %d\n", buffer_len);
1990 /* Add the new line to the buffer. We avoid using strcat()
1991 * because that would involve traversing the entire message
1992 * after each line, and this function needs to run fast.
1994 strcpy(&m[message_len], buf);
1995 m[message_len + linelen] = '\n';
1996 m[message_len + linelen + 1] = 0;
1997 message_len = message_len + linelen + 1;
1999 /* if we've hit the max msg length, flush the rest */
2000 if (message_len >= maxlen) {
2001 while ( (client_gets(buf)>0)
2002 && strcmp(buf, terminator)) ;;
2013 * Build a binary message to be saved on disk.
2016 struct CtdlMessage *make_message(
2017 struct usersupp *author, /* author's usersupp structure */
2018 char *recipient, /* NULL if it's not mail */
2019 char *room, /* room where it's going */
2020 int type, /* see MES_ types in header file */
2021 int net_type, /* see MES_ types in header file */
2022 int format_type, /* local or remote (see citadel.h) */
2023 char *fake_name) /* who we're masquerading as */
2029 struct CtdlMessage *msg;
2031 msg = mallok(sizeof(struct CtdlMessage));
2032 memset(msg, 0, sizeof(struct CtdlMessage));
2033 msg->cm_magic = CTDLMESSAGE_MAGIC;
2034 msg->cm_anon_type = type;
2035 msg->cm_format_type = format_type;
2037 /* Don't confuse the poor folks if it's not routed mail. */
2038 strcpy(dest_node, "");
2040 /* If net_type is MES_BINARY, split out the destination node. */
2041 if (net_type == MES_BINARY) {
2042 strcpy(dest_node, NODENAME);
2043 for (a = 0; a < strlen(recipient); ++a) {
2044 if (recipient[a] == '@') {
2046 strcpy(dest_node, &recipient[a + 1]);
2051 /* if net_type is MES_INTERNET, set the dest node to 'internet' */
2052 if (net_type == MES_INTERNET) {
2053 strcpy(dest_node, "internet");
2056 while (isspace(recipient[strlen(recipient) - 1]))
2057 recipient[strlen(recipient) - 1] = 0;
2059 sprintf(buf, "cit%ld", author->usernum); /* Path */
2060 msg->cm_fields['P'] = strdoop(buf);
2062 sprintf(buf, "%ld", time(NULL)); /* timestamp */
2063 msg->cm_fields['T'] = strdoop(buf);
2065 if (fake_name[0]) /* author */
2066 msg->cm_fields['A'] = strdoop(fake_name);
2068 msg->cm_fields['A'] = strdoop(author->fullname);
2070 if (CC->quickroom.QRflags & QR_MAILBOX) /* room */
2071 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2073 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2075 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2076 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2078 if (recipient[0] != 0)
2079 msg->cm_fields['R'] = strdoop(recipient);
2080 if (dest_node[0] != 0)
2081 msg->cm_fields['D'] = strdoop(dest_node);
2084 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2085 config.c_maxmsglen, NULL);
2093 * Check to see whether we have permission to post a message in the current
2094 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2095 * returns 0 on success.
2097 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2099 if (!(CC->logged_in)) {
2100 sprintf(errmsgbuf, "Not logged in.");
2101 return (ERROR + NOT_LOGGED_IN);
2104 if ((CC->usersupp.axlevel < 2)
2105 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2106 sprintf(errmsgbuf, "Need to be validated to enter "
2107 "(except in %s> to sysop)", MAILROOM);
2108 return (ERROR + HIGHER_ACCESS_REQUIRED);
2111 if ((CC->usersupp.axlevel < 4)
2112 && (CC->quickroom.QRflags & QR_NETWORK)) {
2113 sprintf(errmsgbuf, "Need net privileges to enter here.");
2114 return (ERROR + HIGHER_ACCESS_REQUIRED);
2117 if ((CC->usersupp.axlevel < 6)
2118 && (CC->quickroom.QRflags & QR_READONLY)) {
2119 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2120 return (ERROR + HIGHER_ACCESS_REQUIRED);
2123 strcpy(errmsgbuf, "Ok");
2131 * message entry - mode 0 (normal)
2133 void cmd_ent0(char *entargs)
2136 char recipient[SIZ];
2138 int format_type = 0;
2139 char newusername[SIZ];
2140 struct CtdlMessage *msg;
2144 struct usersupp tempUS;
2148 post = extract_int(entargs, 0);
2149 extract(recipient, entargs, 1);
2150 anon_flag = extract_int(entargs, 2);
2151 format_type = extract_int(entargs, 3);
2153 /* first check to make sure the request is valid. */
2155 err = CtdlDoIHavePermissionToPostInThisRoom(buf);
2157 cprintf("%d %s\n", err, buf);
2161 /* Check some other permission type things. */
2164 if (CC->usersupp.axlevel < 6) {
2165 cprintf("%d You don't have permission to masquerade.\n",
2166 ERROR + HIGHER_ACCESS_REQUIRED);
2169 extract(newusername, entargs, 4);
2170 memset(CC->fake_postname, 0, 32);
2171 strcpy(CC->fake_postname, newusername);
2172 cprintf("%d Ok\n", OK);
2175 CC->cs_flags |= CS_POSTING;
2178 if (CC->quickroom.QRflags & QR_MAILBOX) {
2179 if (CC->usersupp.axlevel >= 2) {
2180 strcpy(buf, recipient);
2182 strcpy(buf, "sysop");
2183 e = alias(buf); /* alias and mail type */
2184 if ((buf[0] == 0) || (e == MES_ERROR)) {
2185 cprintf("%d Unknown address - cannot send message.\n",
2186 ERROR + NO_SUCH_USER);
2189 if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) {
2190 cprintf("%d Net privileges required for network mail.\n",
2191 ERROR + HIGHER_ACCESS_REQUIRED);
2194 if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET)
2195 && ((CC->usersupp.flags & US_INTERNET) == 0)
2196 && (!CC->internal_pgm)) {
2197 cprintf("%d You don't have access to Internet mail.\n",
2198 ERROR + HIGHER_ACCESS_REQUIRED);
2201 if (!strcasecmp(buf, "sysop")) {
2204 else if (e == MES_LOCAL) { /* don't search local file */
2205 if (!strcasecmp(buf, CC->usersupp.fullname)) {
2206 cprintf("%d Can't send mail to yourself!\n",
2207 ERROR + NO_SUCH_USER);
2210 /* Check to make sure the user exists; also get the correct
2211 * upper/lower casing of the name.
2213 a = getuser(&tempUS, buf);
2215 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
2218 strcpy(buf, tempUS.fullname);
2223 if (CC->quickroom.QRflags & QR_ANONONLY)
2225 if (CC->quickroom.QRflags & QR_ANONOPT) {
2229 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2232 /* If we're only checking the validity of the request, return
2233 * success without creating the message.
2236 cprintf("%d %s\n", OK, buf);
2240 cprintf("%d send message\n", SEND_LISTING);
2242 /* Read in the message from the client. */
2243 if (CC->fake_postname[0])
2244 msg = make_message(&CC->usersupp, buf,
2245 CC->quickroom.QRname, b, e, format_type,
2247 else if (CC->fake_username[0])
2248 msg = make_message(&CC->usersupp, buf,
2249 CC->quickroom.QRname, b, e, format_type,
2252 msg = make_message(&CC->usersupp, buf,
2253 CC->quickroom.QRname, b, e, format_type, "");
2256 CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e);
2257 CtdlFreeMessage(msg);
2258 CC->fake_postname[0] = '\0';
2265 * message entry - mode 3 (raw)
2267 void cmd_ent3(char *entargs)
2273 unsigned char ch, which_field;
2274 struct usersupp tempUS;
2276 struct CtdlMessage *msg;
2279 if (CC->internal_pgm == 0) {
2280 cprintf("%d This command is for internal programs only.\n",
2285 /* See if there's a recipient, but make sure it's a real one */
2286 extract(recp, entargs, 1);
2287 for (a = 0; a < strlen(recp); ++a)
2288 if (!isprint(recp[a]))
2289 strcpy(&recp[a], &recp[a + 1]);
2290 while (isspace(recp[0]))
2291 strcpy(recp, &recp[1]);
2292 while (isspace(recp[strlen(recp) - 1]))
2293 recp[strlen(recp) - 1] = 0;
2295 /* If we're in Mail, check the recipient */
2296 if (strlen(recp) > 0) {
2297 e = alias(recp); /* alias and mail type */
2298 if ((recp[0] == 0) || (e == MES_ERROR)) {
2299 cprintf("%d Unknown address - cannot send message.\n",
2300 ERROR + NO_SUCH_USER);
2303 if (e == MES_LOCAL) {
2304 a = getuser(&tempUS, recp);
2306 cprintf("%d No such user.\n",
2307 ERROR + NO_SUCH_USER);
2313 /* At this point, message has been approved. */
2314 if (extract_int(entargs, 0) == 0) {
2315 cprintf("%d OK to send\n", OK);
2319 msglen = extract_long(entargs, 2);
2320 msg = mallok(sizeof(struct CtdlMessage));
2322 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2326 memset(msg, 0, sizeof(struct CtdlMessage));
2327 tempbuf = mallok(msglen);
2328 if (tempbuf == NULL) {
2329 cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR);
2334 cprintf("%d %ld\n", SEND_BINARY, msglen);
2336 client_read(&ch, 1); /* 0xFF magic number */
2337 msg->cm_magic = CTDLMESSAGE_MAGIC;
2338 client_read(&ch, 1); /* anon type */
2339 msg->cm_anon_type = ch;
2340 client_read(&ch, 1); /* format type */
2341 msg->cm_format_type = ch;
2342 msglen = msglen - 3;
2344 while (msglen > 0) {
2345 client_read(&which_field, 1);
2346 if (!isalpha(which_field)) valid_msg = 0;
2350 client_read(&ch, 1);
2352 a = strlen(tempbuf);
2355 } while ( (ch != 0) && (msglen > 0) );
2357 msg->cm_fields[which_field] = strdoop(tempbuf);
2360 msg->cm_flags = CM_SKIP_HOOKS;
2361 if (valid_msg) CtdlSaveMsg(msg, recp, "", e);
2362 CtdlFreeMessage(msg);
2368 * API function to delete messages which match a set of criteria
2369 * (returns the actual number of messages deleted)
2371 int CtdlDeleteMessages(char *room_name, /* which room */
2372 long dmsgnum, /* or "0" for any */
2373 char *content_type /* or "" for any */
2377 struct quickroom qrbuf;
2378 struct cdbdata *cdbfr;
2379 long *msglist = NULL;
2382 int num_deleted = 0;
2384 struct SuppMsgInfo smi;
2386 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2387 room_name, dmsgnum, content_type);
2389 /* get room record, obtaining a lock... */
2390 if (lgetroom(&qrbuf, room_name) != 0) {
2391 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2393 return (0); /* room not found */
2395 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2397 if (cdbfr != NULL) {
2398 msglist = mallok(cdbfr->len);
2399 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2400 num_msgs = cdbfr->len / sizeof(long);
2404 for (i = 0; i < num_msgs; ++i) {
2407 /* Set/clear a bit for each criterion */
2409 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2410 delete_this |= 0x01;
2412 if (strlen(content_type) == 0) {
2413 delete_this |= 0x02;
2415 GetSuppMsgInfo(&smi, msglist[i]);
2416 if (!strcasecmp(smi.smi_content_type,
2418 delete_this |= 0x02;
2422 /* Delete message only if all bits are set */
2423 if (delete_this == 0x03) {
2424 AdjRefCount(msglist[i], -1);
2430 num_msgs = sort_msglist(msglist, num_msgs);
2431 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2432 msglist, (num_msgs * sizeof(long)));
2434 qrbuf.QRhighest = msglist[num_msgs - 1];
2438 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2439 return (num_deleted);
2445 * Check whether the current user has permission to delete messages from
2446 * the current room (returns 1 for yes, 0 for no)
2448 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2449 getuser(&CC->usersupp, CC->curr_user);
2450 if ((CC->usersupp.axlevel < 6)
2451 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2452 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2453 && (!(CC->internal_pgm))) {
2462 * Delete message from current room
2464 void cmd_dele(char *delstr)
2469 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2470 cprintf("%d Higher access required.\n",
2471 ERROR + HIGHER_ACCESS_REQUIRED);
2474 delnum = extract_long(delstr, 0);
2476 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2479 cprintf("%d %d message%s deleted.\n", OK,
2480 num_deleted, ((num_deleted != 1) ? "s" : ""));
2482 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2488 * Back end API function for moves and deletes
2490 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2493 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2494 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2495 if (err != 0) return(err);
2503 * move or copy a message to another room
2505 void cmd_move(char *args)
2509 struct quickroom qtemp;
2513 num = extract_long(args, 0);
2514 extract(targ, args, 1);
2515 targ[ROOMNAMELEN - 1] = 0;
2516 is_copy = extract_int(args, 2);
2518 getuser(&CC->usersupp, CC->curr_user);
2519 if ((CC->usersupp.axlevel < 6)
2520 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
2521 cprintf("%d Higher access required.\n",
2522 ERROR + HIGHER_ACCESS_REQUIRED);
2526 if (getroom(&qtemp, targ) != 0) {
2527 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2531 err = CtdlCopyMsgToRoom(num, targ);
2533 cprintf("%d Cannot store message in %s: error %d\n",
2538 /* Now delete the message from the source room,
2539 * if this is a 'move' rather than a 'copy' operation.
2541 if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2543 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2549 * GetSuppMsgInfo() - Get the supplementary record for a message
2551 void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum)
2554 struct cdbdata *cdbsmi;
2557 memset(smibuf, 0, sizeof(struct SuppMsgInfo));
2558 smibuf->smi_msgnum = msgnum;
2559 smibuf->smi_refcount = 1; /* Default reference count is 1 */
2561 /* Use the negative of the message number for its supp record index */
2562 TheIndex = (0L - msgnum);
2564 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2565 if (cdbsmi == NULL) {
2566 return; /* record not found; go with defaults */
2568 memcpy(smibuf, cdbsmi->ptr,
2569 ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ?
2570 sizeof(struct SuppMsgInfo) : cdbsmi->len));
2577 * PutSuppMsgInfo() - (re)write supplementary record for a message
2579 void PutSuppMsgInfo(struct SuppMsgInfo *smibuf)
2583 /* Use the negative of the message number for its supp record index */
2584 TheIndex = (0L - smibuf->smi_msgnum);
2586 lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n",
2587 smibuf->smi_msgnum, smibuf->smi_refcount);
2589 cdb_store(CDB_MSGMAIN,
2590 &TheIndex, sizeof(long),
2591 smibuf, sizeof(struct SuppMsgInfo));
2596 * AdjRefCount - change the reference count for a message;
2597 * delete the message if it reaches zero
2599 void AdjRefCount(long msgnum, int incr)
2602 struct SuppMsgInfo smi;
2605 /* This is a *tight* critical section; please keep it that way, as
2606 * it may get called while nested in other critical sections.
2607 * Complicating this any further will surely cause deadlock!
2609 begin_critical_section(S_SUPPMSGMAIN);
2610 GetSuppMsgInfo(&smi, msgnum);
2611 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2612 msgnum, smi.smi_refcount);
2613 smi.smi_refcount += incr;
2614 PutSuppMsgInfo(&smi);
2615 end_critical_section(S_SUPPMSGMAIN);
2616 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2617 msgnum, smi.smi_refcount);
2619 /* If the reference count is now zero, delete the message
2620 * (and its supplementary record as well).
2622 if (smi.smi_refcount == 0) {
2623 lprintf(9, "Deleting message <%ld>\n", msgnum);
2625 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2626 delnum = (0L - msgnum);
2627 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2632 * Write a generic object to this room
2634 * Note: this could be much more efficient. Right now we use two temporary
2635 * files, and still pull the message into memory as with all others.
2637 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2638 char *content_type, /* MIME type of this object */
2639 char *tempfilename, /* Where to fetch it from */
2640 struct usersupp *is_mailbox, /* Mailbox room? */
2641 int is_binary, /* Is encoding necessary? */
2642 int is_unique, /* Del others of this type? */
2643 unsigned int flags /* Internal save flags */
2648 char filename[PATH_MAX];
2651 struct quickroom qrbuf;
2652 char roomname[ROOMNAMELEN];
2653 struct CtdlMessage *msg;
2656 if (is_mailbox != NULL)
2657 MailboxName(roomname, is_mailbox, req_room);
2659 safestrncpy(roomname, req_room, sizeof(roomname));
2660 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2662 strcpy(filename, tmpnam(NULL));
2663 fp = fopen(filename, "w");
2667 tempfp = fopen(tempfilename, "r");
2668 if (tempfp == NULL) {
2674 fprintf(fp, "Content-type: %s\n", content_type);
2675 lprintf(9, "Content-type: %s\n", content_type);
2677 if (is_binary == 0) {
2678 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2679 while (ch = getc(tempfp), ch > 0)
2685 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2688 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2689 tempfilename, filename);
2693 lprintf(9, "Allocating\n");
2694 msg = mallok(sizeof(struct CtdlMessage));
2695 memset(msg, 0, sizeof(struct CtdlMessage));
2696 msg->cm_magic = CTDLMESSAGE_MAGIC;
2697 msg->cm_anon_type = MES_NORMAL;
2698 msg->cm_format_type = 4;
2699 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2700 msg->cm_fields['O'] = strdoop(req_room);
2701 msg->cm_fields['N'] = strdoop(config.c_nodename);
2702 msg->cm_fields['H'] = strdoop(config.c_humannode);
2703 msg->cm_flags = flags;
2705 lprintf(9, "Loading\n");
2706 fp = fopen(filename, "rb");
2707 fseek(fp, 0L, SEEK_END);
2710 msg->cm_fields['M'] = mallok(len);
2711 fread(msg->cm_fields['M'], len, 1, fp);
2715 /* Create the requested room if we have to. */
2716 if (getroom(&qrbuf, roomname) != 0) {
2717 create_room(roomname,
2718 ( (is_mailbox != NULL) ? 5 : 3 ),
2721 /* If the caller specified this object as unique, delete all
2722 * other objects of this type that are currently in the room.
2725 lprintf(9, "Deleted %d other msgs of this type\n",
2726 CtdlDeleteMessages(roomname, 0L, content_type));
2728 /* Now write the data */
2729 CtdlSaveMsg(msg, "", roomname, MES_LOCAL);
2730 CtdlFreeMessage(msg);
2738 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2739 config_msgnum = msgnum;
2743 char *CtdlGetSysConfig(char *sysconfname) {
2744 char hold_rm[ROOMNAMELEN];
2747 struct CtdlMessage *msg;
2750 strcpy(hold_rm, CC->quickroom.QRname);
2751 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2752 getroom(&CC->quickroom, hold_rm);
2757 /* We want the last (and probably only) config in this room */
2758 begin_critical_section(S_CONFIG);
2759 config_msgnum = (-1L);
2760 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2761 CtdlGetSysConfigBackend, NULL);
2762 msgnum = config_msgnum;
2763 end_critical_section(S_CONFIG);
2769 msg = CtdlFetchMessage(msgnum);
2771 conf = strdoop(msg->cm_fields['M']);
2772 CtdlFreeMessage(msg);
2779 getroom(&CC->quickroom, hold_rm);
2781 if (conf != NULL) do {
2782 extract_token(buf, conf, 0, '\n');
2783 strcpy(conf, &conf[strlen(buf)+1]);
2784 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2789 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2790 char temp[PATH_MAX];
2793 strcpy(temp, tmpnam(NULL));
2795 fp = fopen(temp, "w");
2796 if (fp == NULL) return;
2797 fprintf(fp, "%s", sysconfdata);
2800 /* this handy API function does all the work for us */
2801 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);