4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
39 #include "dynloader.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
55 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
56 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
57 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
59 extern struct config config;
63 "", "", "", "", "", "", "", "",
64 "", "", "", "", "", "", "", "",
65 "", "", "", "", "", "", "", "",
66 "", "", "", "", "", "", "", "",
67 "", "", "", "", "", "", "", "",
68 "", "", "", "", "", "", "", "",
69 "", "", "", "", "", "", "", "",
70 "", "", "", "", "", "", "", "",
97 * This function is self explanatory.
98 * (What can I say, I'm in a weird mood today...)
100 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
104 for (i = 0; i < strlen(name); ++i) {
105 if (name[i] == '@') {
106 while (isspace(name[i - 1]) && i > 0) {
107 strcpy(&name[i - 1], &name[i]);
110 while (isspace(name[i + 1])) {
111 strcpy(&name[i + 1], &name[i + 2]);
119 * Aliasing for network mail.
120 * (Error messages have been commented out, because this is a server.)
122 int alias(char *name)
123 { /* process alias and routing info for mail */
126 char aaa[SIZ], bbb[SIZ];
127 char *ignetcfg = NULL;
128 char *ignetmap = NULL;
135 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
137 fp = fopen("network/mail.aliases", "r");
139 fp = fopen("/dev/null", "r");
146 while (fgets(aaa, sizeof aaa, fp) != NULL) {
147 while (isspace(name[0]))
148 strcpy(name, &name[1]);
149 aaa[strlen(aaa) - 1] = 0;
151 for (a = 0; a < strlen(aaa); ++a) {
153 strcpy(bbb, &aaa[a + 1]);
157 if (!strcasecmp(name, aaa))
162 /* Hit the Global Address Book */
163 if (CtdlDirectoryLookup(aaa, name) == 0) {
167 lprintf(7, "Mail is being forwarded to %s\n", name);
169 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
170 for (a=0; a<strlen(name); ++a) {
171 if (name[a] == '@') {
172 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
174 lprintf(7, "Changed to <%s>\n", name);
179 /* determine local or remote type, see citadel.h */
180 at = haschar(name, '@');
181 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
182 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
183 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
185 /* figure out the delivery mode */
186 extract_token(node, name, 1, '@');
188 /* If there are one or more dots in the nodename, we assume that it
189 * is an FQDN and will attempt SMTP delivery to the Internet.
191 if (haschar(node, '.') > 0) {
192 return(MES_INTERNET);
195 /* Otherwise we look in the IGnet maps for a valid Citadel node.
196 * Try directly-connected nodes first...
198 ignetcfg = CtdlGetSysConfig(IGNETCFG);
199 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
200 extract_token(buf, ignetcfg, i, '\n');
201 extract_token(testnode, buf, 0, '|');
202 if (!strcasecmp(node, testnode)) {
210 * Then try nodes that are two or more hops away.
212 ignetmap = CtdlGetSysConfig(IGNETMAP);
213 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
214 extract_token(buf, ignetmap, i, '\n');
215 extract_token(testnode, buf, 0, '|');
216 if (!strcasecmp(node, testnode)) {
223 /* If we get to this point it's an invalid node name */
232 fp = fopen("citadel.control", "r");
233 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
239 void simple_listing(long msgnum, void *userdata)
241 cprintf("%ld\n", msgnum);
246 /* Determine if a given message matches the fields in a message template.
247 * Return 0 for a successful match.
249 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
252 /* If there aren't any fields in the template, all messages will
255 if (template == NULL) return(0);
257 /* Null messages are bogus. */
258 if (msg == NULL) return(1);
260 for (i='A'; i<='Z'; ++i) {
261 if (template->cm_fields[i] != NULL) {
262 if (msg->cm_fields[i] == NULL) {
265 if (strcasecmp(msg->cm_fields[i],
266 template->cm_fields[i])) return 1;
270 /* All compares succeeded: we have a match! */
276 * Manipulate the "seen msgs" string.
278 void CtdlSetSeen(long target_msgnum, int target_setting) {
280 struct cdbdata *cdbfr;
290 /* Learn about the user and room in question */
292 getuser(&CC->usersupp, CC->curr_user);
293 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
295 /* Load the message list */
296 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
298 msglist = mallok(cdbfr->len);
299 memcpy(msglist, cdbfr->ptr, cdbfr->len);
300 num_msgs = cdbfr->len / sizeof(long);
303 return; /* No messages at all? No further action. */
306 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
309 for (i=0; i<num_msgs; ++i) {
312 if (msglist[i] == target_msgnum) {
313 is_seen = target_setting;
316 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
322 if (lo < 0L) lo = msglist[i];
325 if ( ((is_seen == 0) && (was_seen == 1))
326 || ((is_seen == 1) && (i == num_msgs-1)) ) {
327 if ( (strlen(newseen) + 20) > SIZ) {
328 strcpy(newseen, &newseen[20]);
331 if (strlen(newseen) > 0) strcat(newseen, ",");
333 sprintf(&newseen[strlen(newseen)], "%ld", lo);
336 sprintf(&newseen[strlen(newseen)], "%ld:%ld",
345 safestrncpy(vbuf.v_seen, newseen, SIZ);
346 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
348 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
353 * API function to perform an operation for each qualifying message in the
354 * current room. (Returns the number of messages processed.)
356 int CtdlForEachMessage(int mode, long ref,
357 int moderation_level,
359 struct CtdlMessage *compare,
360 void (*CallBack) (long, void *),
366 struct cdbdata *cdbfr;
367 long *msglist = NULL;
369 int num_processed = 0;
372 struct CtdlMessage *msg;
375 int printed_lastold = 0;
377 /* Learn about the user and room in question */
379 getuser(&CC->usersupp, CC->curr_user);
380 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
382 /* Load the message list */
383 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
385 msglist = mallok(cdbfr->len);
386 memcpy(msglist, cdbfr->ptr, cdbfr->len);
387 num_msgs = cdbfr->len / sizeof(long);
390 return 0; /* No messages at all? No further action. */
395 * Now begin the traversal.
397 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
398 GetMetaData(&smi, msglist[a]);
400 /* Filter out messages that are moderated below the level
401 * currently being viewed at.
403 if (smi.meta_mod < moderation_level) {
407 /* If the caller is looking for a specific MIME type, filter
408 * out all messages which are not of the type requested.
410 if (content_type != NULL) if (strlen(content_type) > 0) {
411 if (strcasecmp(smi.meta_content_type, content_type)) {
417 num_msgs = sort_msglist(msglist, num_msgs);
419 /* If a template was supplied, filter out the messages which
420 * don't match. (This could induce some delays!)
423 if (compare != NULL) {
424 for (a = 0; a < num_msgs; ++a) {
425 msg = CtdlFetchMessage(msglist[a]);
427 if (CtdlMsgCmp(msg, compare)) {
430 CtdlFreeMessage(msg);
438 * Now iterate through the message list, according to the
439 * criteria supplied by the caller.
442 for (a = 0; a < num_msgs; ++a) {
443 thismsg = msglist[a];
444 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
445 if (is_seen) lastold = thismsg;
450 || ((mode == MSGS_OLD) && (is_seen))
451 || ((mode == MSGS_NEW) && (!is_seen))
452 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
453 || ((mode == MSGS_FIRST) && (a < ref))
454 || ((mode == MSGS_GT) && (thismsg > ref))
455 || ((mode == MSGS_EQ) && (thismsg == ref))
458 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
460 CallBack(lastold, userdata);
464 if (CallBack) CallBack(thismsg, userdata);
468 phree(msglist); /* Clean up */
469 return num_processed;
475 * cmd_msgs() - get list of message #'s in this room
476 * implements the MSGS server command using CtdlForEachMessage()
478 void cmd_msgs(char *cmdbuf)
487 int with_template = 0;
488 struct CtdlMessage *template = NULL;
490 extract(which, cmdbuf, 0);
491 cm_ref = extract_int(cmdbuf, 1);
492 with_template = extract_int(cmdbuf, 2);
496 if (!strncasecmp(which, "OLD", 3))
498 else if (!strncasecmp(which, "NEW", 3))
500 else if (!strncasecmp(which, "FIRST", 5))
502 else if (!strncasecmp(which, "LAST", 4))
504 else if (!strncasecmp(which, "GT", 2))
507 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
508 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
513 cprintf("%d Send template then receive message list\n",
515 template = (struct CtdlMessage *)
516 mallok(sizeof(struct CtdlMessage));
517 memset(template, 0, sizeof(struct CtdlMessage));
518 while(client_gets(buf), strcmp(buf,"000")) {
519 extract(tfield, buf, 0);
520 extract(tvalue, buf, 1);
521 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
522 if (!strcasecmp(tfield, msgkeys[i])) {
523 template->cm_fields[i] =
530 cprintf("%d Message list...\n", LISTING_FOLLOWS);
533 CtdlForEachMessage(mode, cm_ref,
534 CC->usersupp.moderation_filter,
535 NULL, template, simple_listing, NULL);
536 if (template != NULL) CtdlFreeMessage(template);
544 * help_subst() - support routine for help file viewer
546 void help_subst(char *strbuf, char *source, char *dest)
551 while (p = pattern2(strbuf, source), (p >= 0)) {
552 strcpy(workbuf, &strbuf[p + strlen(source)]);
553 strcpy(&strbuf[p], dest);
554 strcat(strbuf, workbuf);
559 void do_help_subst(char *buffer)
563 help_subst(buffer, "^nodename", config.c_nodename);
564 help_subst(buffer, "^humannode", config.c_humannode);
565 help_subst(buffer, "^fqdn", config.c_fqdn);
566 help_subst(buffer, "^username", CC->usersupp.fullname);
567 sprintf(buf2, "%ld", CC->usersupp.usernum);
568 help_subst(buffer, "^usernum", buf2);
569 help_subst(buffer, "^sysadm", config.c_sysadm);
570 help_subst(buffer, "^variantname", CITADEL);
571 sprintf(buf2, "%d", config.c_maxsessions);
572 help_subst(buffer, "^maxsessions", buf2);
578 * memfmout() - Citadel text formatter and paginator.
579 * Although the original purpose of this routine was to format
580 * text to the reader's screen width, all we're really using it
581 * for here is to format text out to 80 columns before sending it
582 * to the client. The client software may reformat it again.
585 int width, /* screen width to use */
586 char *mptr, /* where are we going to get our text from? */
587 char subst, /* nonzero if we should do substitutions */
588 char *nl) /* string to terminate lines with */
600 c = 1; /* c is the current pos */
604 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
606 buffer[strlen(buffer) + 1] = 0;
607 buffer[strlen(buffer)] = ch;
610 if (buffer[0] == '^')
611 do_help_subst(buffer);
613 buffer[strlen(buffer) + 1] = 0;
615 strcpy(buffer, &buffer[1]);
623 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
625 if (((old == 13) || (old == 10)) && (isspace(real))) {
633 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
634 cprintf("%s%s", nl, aaa);
643 if ((strlen(aaa) + c) > (width - 5)) {
652 if ((ch == 13) || (ch == 10)) {
653 cprintf("%s%s", aaa, nl);
660 cprintf("%s%s", aaa, nl);
666 * Callback function for mime parser that simply lists the part
668 void list_this_part(char *name, char *filename, char *partnum, char *disp,
669 void *content, char *cbtype, size_t length, char *encoding,
673 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
674 name, filename, partnum, disp, cbtype, (long)length);
679 * Callback function for mime parser that opens a section for downloading
681 void mime_download(char *name, char *filename, char *partnum, char *disp,
682 void *content, char *cbtype, size_t length, char *encoding,
686 /* Silently go away if there's already a download open... */
687 if (CC->download_fp != NULL)
690 /* ...or if this is not the desired section */
691 if (strcasecmp(desired_section, partnum))
694 CC->download_fp = tmpfile();
695 if (CC->download_fp == NULL)
698 fwrite(content, length, 1, CC->download_fp);
699 fflush(CC->download_fp);
700 rewind(CC->download_fp);
702 OpenCmdResult(filename, cbtype);
708 * Load a message from disk into memory.
709 * This is used by CtdlOutputMsg() and other fetch functions.
711 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
712 * using the CtdlMessageFree() function.
714 struct CtdlMessage *CtdlFetchMessage(long msgnum)
716 struct cdbdata *dmsgtext;
717 struct CtdlMessage *ret = NULL;
720 CIT_UBYTE field_header;
723 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
724 if (dmsgtext == NULL) {
727 mptr = dmsgtext->ptr;
729 /* Parse the three bytes that begin EVERY message on disk.
730 * The first is always 0xFF, the on-disk magic number.
731 * The second is the anonymous/public type byte.
732 * The third is the format type byte (vari, fixed, or MIME).
736 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
740 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
741 memset(ret, 0, sizeof(struct CtdlMessage));
743 ret->cm_magic = CTDLMESSAGE_MAGIC;
744 ret->cm_anon_type = *mptr++; /* Anon type byte */
745 ret->cm_format_type = *mptr++; /* Format type byte */
748 * The rest is zero or more arbitrary fields. Load them in.
749 * We're done when we encounter either a zero-length field or
750 * have just processed the 'M' (message text) field.
753 field_length = strlen(mptr);
754 if (field_length == 0)
756 field_header = *mptr++;
757 ret->cm_fields[field_header] = mallok(field_length);
758 strcpy(ret->cm_fields[field_header], mptr);
760 while (*mptr++ != 0); /* advance to next field */
762 } while ((field_length > 0) && (field_header != 'M'));
766 /* Always make sure there's something in the msg text field */
767 if (ret->cm_fields['M'] == NULL)
768 ret->cm_fields['M'] = strdoop("<no text>\n");
770 /* Perform "before read" hooks (aborting if any return nonzero) */
771 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
772 CtdlFreeMessage(ret);
781 * Returns 1 if the supplied pointer points to a valid Citadel message.
782 * If the pointer is NULL or the magic number check fails, returns 0.
784 int is_valid_message(struct CtdlMessage *msg) {
787 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
788 lprintf(3, "is_valid_message() -- self-check failed\n");
796 * 'Destructor' for struct CtdlMessage
798 void CtdlFreeMessage(struct CtdlMessage *msg)
802 if (is_valid_message(msg) == 0) return;
804 for (i = 0; i < 256; ++i)
805 if (msg->cm_fields[i] != NULL) {
806 phree(msg->cm_fields[i]);
809 msg->cm_magic = 0; /* just in case */
815 * Pre callback function for multipart/alternative
817 * NOTE: this differs from the standard behavior for a reason. Normally when
818 * displaying multipart/alternative you want to show the _last_ usable
819 * format in the message. Here we show the _first_ one, because it's
820 * usually text/plain. Since this set of functions is designed for text
821 * output to non-MIME-aware clients, this is the desired behavior.
824 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
825 void *content, char *cbtype, size_t length, char *encoding,
828 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);
829 if (!strcasecmp(cbtype, "multipart/alternative")) {
837 * Post callback function for multipart/alternative
839 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
840 void *content, char *cbtype, size_t length, char *encoding,
843 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);
844 if (!strcasecmp(cbtype, "multipart/alternative")) {
852 * Inline callback function for mime parser that wants to display text
854 void fixed_output(char *name, char *filename, char *partnum, char *disp,
855 void *content, char *cbtype, size_t length, char *encoding,
863 lprintf(9, "fixed_output() type=<%s>\n", cbtype);
866 * If we're in the middle of a multipart/alternative scope and
867 * we've already printed another section, skip this one.
869 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
870 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
875 if ( (!strcasecmp(cbtype, "text/plain"))
876 || (strlen(cbtype)==0) ) {
882 if (ch==10) cprintf("\r\n");
883 else cprintf("%c", ch);
887 if (ch != '\n') cprintf("\n");
889 else if (!strcasecmp(cbtype, "text/html")) {
890 ptr = html_to_ascii(content, 80, 0);
895 if (ch==10) cprintf("\r\n");
896 else cprintf("%c", ch);
900 else if (strncasecmp(cbtype, "multipart/", 10)) {
901 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
902 partnum, filename, cbtype, (long)length);
908 * Get a message off disk. (returns om_* values found in msgbase.h)
911 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
912 int mode, /* how would you like that message? */
913 int headers_only, /* eschew the message body? */
914 int do_proto, /* do Citadel protocol responses? */
915 int crlf /* Use CRLF newlines instead of LF? */
917 struct CtdlMessage *TheMessage;
920 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
925 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
926 if (do_proto) cprintf("%d Not logged in.\n",
927 ERROR + NOT_LOGGED_IN);
928 return(om_not_logged_in);
931 /* FIXME ... small security issue
932 * We need to check to make sure the requested message is actually
933 * in the current room, and set msg_ok to 1 only if it is. This
934 * functionality is currently missing because I'm in a hurry to replace
935 * broken production code with nonbroken pre-beta code. :( -- ajc
938 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
940 return(om_no_such_msg);
945 * Fetch the message from disk
947 TheMessage = CtdlFetchMessage(msg_num);
948 if (TheMessage == NULL) {
949 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
951 return(om_no_such_msg);
954 retcode = CtdlOutputPreLoadedMsg(
955 TheMessage, msg_num, mode,
956 headers_only, do_proto, crlf);
958 CtdlFreeMessage(TheMessage);
964 * Get a message off disk. (returns om_* values found in msgbase.h)
967 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
969 int mode, /* how would you like that message? */
970 int headers_only, /* eschew the message body? */
971 int do_proto, /* do Citadel protocol responses? */
972 int crlf /* Use CRLF newlines instead of LF? */
978 char display_name[SIZ];
980 char *nl; /* newline string */
982 /* buffers needed for RFC822 translation */
992 sprintf(mid, "%ld", msg_num);
993 nl = (crlf ? "\r\n" : "\n");
995 if (!is_valid_message(TheMessage)) {
996 lprintf(1, "ERROR: invalid preloaded message for output\n");
997 return(om_no_such_msg);
1000 /* Are we downloading a MIME component? */
1001 if (mode == MT_DOWNLOAD) {
1002 if (TheMessage->cm_format_type != FMT_RFC822) {
1004 cprintf("%d This is not a MIME message.\n",
1006 } else if (CC->download_fp != NULL) {
1007 if (do_proto) cprintf(
1008 "%d You already have a download open.\n",
1011 /* Parse the message text component */
1012 mptr = TheMessage->cm_fields['M'];
1013 mime_parser(mptr, NULL,
1014 *mime_download, NULL, NULL,
1016 /* If there's no file open by this time, the requested
1017 * section wasn't found, so print an error
1019 if (CC->download_fp == NULL) {
1020 if (do_proto) cprintf(
1021 "%d Section %s not found.\n",
1022 ERROR + FILE_NOT_FOUND,
1026 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1029 /* now for the user-mode message reading loops */
1030 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1032 /* Tell the client which format type we're using. If this is a
1033 * MIME message, *lie* about it and tell the user it's fixed-format.
1035 if (mode == MT_CITADEL) {
1036 if (TheMessage->cm_format_type == FMT_RFC822) {
1037 if (do_proto) cprintf("type=1\n");
1040 if (do_proto) cprintf("type=%d\n",
1041 TheMessage->cm_format_type);
1045 /* nhdr=yes means that we're only displaying headers, no body */
1046 if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1047 if (do_proto) cprintf("nhdr=yes\n");
1050 /* begin header processing loop for Citadel message format */
1052 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1054 strcpy(display_name, "<unknown>");
1055 if (TheMessage->cm_fields['A']) {
1056 strcpy(buf, TheMessage->cm_fields['A']);
1057 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1058 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1059 strcpy(display_name, "****");
1061 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1062 strcpy(display_name, "anonymous");
1065 strcpy(display_name, buf);
1067 if ((is_room_aide())
1068 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1069 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1070 sprintf(&display_name[strlen(display_name)],
1075 strcpy(allkeys, FORDER);
1076 for (i=0; i<strlen(allkeys); ++i) {
1077 k = (int) allkeys[i];
1079 if (TheMessage->cm_fields[k] != NULL) {
1081 if (do_proto) cprintf("%s=%s\n",
1086 if (do_proto) cprintf("%s=%s\n",
1088 TheMessage->cm_fields[k]
1097 /* begin header processing loop for RFC822 transfer format */
1102 strcpy(snode, NODENAME);
1103 strcpy(lnode, HUMANNODE);
1104 if (mode == MT_RFC822) {
1105 cprintf("X-UIDL: %ld%s", msg_num, nl);
1106 for (i = 0; i < 256; ++i) {
1107 if (TheMessage->cm_fields[i]) {
1108 mptr = TheMessage->cm_fields[i];
1111 strcpy(luser, mptr);
1112 strcpy(suser, mptr);
1115 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1116 into thinking that mail messages are newsgroup messages instead. When we
1117 add NNTP support back into Citadel we'll have to add code to only output
1118 this field when appropriate.
1119 else if (i == 'P') {
1120 cprintf("Path: %s%s", mptr, nl);
1124 cprintf("Subject: %s%s", mptr, nl);
1126 safestrncpy(mid, mptr, sizeof mid);
1128 safestrncpy(lnode, mptr, sizeof lnode);
1130 safestrncpy(fuser, mptr, sizeof fuser);
1132 cprintf("X-Citadel-Room: %s%s",
1135 safestrncpy(snode, mptr, sizeof snode);
1137 cprintf("To: %s%s", mptr, nl);
1138 else if (i == 'T') {
1139 datestring(datestamp, atol(mptr),
1140 DATESTRING_RFC822 );
1141 cprintf("Date: %s%s", datestamp, nl);
1147 for (i=0; i<strlen(suser); ++i) {
1148 suser[i] = tolower(suser[i]);
1149 if (!isalnum(suser[i])) suser[i]='_';
1152 if (mode == MT_RFC822) {
1153 if (!strcasecmp(snode, NODENAME)) {
1154 strcpy(snode, FQDN);
1157 /* Construct a fun message id */
1158 cprintf("Message-ID: <%s", mid);
1159 if (strchr(mid, '@')==NULL) {
1160 cprintf("@%s", snode);
1164 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1166 if (strlen(fuser) > 0) {
1167 cprintf("From: %s (%s)%s", fuser, luser, nl);
1170 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1173 cprintf("Organization: %s%s", lnode, nl);
1176 /* end header processing loop ... at this point, we're in the text */
1178 mptr = TheMessage->cm_fields['M'];
1180 /* Tell the client about the MIME parts in this message */
1181 if (TheMessage->cm_format_type == FMT_RFC822) {
1182 if (mode == MT_CITADEL) {
1183 mime_parser(mptr, NULL,
1184 *list_this_part, NULL, NULL,
1187 else if (mode == MT_MIME) { /* list parts only */
1188 mime_parser(mptr, NULL,
1189 *list_this_part, NULL, NULL,
1191 if (do_proto) cprintf("000\n");
1194 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1195 /* FIXME ... we have to put some code in here to avoid
1196 * printing duplicate header information when both
1197 * Citadel and RFC822 headers exist. Preference should
1198 * probably be given to the RFC822 headers.
1200 while (ch=*(mptr++), ch!=0) {
1202 else if (ch==10) cprintf("%s", nl);
1203 else cprintf("%c", ch);
1205 if (do_proto) cprintf("000\n");
1211 if (do_proto) cprintf("000\n");
1215 /* signify start of msg text */
1216 if (mode == MT_CITADEL)
1217 if (do_proto) cprintf("text\n");
1218 if (mode == MT_RFC822) {
1219 if (TheMessage->cm_fields['U'] == NULL) {
1220 cprintf("Subject: (no subject)%s", nl);
1225 /* If the format type on disk is 1 (fixed-format), then we want
1226 * everything to be output completely literally ... regardless of
1227 * what message transfer format is in use.
1229 if (TheMessage->cm_format_type == FMT_FIXED) {
1231 while (ch = *mptr++, ch > 0) {
1234 if ((ch == 10) || (strlen(buf) > 250)) {
1235 cprintf("%s%s", buf, nl);
1238 buf[strlen(buf) + 1] = 0;
1239 buf[strlen(buf)] = ch;
1242 if (strlen(buf) > 0)
1243 cprintf("%s%s", buf, nl);
1246 /* If the message on disk is format 0 (Citadel vari-format), we
1247 * output using the formatter at 80 columns. This is the final output
1248 * form if the transfer format is RFC822, but if the transfer format
1249 * is Citadel proprietary, it'll still work, because the indentation
1250 * for new paragraphs is correct and the client will reformat the
1251 * message to the reader's screen width.
1253 if (TheMessage->cm_format_type == FMT_CITADEL) {
1254 memfmout(80, mptr, 0, nl);
1257 /* If the message on disk is format 4 (MIME), we've gotta hand it
1258 * off to the MIME parser. The client has already been told that
1259 * this message is format 1 (fixed format), so the callback function
1260 * we use will display those parts as-is.
1262 if (TheMessage->cm_format_type == FMT_RFC822) {
1263 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1264 memset(ma, 0, sizeof(struct ma_info));
1265 mime_parser(mptr, NULL,
1266 *fixed_output, *fixed_output_pre, *fixed_output_post,
1270 /* now we're done */
1271 if (do_proto) cprintf("000\n");
1278 * display a message (mode 0 - Citadel proprietary)
1280 void cmd_msg0(char *cmdbuf)
1283 int headers_only = 0;
1285 msgid = extract_long(cmdbuf, 0);
1286 headers_only = extract_int(cmdbuf, 1);
1288 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1294 * display a message (mode 2 - RFC822)
1296 void cmd_msg2(char *cmdbuf)
1299 int headers_only = 0;
1301 msgid = extract_long(cmdbuf, 0);
1302 headers_only = extract_int(cmdbuf, 1);
1304 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1310 * display a message (mode 3 - IGnet raw format - internal programs only)
1312 void cmd_msg3(char *cmdbuf)
1315 struct CtdlMessage *msg;
1318 if (CC->internal_pgm == 0) {
1319 cprintf("%d This command is for internal programs only.\n",
1324 msgnum = extract_long(cmdbuf, 0);
1325 msg = CtdlFetchMessage(msgnum);
1327 cprintf("%d Message %ld not found.\n",
1332 serialize_message(&smr, msg);
1333 CtdlFreeMessage(msg);
1336 cprintf("%d Unable to serialize message\n",
1337 ERROR+INTERNAL_ERROR);
1341 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1342 client_write(smr.ser, smr.len);
1349 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1351 void cmd_msg4(char *cmdbuf)
1355 msgid = extract_long(cmdbuf, 0);
1356 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1360 * Open a component of a MIME message as a download file
1362 void cmd_opna(char *cmdbuf)
1366 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1368 msgid = extract_long(cmdbuf, 0);
1369 extract(desired_section, cmdbuf, 1);
1371 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1376 * Save a message pointer into a specified room
1377 * (Returns 0 for success, nonzero for failure)
1378 * roomname may be NULL to use the current room
1380 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1382 char hold_rm[ROOMNAMELEN];
1383 struct cdbdata *cdbfr;
1386 long highest_msg = 0L;
1387 struct CtdlMessage *msg = NULL;
1389 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1390 roomname, msgid, flags);
1392 strcpy(hold_rm, CC->quickroom.QRname);
1394 /* We may need to check to see if this message is real */
1395 if ( (flags & SM_VERIFY_GOODNESS)
1396 || (flags & SM_DO_REPL_CHECK)
1398 msg = CtdlFetchMessage(msgid);
1399 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1402 /* Perform replication checks if necessary */
1403 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1405 if (getroom(&CC->quickroom,
1406 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1408 lprintf(9, "No such room <%s>\n", roomname);
1409 if (msg != NULL) CtdlFreeMessage(msg);
1410 return(ERROR + ROOM_NOT_FOUND);
1413 if (ReplicationChecks(msg) != 0) {
1414 getroom(&CC->quickroom, hold_rm);
1415 if (msg != NULL) CtdlFreeMessage(msg);
1416 lprintf(9, "Did replication, and newer exists\n");
1421 /* Now the regular stuff */
1422 if (lgetroom(&CC->quickroom,
1423 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1425 lprintf(9, "No such room <%s>\n", roomname);
1426 if (msg != NULL) CtdlFreeMessage(msg);
1427 return(ERROR + ROOM_NOT_FOUND);
1430 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1431 if (cdbfr == NULL) {
1435 msglist = mallok(cdbfr->len);
1436 if (msglist == NULL)
1437 lprintf(3, "ERROR malloc msglist!\n");
1438 num_msgs = cdbfr->len / sizeof(long);
1439 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1444 /* Make sure the message doesn't already exist in this room. It
1445 * is absolutely taboo to have more than one reference to the same
1446 * message in a room.
1448 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1449 if (msglist[i] == msgid) {
1450 lputroom(&CC->quickroom); /* unlock the room */
1451 getroom(&CC->quickroom, hold_rm);
1452 if (msg != NULL) CtdlFreeMessage(msg);
1453 return(ERROR + ALREADY_EXISTS);
1457 /* Now add the new message */
1459 msglist = reallok(msglist,
1460 (num_msgs * sizeof(long)));
1462 if (msglist == NULL) {
1463 lprintf(3, "ERROR: can't realloc message list!\n");
1465 msglist[num_msgs - 1] = msgid;
1467 /* Sort the message list, so all the msgid's are in order */
1468 num_msgs = sort_msglist(msglist, num_msgs);
1470 /* Determine the highest message number */
1471 highest_msg = msglist[num_msgs - 1];
1473 /* Write it back to disk. */
1474 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1475 msglist, num_msgs * sizeof(long));
1477 /* Free up the memory we used. */
1480 /* Update the highest-message pointer and unlock the room. */
1481 CC->quickroom.QRhighest = highest_msg;
1482 lputroom(&CC->quickroom);
1483 getroom(&CC->quickroom, hold_rm);
1485 /* Bump the reference count for this message. */
1486 if ((flags & SM_DONT_BUMP_REF)==0) {
1487 AdjRefCount(msgid, +1);
1490 /* Return success. */
1491 if (msg != NULL) CtdlFreeMessage(msg);
1498 * Message base operation to send a message to the master file
1499 * (returns new message number)
1501 * This is the back end for CtdlSubmitMsg() and should not be directly
1502 * called by server-side modules.
1505 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1506 FILE *save_a_copy) /* save a copy to disk? */
1513 /* Get a new message number */
1514 newmsgid = get_new_message_number();
1515 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1517 /* Generate an ID if we don't have one already */
1518 if (msg->cm_fields['I']==NULL) {
1519 msg->cm_fields['I'] = strdoop(msgidbuf);
1522 serialize_message(&smr, msg);
1525 cprintf("%d Unable to serialize message\n",
1526 ERROR+INTERNAL_ERROR);
1530 /* Write our little bundle of joy into the message base */
1531 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1532 smr.ser, smr.len) < 0) {
1533 lprintf(2, "Can't store message\n");
1539 /* If the caller specified that a copy should be saved to a particular
1540 * file handle, do that now too.
1542 if (save_a_copy != NULL) {
1543 fwrite(smr.ser, smr.len, 1, save_a_copy);
1546 /* Free the memory we used for the serialized message */
1549 /* Return the *local* message ID to the caller
1550 * (even if we're storing an incoming network message)
1558 * Serialize a struct CtdlMessage into the format used on disk and network.
1560 * This function loads up a "struct ser_ret" (defined in server.h) which
1561 * contains the length of the serialized message and a pointer to the
1562 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1564 void serialize_message(struct ser_ret *ret, /* return values */
1565 struct CtdlMessage *msg) /* unserialized msg */
1569 static char *forder = FORDER;
1571 if (is_valid_message(msg) == 0) return; /* self check */
1574 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1575 ret->len = ret->len +
1576 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1578 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1579 ret->ser = mallok(ret->len);
1580 if (ret->ser == NULL) {
1586 ret->ser[1] = msg->cm_anon_type;
1587 ret->ser[2] = msg->cm_format_type;
1590 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1591 ret->ser[wlen++] = (char)forder[i];
1592 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1593 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1595 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1596 (long)ret->len, (long)wlen);
1604 * Back end for the ReplicationChecks() function
1606 void check_repl(long msgnum, void *userdata) {
1607 struct CtdlMessage *msg;
1608 time_t timestamp = (-1L);
1610 lprintf(9, "check_repl() found message %ld\n", msgnum);
1611 msg = CtdlFetchMessage(msgnum);
1612 if (msg == NULL) return;
1613 if (msg->cm_fields['T'] != NULL) {
1614 timestamp = atol(msg->cm_fields['T']);
1616 CtdlFreeMessage(msg);
1618 if (timestamp > msg_repl->highest) {
1619 msg_repl->highest = timestamp; /* newer! */
1620 lprintf(9, "newer!\n");
1623 lprintf(9, "older!\n");
1625 /* Existing isn't newer? Then delete the old one(s). */
1626 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1631 * Check to see if any messages already exist which carry the same Extended ID
1635 * -> With older timestamps: delete them and return 0. Message will be saved.
1636 * -> With newer timestamps: return 1. Message save will be aborted.
1638 int ReplicationChecks(struct CtdlMessage *msg) {
1639 struct CtdlMessage *template;
1642 lprintf(9, "ReplicationChecks() started\n");
1643 /* No extended id? Don't do anything. */
1644 if (msg->cm_fields['E'] == NULL) return 0;
1645 if (strlen(msg->cm_fields['E']) == 0) return 0;
1646 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1648 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1649 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1650 msg_repl->highest = atol(msg->cm_fields['T']);
1652 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1653 memset(template, 0, sizeof(struct CtdlMessage));
1654 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1656 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1659 /* If a newer message exists with the same Extended ID, abort
1662 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1666 CtdlFreeMessage(template);
1667 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1675 * Save a message to disk and submit it into the delivery system.
1677 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1678 struct recptypes *recps, /* recipients (if mail) */
1679 char *force /* force a particular room? */
1682 char hold_rm[ROOMNAMELEN];
1683 char actual_rm[ROOMNAMELEN];
1684 char force_room[ROOMNAMELEN];
1685 char content_type[SIZ]; /* We have to learn this */
1686 char recipient[SIZ];
1689 struct usersupp userbuf;
1691 struct MetaData smi;
1692 FILE *network_fp = NULL;
1693 static int seqnum = 1;
1694 struct CtdlMessage *imsg = NULL;
1697 char *hold_R, *hold_D;
1699 lprintf(9, "CtdlSubmitMsg() called\n");
1700 if (is_valid_message(msg) == 0) return(-1); /* self check */
1702 /* If this message has no timestamp, we take the liberty of
1703 * giving it one, right now.
1705 if (msg->cm_fields['T'] == NULL) {
1706 lprintf(9, "Generating timestamp\n");
1707 sprintf(aaa, "%ld", (long)time(NULL));
1708 msg->cm_fields['T'] = strdoop(aaa);
1711 /* If this message has no path, we generate one.
1713 if (msg->cm_fields['P'] == NULL) {
1714 lprintf(9, "Generating path\n");
1715 if (msg->cm_fields['A'] != NULL) {
1716 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1717 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1718 if (isspace(msg->cm_fields['P'][a])) {
1719 msg->cm_fields['P'][a] = ' ';
1724 msg->cm_fields['P'] = strdoop("unknown");
1728 strcpy(force_room, force);
1730 /* Learn about what's inside, because it's what's inside that counts */
1731 lprintf(9, "Learning what's inside\n");
1732 if (msg->cm_fields['M'] == NULL) {
1733 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1736 switch (msg->cm_format_type) {
1738 strcpy(content_type, "text/x-citadel-variformat");
1741 strcpy(content_type, "text/plain");
1744 strcpy(content_type, "text/plain");
1745 /* advance past header fields */
1746 mptr = msg->cm_fields['M'];
1749 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1750 safestrncpy(content_type, mptr,
1751 sizeof(content_type));
1752 strcpy(content_type, &content_type[14]);
1753 for (a = 0; a < strlen(content_type); ++a)
1754 if ((content_type[a] == ';')
1755 || (content_type[a] == ' ')
1756 || (content_type[a] == 13)
1757 || (content_type[a] == 10))
1758 content_type[a] = 0;
1765 /* Goto the correct room */
1766 lprintf(9, "Switching rooms\n");
1767 strcpy(hold_rm, CC->quickroom.QRname);
1768 strcpy(actual_rm, CC->quickroom.QRname);
1769 if (recps != NULL) {
1770 strcpy(actual_rm, SENTITEMS);
1773 /* If the user is a twit, move to the twit room for posting */
1774 lprintf(9, "Handling twit stuff\n");
1776 if (CC->usersupp.axlevel == 2) {
1777 strcpy(hold_rm, actual_rm);
1778 strcpy(actual_rm, config.c_twitroom);
1782 /* ...or if this message is destined for Aide> then go there. */
1783 if (strlen(force_room) > 0) {
1784 strcpy(actual_rm, force_room);
1787 lprintf(9, "Possibly relocating\n");
1788 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1789 getroom(&CC->quickroom, actual_rm);
1793 * If this message has no O (room) field, generate one.
1795 if (msg->cm_fields['O'] == NULL) {
1796 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1799 /* Perform "before save" hooks (aborting if any return nonzero) */
1800 lprintf(9, "Performing before-save hooks\n");
1801 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1803 /* If this message has an Extended ID, perform replication checks */
1804 lprintf(9, "Performing replication checks\n");
1805 if (ReplicationChecks(msg) > 0) return(-1);
1807 /* Save it to disk */
1808 lprintf(9, "Saving to disk\n");
1809 newmsgid = send_message(msg, NULL);
1810 if (newmsgid <= 0L) return(-1);
1812 /* Write a supplemental message info record. This doesn't have to
1813 * be a critical section because nobody else knows about this message
1816 lprintf(9, "Creating MetaData record\n");
1817 memset(&smi, 0, sizeof(struct MetaData));
1818 smi.meta_msgnum = newmsgid;
1819 smi.meta_refcount = 0;
1820 safestrncpy(smi.meta_content_type, content_type, 64);
1823 /* Now figure out where to store the pointers */
1824 lprintf(9, "Storing pointers\n");
1826 /* If this is being done by the networker delivering a private
1827 * message, we want to BYPASS saving the sender's copy (because there
1828 * is no local sender; it would otherwise go to the Trashcan).
1830 if ((!CC->internal_pgm) || (recps == NULL)) {
1831 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1832 lprintf(3, "ERROR saving message pointer!\n");
1833 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1837 /* For internet mail, drop a copy in the outbound queue room */
1839 if (recps->num_internet > 0) {
1840 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1843 /* If other rooms are specified, drop them there too. */
1845 if (recps->num_room > 0)
1846 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1847 extract(recipient, recps->recp_room, i);
1848 lprintf(9, "Delivering to local room <%s>\n", recipient);
1849 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1852 /* Bump this user's messages posted counter. */
1853 lprintf(9, "Updating user\n");
1854 lgetuser(&CC->usersupp, CC->curr_user);
1855 CC->usersupp.posted = CC->usersupp.posted + 1;
1856 lputuser(&CC->usersupp);
1858 /* If this is private, local mail, make a copy in the
1859 * recipient's mailbox and bump the reference count.
1862 if (recps->num_local > 0)
1863 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1864 extract(recipient, recps->recp_local, i);
1865 lprintf(9, "Delivering private local mail to <%s>\n",
1867 if (getuser(&userbuf, recipient) == 0) {
1868 MailboxName(actual_rm, &userbuf, MAILROOM);
1869 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1872 lprintf(9, "No user <%s>\n", recipient);
1873 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1877 /* Perform "after save" hooks */
1878 lprintf(9, "Performing after-save hooks\n");
1879 PerformMessageHooks(msg, EVT_AFTERSAVE);
1881 /* For IGnet mail, we have to save a new copy into the spooler for
1882 * each recipient, with the R and D fields set to the recipient and
1883 * destination-node. This has two ugly side effects: all other
1884 * recipients end up being unlisted in this recipient's copy of the
1885 * message, and it has to deliver multiple messages to the same
1886 * node. We'll revisit this again in a year or so when everyone has
1887 * a network spool receiver that can handle the new style messages.
1890 if (recps->num_ignet > 0)
1891 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1892 extract(recipient, recps->recp_ignet, i);
1894 hold_R = msg->cm_fields['R'];
1895 hold_D = msg->cm_fields['D'];
1896 msg->cm_fields['R'] = mallok(SIZ);
1897 msg->cm_fields['D'] = mallok(SIZ);
1898 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1899 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1901 serialize_message(&smr, msg);
1904 "./network/spoolin/netmail.%04lx.%04x.%04x",
1905 (long) getpid(), CC->cs_pid, ++seqnum);
1906 network_fp = fopen(aaa, "wb+");
1907 if (network_fp != NULL) {
1908 fwrite(smr.ser, smr.len, 1, network_fp);
1914 phree(msg->cm_fields['R']);
1915 phree(msg->cm_fields['D']);
1916 msg->cm_fields['R'] = hold_R;
1917 msg->cm_fields['D'] = hold_D;
1920 /* Go back to the room we started from */
1921 lprintf(9, "Returning to original room\n");
1922 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1923 getroom(&CC->quickroom, hold_rm);
1925 /* For internet mail, generate delivery instructions.
1926 * Yes, this is recursive. Deal with it. Infinite recursion does
1927 * not happen because the delivery instructions message does not
1928 * contain a recipient.
1931 if (recps->num_internet > 0) {
1932 lprintf(9, "Generating delivery instructions\n");
1933 instr = mallok(SIZ * 2);
1935 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1937 SPOOLMIME, newmsgid, (long)time(NULL),
1938 msg->cm_fields['A'], msg->cm_fields['N']
1941 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
1942 extract(recipient, recps->recp_internet, i);
1943 sprintf(&instr[strlen(instr)],
1944 "remote|%s|0||\n", recipient);
1947 imsg = mallok(sizeof(struct CtdlMessage));
1948 memset(imsg, 0, sizeof(struct CtdlMessage));
1949 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1950 imsg->cm_anon_type = MES_NORMAL;
1951 imsg->cm_format_type = FMT_RFC822;
1952 imsg->cm_fields['A'] = strdoop("Citadel");
1953 imsg->cm_fields['M'] = instr;
1954 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
1955 CtdlFreeMessage(imsg);
1964 * Convenience function for generating small administrative messages.
1966 void quickie_message(char *from, char *to, char *room, char *text)
1968 struct CtdlMessage *msg;
1970 msg = mallok(sizeof(struct CtdlMessage));
1971 memset(msg, 0, sizeof(struct CtdlMessage));
1972 msg->cm_magic = CTDLMESSAGE_MAGIC;
1973 msg->cm_anon_type = MES_NORMAL;
1974 msg->cm_format_type = 0;
1975 msg->cm_fields['A'] = strdoop(from);
1976 msg->cm_fields['O'] = strdoop(room);
1977 msg->cm_fields['N'] = strdoop(NODENAME);
1979 msg->cm_fields['R'] = strdoop(to);
1980 msg->cm_fields['M'] = strdoop(text);
1982 CtdlSubmitMsg(msg, NULL, room);
1983 CtdlFreeMessage(msg);
1984 syslog(LOG_NOTICE, text);
1990 * Back end function used by make_message() and similar functions
1992 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
1993 size_t maxlen, /* maximum message length */
1994 char *exist /* if non-null, append to it;
1995 exist is ALWAYS freed */
1999 size_t message_len = 0;
2000 size_t buffer_len = 0;
2004 if (exist == NULL) {
2008 m = reallok(exist, strlen(exist) + 4096);
2009 if (m == NULL) phree(exist);
2012 /* flush the input if we have nowhere to store it */
2014 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2018 /* otherwise read it into memory */
2024 /* read in the lines of message text one by one */
2025 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2027 /* strip trailing newline type stuff */
2028 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2029 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2031 linelen = strlen(buf);
2033 /* augment the buffer if we have to */
2034 if ((message_len + linelen + 2) > buffer_len) {
2035 lprintf(9, "realloking\n");
2036 ptr = reallok(m, (buffer_len * 2) );
2037 if (ptr == NULL) { /* flush if can't allocate */
2038 while ( (client_gets(buf)>0) &&
2039 strcmp(buf, terminator)) ;;
2042 buffer_len = (buffer_len * 2);
2044 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2048 /* Add the new line to the buffer. NOTE: this loop must avoid
2049 * using functions like strcat() and strlen() because they
2050 * traverse the entire buffer upon every call, and doing that
2051 * for a multi-megabyte message slows it down beyond usability.
2053 strcpy(&m[message_len], buf);
2054 m[message_len + linelen] = '\n';
2055 m[message_len + linelen + 1] = 0;
2056 message_len = message_len + linelen + 1;
2058 /* if we've hit the max msg length, flush the rest */
2059 if (message_len >= maxlen) {
2060 while ( (client_gets(buf)>0)
2061 && strcmp(buf, terminator)) ;;
2072 * Build a binary message to be saved on disk.
2075 static struct CtdlMessage *make_message(
2076 struct usersupp *author, /* author's usersupp structure */
2077 char *recipient, /* NULL if it's not mail */
2078 char *room, /* room where it's going */
2079 int type, /* see MES_ types in header file */
2080 int format_type, /* variformat, plain text, MIME... */
2081 char *fake_name /* who we're masquerading as */
2083 char dest_node[SIZ];
2085 struct CtdlMessage *msg;
2087 msg = mallok(sizeof(struct CtdlMessage));
2088 memset(msg, 0, sizeof(struct CtdlMessage));
2089 msg->cm_magic = CTDLMESSAGE_MAGIC;
2090 msg->cm_anon_type = type;
2091 msg->cm_format_type = format_type;
2093 /* Don't confuse the poor folks if it's not routed mail. */
2094 strcpy(dest_node, "");
2098 sprintf(buf, "cit%ld", author->usernum); /* Path */
2099 msg->cm_fields['P'] = strdoop(buf);
2101 sprintf(buf, "%ld", (long)time(NULL)); /* timestamp */
2102 msg->cm_fields['T'] = strdoop(buf);
2104 if (fake_name[0]) /* author */
2105 msg->cm_fields['A'] = strdoop(fake_name);
2107 msg->cm_fields['A'] = strdoop(author->fullname);
2109 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2110 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2113 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2116 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2117 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2119 if (recipient[0] != 0) {
2120 msg->cm_fields['R'] = strdoop(recipient);
2122 if (dest_node[0] != 0) {
2123 msg->cm_fields['D'] = strdoop(dest_node);
2126 if (author == &CC->usersupp) {
2127 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2130 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2131 config.c_maxmsglen, NULL);
2138 * Check to see whether we have permission to post a message in the current
2139 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2140 * returns 0 on success.
2142 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2144 if (!(CC->logged_in)) {
2145 sprintf(errmsgbuf, "Not logged in.");
2146 return (ERROR + NOT_LOGGED_IN);
2149 if ((CC->usersupp.axlevel < 2)
2150 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2151 sprintf(errmsgbuf, "Need to be validated to enter "
2152 "(except in %s> to sysop)", MAILROOM);
2153 return (ERROR + HIGHER_ACCESS_REQUIRED);
2156 if ((CC->usersupp.axlevel < 4)
2157 && (CC->quickroom.QRflags & QR_NETWORK)) {
2158 sprintf(errmsgbuf, "Need net privileges to enter here.");
2159 return (ERROR + HIGHER_ACCESS_REQUIRED);
2162 if ((CC->usersupp.axlevel < 6)
2163 && (CC->quickroom.QRflags & QR_READONLY)) {
2164 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2165 return (ERROR + HIGHER_ACCESS_REQUIRED);
2168 strcpy(errmsgbuf, "Ok");
2174 * Validate recipients, count delivery types and errors, and handle aliasing
2175 * FIXME check for dupes!!!!!
2177 struct recptypes *validate_recipients(char *recipients) {
2178 struct recptypes *ret;
2179 char this_recp[SIZ];
2180 char this_recp_cooked[SIZ];
2186 struct usersupp tempUS;
2187 struct quickroom tempQR;
2190 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2191 if (ret == NULL) return(NULL);
2192 memset(ret, 0, sizeof(struct recptypes));
2195 ret->num_internet = 0;
2200 if (recipients == NULL) {
2203 else if (strlen(recipients) == 0) {
2207 /* Change all valid separator characters to commas */
2208 for (i=0; i<strlen(recipients); ++i) {
2209 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2210 recipients[i] = ',';
2215 num_recps = num_tokens(recipients, ',');
2218 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2219 extract_token(this_recp, recipients, i, ',');
2221 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2222 mailtype = alias(this_recp);
2223 mailtype = alias(this_recp);
2224 mailtype = alias(this_recp);
2225 for (j=0; j<=strlen(this_recp); ++j) {
2226 if (this_recp[j]=='_') {
2227 this_recp_cooked[j] = ' ';
2230 this_recp_cooked[j] = this_recp[j];
2236 if (!strcasecmp(this_recp, "sysop")) {
2238 strcpy(this_recp, AIDEROOM);
2239 if (strlen(ret->recp_room) > 0) {
2240 strcat(ret->recp_room, "|");
2242 strcat(ret->recp_room, this_recp);
2244 else if (getuser(&tempUS, this_recp) == 0) {
2246 strcpy(this_recp, tempUS.fullname);
2247 if (strlen(ret->recp_local) > 0) {
2248 strcat(ret->recp_local, "|");
2250 strcat(ret->recp_local, this_recp);
2252 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2254 strcpy(this_recp, tempUS.fullname);
2255 if (strlen(ret->recp_local) > 0) {
2256 strcat(ret->recp_local, "|");
2258 strcat(ret->recp_local, this_recp);
2260 else if ( (!strncasecmp(this_recp, "room_", 5))
2261 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2263 if (strlen(ret->recp_room) > 0) {
2264 strcat(ret->recp_room, "|");
2266 strcat(ret->recp_room, &this_recp_cooked[5]);
2274 ++ret->num_internet;
2275 if (strlen(ret->recp_internet) > 0) {
2276 strcat(ret->recp_internet, "|");
2278 strcat(ret->recp_internet, this_recp);
2282 if (strlen(ret->recp_ignet) > 0) {
2283 strcat(ret->recp_ignet, "|");
2285 strcat(ret->recp_ignet, this_recp);
2293 if (strlen(ret->errormsg) == 0) {
2295 "Invalid recipient: %s",
2302 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2303 strcat(ret->errormsg, append);
2307 if (strlen(ret->display_recp) == 0) {
2308 strcpy(append, this_recp);
2311 sprintf(append, ", %s", this_recp);
2313 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2314 strcat(ret->display_recp, append);
2319 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2320 ret->num_room + ret->num_error) == 0) {
2322 strcpy(ret->errormsg, "No recipients specified.");
2325 lprintf(9, "validate_recipients()\n");
2326 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2327 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2328 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2329 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2330 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2338 * message entry - mode 0 (normal)
2340 void cmd_ent0(char *entargs)
2344 char masquerade_as[SIZ];
2346 int format_type = 0;
2347 char newusername[SIZ];
2348 struct CtdlMessage *msg;
2352 struct recptypes *valid = NULL;
2354 post = extract_int(entargs, 0);
2355 extract(recp, entargs, 1);
2356 anon_flag = extract_int(entargs, 2);
2357 format_type = extract_int(entargs, 3);
2359 /* first check to make sure the request is valid. */
2361 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg);
2363 cprintf("%d %s\n", err, errmsg);
2367 /* Check some other permission type things. */
2370 if (CC->usersupp.axlevel < 6) {
2371 cprintf("%d You don't have permission to masquerade.\n",
2372 ERROR + HIGHER_ACCESS_REQUIRED);
2375 extract(newusername, entargs, 4);
2376 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2377 safestrncpy(CC->fake_postname, newusername,
2378 sizeof(CC->fake_postname) );
2379 cprintf("%d ok\n", OK);
2382 CC->cs_flags |= CS_POSTING;
2384 if (CC->quickroom.QRflags & QR_MAILBOX) {
2385 if (CC->usersupp.axlevel < 2) {
2386 strcpy(recp, "sysop");
2389 valid = validate_recipients(recp);
2390 if (valid->num_error > 0) {
2392 ERROR + NO_SUCH_USER, valid->errormsg);
2397 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2398 && (CC->usersupp.axlevel < 4) ) {
2399 cprintf("%d Higher access required for network mail.\n",
2400 ERROR + HIGHER_ACCESS_REQUIRED);
2405 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2406 && ((CC->usersupp.flags & US_INTERNET) == 0)
2407 && (!CC->internal_pgm)) {
2408 cprintf("%d You don't have access to Internet mail.\n",
2409 ERROR + HIGHER_ACCESS_REQUIRED);
2416 /* Is this a room which has anonymous-only or anonymous-option? */
2417 anonymous = MES_NORMAL;
2418 if (CC->quickroom.QRflags & QR_ANONONLY) {
2419 anonymous = MES_ANONONLY;
2421 if (CC->quickroom.QRflags & QR_ANONOPT) {
2422 if (anon_flag == 1) { /* only if the user requested it */
2423 anonymous = MES_ANONOPT;
2427 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2431 /* If we're only checking the validity of the request, return
2432 * success without creating the message.
2435 cprintf("%d %s\n", OK,
2436 ((valid != NULL) ? valid->display_recp : "") );
2441 /* Handle author masquerading */
2442 if (CC->fake_postname[0]) {
2443 strcpy(masquerade_as, CC->fake_postname);
2445 else if (CC->fake_username[0]) {
2446 strcpy(masquerade_as, CC->fake_username);
2449 strcpy(masquerade_as, "");
2452 /* Read in the message from the client. */
2453 cprintf("%d send message\n", SEND_LISTING);
2454 msg = make_message(&CC->usersupp, recp,
2455 CC->quickroom.QRname, anonymous, format_type, masquerade_as);
2458 CtdlSubmitMsg(msg, valid, "");
2459 CtdlFreeMessage(msg);
2461 CC->fake_postname[0] = '\0';
2469 * API function to delete messages which match a set of criteria
2470 * (returns the actual number of messages deleted)
2472 int CtdlDeleteMessages(char *room_name, /* which room */
2473 long dmsgnum, /* or "0" for any */
2474 char *content_type /* or "" for any */
2478 struct quickroom qrbuf;
2479 struct cdbdata *cdbfr;
2480 long *msglist = NULL;
2481 long *dellist = NULL;
2484 int num_deleted = 0;
2486 struct MetaData smi;
2488 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2489 room_name, dmsgnum, content_type);
2491 /* get room record, obtaining a lock... */
2492 if (lgetroom(&qrbuf, room_name) != 0) {
2493 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2495 return (0); /* room not found */
2497 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2499 if (cdbfr != NULL) {
2500 msglist = mallok(cdbfr->len);
2501 dellist = mallok(cdbfr->len);
2502 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2503 num_msgs = cdbfr->len / sizeof(long);
2507 for (i = 0; i < num_msgs; ++i) {
2510 /* Set/clear a bit for each criterion */
2512 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2513 delete_this |= 0x01;
2515 if (strlen(content_type) == 0) {
2516 delete_this |= 0x02;
2518 GetMetaData(&smi, msglist[i]);
2519 if (!strcasecmp(smi.meta_content_type,
2521 delete_this |= 0x02;
2525 /* Delete message only if all bits are set */
2526 if (delete_this == 0x03) {
2527 dellist[num_deleted++] = msglist[i];
2532 num_msgs = sort_msglist(msglist, num_msgs);
2533 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2534 msglist, (num_msgs * sizeof(long)));
2536 qrbuf.QRhighest = msglist[num_msgs - 1];
2540 /* Go through the messages we pulled out of the index, and decrement
2541 * their reference counts by 1. If this is the only room the message
2542 * was in, the reference count will reach zero and the message will
2543 * automatically be deleted from the database. We do this in a
2544 * separate pass because there might be plug-in hooks getting called,
2545 * and we don't want that happening during an S_QUICKROOM critical
2548 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2549 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2550 AdjRefCount(dellist[i], -1);
2553 /* Now free the memory we used, and go away. */
2554 if (msglist != NULL) phree(msglist);
2555 if (dellist != NULL) phree(dellist);
2556 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2557 return (num_deleted);
2563 * Check whether the current user has permission to delete messages from
2564 * the current room (returns 1 for yes, 0 for no)
2566 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2567 getuser(&CC->usersupp, CC->curr_user);
2568 if ((CC->usersupp.axlevel < 6)
2569 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2570 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2571 && (!(CC->internal_pgm))) {
2580 * Delete message from current room
2582 void cmd_dele(char *delstr)
2587 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2588 cprintf("%d Higher access required.\n",
2589 ERROR + HIGHER_ACCESS_REQUIRED);
2592 delnum = extract_long(delstr, 0);
2594 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2597 cprintf("%d %d message%s deleted.\n", OK,
2598 num_deleted, ((num_deleted != 1) ? "s" : ""));
2600 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2606 * Back end API function for moves and deletes
2608 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2611 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2612 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2613 if (err != 0) return(err);
2621 * move or copy a message to another room
2623 void cmd_move(char *args)
2627 struct quickroom qtemp;
2631 num = extract_long(args, 0);
2632 extract(targ, args, 1);
2633 targ[ROOMNAMELEN - 1] = 0;
2634 is_copy = extract_int(args, 2);
2636 if (getroom(&qtemp, targ) != 0) {
2637 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2641 getuser(&CC->usersupp, CC->curr_user);
2642 /* Aides can move/copy */
2643 if ((CC->usersupp.axlevel < 6)
2644 /* Roomaides can move/copy */
2645 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2646 /* Permit move/copy to/from personal rooms */
2647 && (!((CC->quickroom.QRflags & QR_MAILBOX)
2648 && (qtemp.QRflags & QR_MAILBOX)))
2649 /* Permit only copy from public to personal room */
2650 && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2651 && (qtemp.QRflags & QR_MAILBOX)))) {
2652 cprintf("%d Higher access required.\n",
2653 ERROR + HIGHER_ACCESS_REQUIRED);
2657 err = CtdlCopyMsgToRoom(num, targ);
2659 cprintf("%d Cannot store message in %s: error %d\n",
2664 /* Now delete the message from the source room,
2665 * if this is a 'move' rather than a 'copy' operation.
2668 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2671 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2677 * GetMetaData() - Get the supplementary record for a message
2679 void GetMetaData(struct MetaData *smibuf, long msgnum)
2682 struct cdbdata *cdbsmi;
2685 memset(smibuf, 0, sizeof(struct MetaData));
2686 smibuf->meta_msgnum = msgnum;
2687 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2689 /* Use the negative of the message number for its supp record index */
2690 TheIndex = (0L - msgnum);
2692 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2693 if (cdbsmi == NULL) {
2694 return; /* record not found; go with defaults */
2696 memcpy(smibuf, cdbsmi->ptr,
2697 ((cdbsmi->len > sizeof(struct MetaData)) ?
2698 sizeof(struct MetaData) : cdbsmi->len));
2705 * PutMetaData() - (re)write supplementary record for a message
2707 void PutMetaData(struct MetaData *smibuf)
2711 /* Use the negative of the message number for the metadata db index */
2712 TheIndex = (0L - smibuf->meta_msgnum);
2714 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2715 smibuf->meta_msgnum, smibuf->meta_refcount);
2717 cdb_store(CDB_MSGMAIN,
2718 &TheIndex, sizeof(long),
2719 smibuf, sizeof(struct MetaData));
2724 * AdjRefCount - change the reference count for a message;
2725 * delete the message if it reaches zero
2727 void AdjRefCount(long msgnum, int incr)
2730 struct MetaData smi;
2733 /* This is a *tight* critical section; please keep it that way, as
2734 * it may get called while nested in other critical sections.
2735 * Complicating this any further will surely cause deadlock!
2737 begin_critical_section(S_SUPPMSGMAIN);
2738 GetMetaData(&smi, msgnum);
2739 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2740 msgnum, smi.meta_refcount);
2741 smi.meta_refcount += incr;
2743 end_critical_section(S_SUPPMSGMAIN);
2744 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2745 msgnum, smi.meta_refcount);
2747 /* If the reference count is now zero, delete the message
2748 * (and its supplementary record as well).
2750 if (smi.meta_refcount == 0) {
2751 lprintf(9, "Deleting message <%ld>\n", msgnum);
2753 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2755 /* We have to delete the metadata record too! */
2756 delnum = (0L - msgnum);
2757 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2762 * Write a generic object to this room
2764 * Note: this could be much more efficient. Right now we use two temporary
2765 * files, and still pull the message into memory as with all others.
2767 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2768 char *content_type, /* MIME type of this object */
2769 char *tempfilename, /* Where to fetch it from */
2770 struct usersupp *is_mailbox, /* Mailbox room? */
2771 int is_binary, /* Is encoding necessary? */
2772 int is_unique, /* Del others of this type? */
2773 unsigned int flags /* Internal save flags */
2778 char filename[PATH_MAX];
2781 struct quickroom qrbuf;
2782 char roomname[ROOMNAMELEN];
2783 struct CtdlMessage *msg;
2786 if (is_mailbox != NULL)
2787 MailboxName(roomname, is_mailbox, req_room);
2789 safestrncpy(roomname, req_room, sizeof(roomname));
2790 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2792 strcpy(filename, tmpnam(NULL));
2793 fp = fopen(filename, "w");
2797 tempfp = fopen(tempfilename, "r");
2798 if (tempfp == NULL) {
2804 fprintf(fp, "Content-type: %s\n", content_type);
2805 lprintf(9, "Content-type: %s\n", content_type);
2807 if (is_binary == 0) {
2808 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2809 while (ch = getc(tempfp), ch > 0)
2815 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2818 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2819 tempfilename, filename);
2823 lprintf(9, "Allocating\n");
2824 msg = mallok(sizeof(struct CtdlMessage));
2825 memset(msg, 0, sizeof(struct CtdlMessage));
2826 msg->cm_magic = CTDLMESSAGE_MAGIC;
2827 msg->cm_anon_type = MES_NORMAL;
2828 msg->cm_format_type = 4;
2829 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2830 msg->cm_fields['O'] = strdoop(req_room);
2831 msg->cm_fields['N'] = strdoop(config.c_nodename);
2832 msg->cm_fields['H'] = strdoop(config.c_humannode);
2833 msg->cm_flags = flags;
2835 lprintf(9, "Loading\n");
2836 fp = fopen(filename, "rb");
2837 fseek(fp, 0L, SEEK_END);
2840 msg->cm_fields['M'] = mallok(len);
2841 fread(msg->cm_fields['M'], len, 1, fp);
2845 /* Create the requested room if we have to. */
2846 if (getroom(&qrbuf, roomname) != 0) {
2847 create_room(roomname,
2848 ( (is_mailbox != NULL) ? 5 : 3 ),
2851 /* If the caller specified this object as unique, delete all
2852 * other objects of this type that are currently in the room.
2855 lprintf(9, "Deleted %d other msgs of this type\n",
2856 CtdlDeleteMessages(roomname, 0L, content_type));
2858 /* Now write the data */
2859 CtdlSubmitMsg(msg, NULL, roomname);
2860 CtdlFreeMessage(msg);
2868 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2869 config_msgnum = msgnum;
2873 char *CtdlGetSysConfig(char *sysconfname) {
2874 char hold_rm[ROOMNAMELEN];
2877 struct CtdlMessage *msg;
2880 strcpy(hold_rm, CC->quickroom.QRname);
2881 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2882 getroom(&CC->quickroom, hold_rm);
2887 /* We want the last (and probably only) config in this room */
2888 begin_critical_section(S_CONFIG);
2889 config_msgnum = (-1L);
2890 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2891 CtdlGetSysConfigBackend, NULL);
2892 msgnum = config_msgnum;
2893 end_critical_section(S_CONFIG);
2899 msg = CtdlFetchMessage(msgnum);
2901 conf = strdoop(msg->cm_fields['M']);
2902 CtdlFreeMessage(msg);
2909 getroom(&CC->quickroom, hold_rm);
2911 if (conf != NULL) do {
2912 extract_token(buf, conf, 0, '\n');
2913 strcpy(conf, &conf[strlen(buf)+1]);
2914 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2919 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2920 char temp[PATH_MAX];
2923 strcpy(temp, tmpnam(NULL));
2925 fp = fopen(temp, "w");
2926 if (fp == NULL) return;
2927 fprintf(fp, "%s", sysconfdata);
2930 /* this handy API function does all the work for us */
2931 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);