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;
64 * This really belongs in serv_network.c, but I don't know how to export
65 * symbols between modules.
67 struct FilterList *filterlist = NULL;
71 * These are the four-character field headers we use when outputting
72 * messages in Citadel format (as opposed to RFC822 format).
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
109 * This function is self explanatory.
110 * (What can I say, I'm in a weird mood today...)
112 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
116 for (i = 0; i < strlen(name); ++i) {
117 if (name[i] == '@') {
118 while (isspace(name[i - 1]) && i > 0) {
119 strcpy(&name[i - 1], &name[i]);
122 while (isspace(name[i + 1])) {
123 strcpy(&name[i + 1], &name[i + 2]);
131 * Aliasing for network mail.
132 * (Error messages have been commented out, because this is a server.)
134 int alias(char *name)
135 { /* process alias and routing info for mail */
138 char aaa[SIZ], bbb[SIZ];
139 char *ignetcfg = NULL;
140 char *ignetmap = NULL;
147 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
149 fp = fopen("network/mail.aliases", "r");
151 fp = fopen("/dev/null", "r");
158 while (fgets(aaa, sizeof aaa, fp) != NULL) {
159 while (isspace(name[0]))
160 strcpy(name, &name[1]);
161 aaa[strlen(aaa) - 1] = 0;
163 for (a = 0; a < strlen(aaa); ++a) {
165 strcpy(bbb, &aaa[a + 1]);
169 if (!strcasecmp(name, aaa))
174 /* Hit the Global Address Book */
175 if (CtdlDirectoryLookup(aaa, name) == 0) {
179 lprintf(7, "Mail is being forwarded to %s\n", name);
181 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
182 for (a=0; a<strlen(name); ++a) {
183 if (name[a] == '@') {
184 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
186 lprintf(7, "Changed to <%s>\n", name);
191 /* determine local or remote type, see citadel.h */
192 at = haschar(name, '@');
193 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
194 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
195 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
197 /* figure out the delivery mode */
198 extract_token(node, name, 1, '@');
200 /* If there are one or more dots in the nodename, we assume that it
201 * is an FQDN and will attempt SMTP delivery to the Internet.
203 if (haschar(node, '.') > 0) {
204 return(MES_INTERNET);
207 /* Otherwise we look in the IGnet maps for a valid Citadel node.
208 * Try directly-connected nodes first...
210 ignetcfg = CtdlGetSysConfig(IGNETCFG);
211 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
212 extract_token(buf, ignetcfg, i, '\n');
213 extract_token(testnode, buf, 0, '|');
214 if (!strcasecmp(node, testnode)) {
222 * Then try nodes that are two or more hops away.
224 ignetmap = CtdlGetSysConfig(IGNETMAP);
225 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
226 extract_token(buf, ignetmap, i, '\n');
227 extract_token(testnode, buf, 0, '|');
228 if (!strcasecmp(node, testnode)) {
235 /* If we get to this point it's an invalid node name */
244 fp = fopen("citadel.control", "r");
245 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
258 /* Determine if a given message matches the fields in a message template.
259 * Return 0 for a successful match.
261 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
264 /* If there aren't any fields in the template, all messages will
267 if (template == NULL) return(0);
269 /* Null messages are bogus. */
270 if (msg == NULL) return(1);
272 for (i='A'; i<='Z'; ++i) {
273 if (template->cm_fields[i] != NULL) {
274 if (msg->cm_fields[i] == NULL) {
277 if (strcasecmp(msg->cm_fields[i],
278 template->cm_fields[i])) return 1;
282 /* All compares succeeded: we have a match! */
288 * Manipulate the "seen msgs" string.
290 void CtdlSetSeen(long target_msgnum, int target_setting) {
292 struct cdbdata *cdbfr;
302 /* Learn about the user and room in question */
304 getuser(&CC->usersupp, CC->curr_user);
305 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
307 /* Load the message list */
308 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
310 msglist = mallok(cdbfr->len);
311 memcpy(msglist, cdbfr->ptr, cdbfr->len);
312 num_msgs = cdbfr->len / sizeof(long);
315 return; /* No messages at all? No further action. */
318 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
321 for (i=0; i<num_msgs; ++i) {
324 if (msglist[i] == target_msgnum) {
325 is_seen = target_setting;
328 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
334 if (lo < 0L) lo = msglist[i];
337 if ( ((is_seen == 0) && (was_seen == 1))
338 || ((is_seen == 1) && (i == num_msgs-1)) ) {
341 if ( (strlen(newseen) + 20) > SIZ) {
342 strcpy(newseen, &newseen[20]);
345 tmp = strlen(newseen);
347 strcat(newseen, ",");
351 snprintf(&newseen[tmp], sizeof newseen - tmp,
355 snprintf(&newseen[tmp], sizeof newseen - tmp,
364 safestrncpy(vbuf.v_seen, newseen, SIZ);
365 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
367 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
372 * API function to perform an operation for each qualifying message in the
373 * current room. (Returns the number of messages processed.)
375 int CtdlForEachMessage(int mode, long ref,
377 struct CtdlMessage *compare,
378 void (*CallBack) (long, void *),
384 struct cdbdata *cdbfr;
385 long *msglist = NULL;
387 int num_processed = 0;
390 struct CtdlMessage *msg;
393 int printed_lastold = 0;
395 /* Learn about the user and room in question */
397 getuser(&CC->usersupp, CC->curr_user);
398 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
400 /* Load the message list */
401 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
403 msglist = mallok(cdbfr->len);
404 memcpy(msglist, cdbfr->ptr, cdbfr->len);
405 num_msgs = cdbfr->len / sizeof(long);
408 return 0; /* No messages at all? No further action. */
413 * Now begin the traversal.
415 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
417 /* If the caller is looking for a specific MIME type, filter
418 * out all messages which are not of the type requested.
420 if (content_type != NULL) if (strlen(content_type) > 0) {
422 /* This call to GetMetaData() sits inside this loop
423 * so that we only do the extra database read per msg
424 * if we need to. Doing the extra read all the time
425 * really kills the server. If we ever need to use
426 * metadata for another search criterion, we need to
427 * move the read somewhere else -- but still be smart
428 * enough to only do the read if the caller has
429 * specified something that will need it.
431 GetMetaData(&smi, msglist[a]);
433 if (strcasecmp(smi.meta_content_type, content_type)) {
439 num_msgs = sort_msglist(msglist, num_msgs);
441 /* If a template was supplied, filter out the messages which
442 * don't match. (This could induce some delays!)
445 if (compare != NULL) {
446 for (a = 0; a < num_msgs; ++a) {
447 msg = CtdlFetchMessage(msglist[a]);
449 if (CtdlMsgCmp(msg, compare)) {
452 CtdlFreeMessage(msg);
460 * Now iterate through the message list, according to the
461 * criteria supplied by the caller.
464 for (a = 0; a < num_msgs; ++a) {
465 thismsg = msglist[a];
466 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
467 if (is_seen) lastold = thismsg;
472 || ((mode == MSGS_OLD) && (is_seen))
473 || ((mode == MSGS_NEW) && (!is_seen))
474 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
475 || ((mode == MSGS_FIRST) && (a < ref))
476 || ((mode == MSGS_GT) && (thismsg > ref))
477 || ((mode == MSGS_EQ) && (thismsg == ref))
480 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
482 CallBack(lastold, userdata);
486 if (CallBack) CallBack(thismsg, userdata);
490 phree(msglist); /* Clean up */
491 return num_processed;
497 * cmd_msgs() - get list of message #'s in this room
498 * implements the MSGS server command using CtdlForEachMessage()
500 void cmd_msgs(char *cmdbuf)
509 int with_template = 0;
510 struct CtdlMessage *template = NULL;
512 extract(which, cmdbuf, 0);
513 cm_ref = extract_int(cmdbuf, 1);
514 with_template = extract_int(cmdbuf, 2);
518 if (!strncasecmp(which, "OLD", 3))
520 else if (!strncasecmp(which, "NEW", 3))
522 else if (!strncasecmp(which, "FIRST", 5))
524 else if (!strncasecmp(which, "LAST", 4))
526 else if (!strncasecmp(which, "GT", 2))
529 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
530 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
535 cprintf("%d Send template then receive message list\n",
537 template = (struct CtdlMessage *)
538 mallok(sizeof(struct CtdlMessage));
539 memset(template, 0, sizeof(struct CtdlMessage));
540 while(client_gets(buf), strcmp(buf,"000")) {
541 extract(tfield, buf, 0);
542 extract(tvalue, buf, 1);
543 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
544 if (!strcasecmp(tfield, msgkeys[i])) {
545 template->cm_fields[i] =
552 cprintf("%d Message list...\n", LISTING_FOLLOWS);
555 CtdlForEachMessage(mode, cm_ref,
556 NULL, template, simple_listing, NULL);
557 if (template != NULL) CtdlFreeMessage(template);
565 * help_subst() - support routine for help file viewer
567 void help_subst(char *strbuf, char *source, char *dest)
572 while (p = pattern2(strbuf, source), (p >= 0)) {
573 strcpy(workbuf, &strbuf[p + strlen(source)]);
574 strcpy(&strbuf[p], dest);
575 strcat(strbuf, workbuf);
580 void do_help_subst(char *buffer)
584 help_subst(buffer, "^nodename", config.c_nodename);
585 help_subst(buffer, "^humannode", config.c_humannode);
586 help_subst(buffer, "^fqdn", config.c_fqdn);
587 help_subst(buffer, "^username", CC->usersupp.fullname);
588 snprintf(buf2, sizeof buf2, "%ld", CC->usersupp.usernum);
589 help_subst(buffer, "^usernum", buf2);
590 help_subst(buffer, "^sysadm", config.c_sysadm);
591 help_subst(buffer, "^variantname", CITADEL);
592 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
593 help_subst(buffer, "^maxsessions", buf2);
599 * memfmout() - Citadel text formatter and paginator.
600 * Although the original purpose of this routine was to format
601 * text to the reader's screen width, all we're really using it
602 * for here is to format text out to 80 columns before sending it
603 * to the client. The client software may reformat it again.
606 int width, /* screen width to use */
607 char *mptr, /* where are we going to get our text from? */
608 char subst, /* nonzero if we should do substitutions */
609 char *nl) /* string to terminate lines with */
621 c = 1; /* c is the current pos */
625 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
627 buffer[strlen(buffer) + 1] = 0;
628 buffer[strlen(buffer)] = ch;
631 if (buffer[0] == '^')
632 do_help_subst(buffer);
634 buffer[strlen(buffer) + 1] = 0;
636 strcpy(buffer, &buffer[1]);
644 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
646 if (((old == 13) || (old == 10)) && (isspace(real))) {
654 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
655 cprintf("%s%s", nl, aaa);
664 if ((strlen(aaa) + c) > (width - 5)) {
673 if ((ch == 13) || (ch == 10)) {
674 cprintf("%s%s", aaa, nl);
681 cprintf("%s%s", aaa, nl);
687 * Callback function for mime parser that simply lists the part
689 void list_this_part(char *name, char *filename, char *partnum, char *disp,
690 void *content, char *cbtype, size_t length, char *encoding,
694 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
695 name, filename, partnum, disp, cbtype, (long)length);
700 * Callback function for mime parser that opens a section for downloading
702 void mime_download(char *name, char *filename, char *partnum, char *disp,
703 void *content, char *cbtype, size_t length, char *encoding,
707 /* Silently go away if there's already a download open... */
708 if (CC->download_fp != NULL)
711 /* ...or if this is not the desired section */
712 if (strcasecmp(desired_section, partnum))
715 CC->download_fp = tmpfile();
716 if (CC->download_fp == NULL)
719 fwrite(content, length, 1, CC->download_fp);
720 fflush(CC->download_fp);
721 rewind(CC->download_fp);
723 OpenCmdResult(filename, cbtype);
729 * Load a message from disk into memory.
730 * This is used by CtdlOutputMsg() and other fetch functions.
732 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
733 * using the CtdlMessageFree() function.
735 struct CtdlMessage *CtdlFetchMessage(long msgnum)
737 struct cdbdata *dmsgtext;
738 struct CtdlMessage *ret = NULL;
741 CIT_UBYTE field_header;
744 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
745 if (dmsgtext == NULL) {
748 mptr = dmsgtext->ptr;
750 /* Parse the three bytes that begin EVERY message on disk.
751 * The first is always 0xFF, the on-disk magic number.
752 * The second is the anonymous/public type byte.
753 * The third is the format type byte (vari, fixed, or MIME).
757 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
761 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
762 memset(ret, 0, sizeof(struct CtdlMessage));
764 ret->cm_magic = CTDLMESSAGE_MAGIC;
765 ret->cm_anon_type = *mptr++; /* Anon type byte */
766 ret->cm_format_type = *mptr++; /* Format type byte */
769 * The rest is zero or more arbitrary fields. Load them in.
770 * We're done when we encounter either a zero-length field or
771 * have just processed the 'M' (message text) field.
774 field_length = strlen(mptr);
775 if (field_length == 0)
777 field_header = *mptr++;
778 ret->cm_fields[field_header] = mallok(field_length);
779 strcpy(ret->cm_fields[field_header], mptr);
781 while (*mptr++ != 0); /* advance to next field */
783 } while ((field_length > 0) && (field_header != 'M'));
787 /* Always make sure there's something in the msg text field */
788 if (ret->cm_fields['M'] == NULL)
789 ret->cm_fields['M'] = strdoop("<no text>\n");
791 /* Perform "before read" hooks (aborting if any return nonzero) */
792 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
793 CtdlFreeMessage(ret);
802 * Returns 1 if the supplied pointer points to a valid Citadel message.
803 * If the pointer is NULL or the magic number check fails, returns 0.
805 int is_valid_message(struct CtdlMessage *msg) {
808 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
809 lprintf(3, "is_valid_message() -- self-check failed\n");
817 * 'Destructor' for struct CtdlMessage
819 void CtdlFreeMessage(struct CtdlMessage *msg)
823 if (is_valid_message(msg) == 0) return;
825 for (i = 0; i < 256; ++i)
826 if (msg->cm_fields[i] != NULL) {
827 phree(msg->cm_fields[i]);
830 msg->cm_magic = 0; /* just in case */
836 * Pre callback function for multipart/alternative
838 * NOTE: this differs from the standard behavior for a reason. Normally when
839 * displaying multipart/alternative you want to show the _last_ usable
840 * format in the message. Here we show the _first_ one, because it's
841 * usually text/plain. Since this set of functions is designed for text
842 * output to non-MIME-aware clients, this is the desired behavior.
845 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
846 void *content, char *cbtype, size_t length, char *encoding,
849 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);
850 if (!strcasecmp(cbtype, "multipart/alternative")) {
858 * Post callback function for multipart/alternative
860 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
861 void *content, char *cbtype, size_t length, char *encoding,
864 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);
865 if (!strcasecmp(cbtype, "multipart/alternative")) {
873 * Inline callback function for mime parser that wants to display text
875 void fixed_output(char *name, char *filename, char *partnum, char *disp,
876 void *content, char *cbtype, size_t length, char *encoding,
884 lprintf(9, "fixed_output() type=<%s>\n", cbtype);
887 * If we're in the middle of a multipart/alternative scope and
888 * we've already printed another section, skip this one.
890 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
891 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
896 if ( (!strcasecmp(cbtype, "text/plain"))
897 || (strlen(cbtype)==0) ) {
903 if (ch==10) cprintf("\r\n");
904 else cprintf("%c", ch);
908 if (ch != '\n') cprintf("\n");
910 else if (!strcasecmp(cbtype, "text/html")) {
911 ptr = html_to_ascii(content, 80, 0);
916 if (ch==10) cprintf("\r\n");
917 else cprintf("%c", ch);
921 else if (strncasecmp(cbtype, "multipart/", 10)) {
922 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
923 partnum, filename, cbtype, (long)length);
929 * Get a message off disk. (returns om_* values found in msgbase.h)
932 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
933 int mode, /* how would you like that message? */
934 int headers_only, /* eschew the message body? */
935 int do_proto, /* do Citadel protocol responses? */
936 int crlf /* Use CRLF newlines instead of LF? */
938 struct CtdlMessage *TheMessage;
941 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
946 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
947 if (do_proto) cprintf("%d Not logged in.\n",
948 ERROR + NOT_LOGGED_IN);
949 return(om_not_logged_in);
952 /* FIXME ... small security issue
953 * We need to check to make sure the requested message is actually
954 * in the current room, and set msg_ok to 1 only if it is. This
955 * functionality is currently missing because I'm in a hurry to replace
956 * broken production code with nonbroken pre-beta code. :( -- ajc
959 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
961 return(om_no_such_msg);
966 * Fetch the message from disk
968 TheMessage = CtdlFetchMessage(msg_num);
969 if (TheMessage == NULL) {
970 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
972 return(om_no_such_msg);
975 retcode = CtdlOutputPreLoadedMsg(
976 TheMessage, msg_num, mode,
977 headers_only, do_proto, crlf);
979 CtdlFreeMessage(TheMessage);
985 * Get a message off disk. (returns om_* values found in msgbase.h)
988 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
990 int mode, /* how would you like that message? */
991 int headers_only, /* eschew the message body? */
992 int do_proto, /* do Citadel protocol responses? */
993 int crlf /* Use CRLF newlines instead of LF? */
999 char display_name[SIZ];
1001 char *nl; /* newline string */
1004 /* buffers needed for RFC822 translation */
1011 char datestamp[SIZ];
1014 snprintf(mid, sizeof mid, "%ld", msg_num);
1015 nl = (crlf ? "\r\n" : "\n");
1017 if (!is_valid_message(TheMessage)) {
1018 lprintf(1, "ERROR: invalid preloaded message for output\n");
1019 return(om_no_such_msg);
1022 /* Are we downloading a MIME component? */
1023 if (mode == MT_DOWNLOAD) {
1024 if (TheMessage->cm_format_type != FMT_RFC822) {
1026 cprintf("%d This is not a MIME message.\n",
1028 } else if (CC->download_fp != NULL) {
1029 if (do_proto) cprintf(
1030 "%d You already have a download open.\n",
1033 /* Parse the message text component */
1034 mptr = TheMessage->cm_fields['M'];
1035 mime_parser(mptr, NULL,
1036 *mime_download, NULL, NULL,
1038 /* If there's no file open by this time, the requested
1039 * section wasn't found, so print an error
1041 if (CC->download_fp == NULL) {
1042 if (do_proto) cprintf(
1043 "%d Section %s not found.\n",
1044 ERROR + FILE_NOT_FOUND,
1048 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1051 /* now for the user-mode message reading loops */
1052 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1054 /* Tell the client which format type we're using. If this is a
1055 * MIME message, *lie* about it and tell the user it's fixed-format.
1057 if (mode == MT_CITADEL) {
1058 if (TheMessage->cm_format_type == FMT_RFC822) {
1059 if (do_proto) cprintf("type=1\n");
1062 if (do_proto) cprintf("type=%d\n",
1063 TheMessage->cm_format_type);
1067 /* nhdr=yes means that we're only displaying headers, no body */
1068 if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1069 if (do_proto) cprintf("nhdr=yes\n");
1072 /* begin header processing loop for Citadel message format */
1074 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1076 strcpy(display_name, "<unknown>");
1077 if (TheMessage->cm_fields['A']) {
1078 strcpy(buf, TheMessage->cm_fields['A']);
1079 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1080 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1081 strcpy(display_name, "****");
1083 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1084 strcpy(display_name, "anonymous");
1087 strcpy(display_name, buf);
1089 if ((is_room_aide())
1090 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1091 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1092 size_t tmp = strlen(display_name);
1093 snprintf(&display_name[tmp],
1094 sizeof display_name - tmp,
1099 /* Don't show Internet address for users on the
1100 * local Citadel network.
1103 if (TheMessage->cm_fields['N'] != NULL)
1104 if (strlen(TheMessage->cm_fields['N']) > 0)
1105 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1109 /* Now spew the header fields in the order we like them. */
1110 strcpy(allkeys, FORDER);
1111 for (i=0; i<strlen(allkeys); ++i) {
1112 k = (int) allkeys[i];
1114 if ( (TheMessage->cm_fields[k] != NULL)
1115 && (msgkeys[k] != NULL) ) {
1117 if (do_proto) cprintf("%s=%s\n",
1121 else if ((k == 'F') && (suppress_f)) {
1124 /* Masquerade display name if needed */
1126 if (do_proto) cprintf("%s=%s\n",
1128 TheMessage->cm_fields[k]
1137 /* begin header processing loop for RFC822 transfer format */
1142 strcpy(snode, NODENAME);
1143 strcpy(lnode, HUMANNODE);
1144 if (mode == MT_RFC822) {
1145 cprintf("X-UIDL: %ld%s", msg_num, nl);
1146 for (i = 0; i < 256; ++i) {
1147 if (TheMessage->cm_fields[i]) {
1148 mptr = TheMessage->cm_fields[i];
1151 strcpy(luser, mptr);
1152 strcpy(suser, mptr);
1155 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1156 into thinking that mail messages are newsgroup messages instead. When we
1157 add NNTP support back into Citadel we'll have to add code to only output
1158 this field when appropriate.
1159 else if (i == 'P') {
1160 cprintf("Path: %s%s", mptr, nl);
1164 cprintf("Subject: %s%s", mptr, nl);
1166 safestrncpy(mid, mptr, sizeof mid);
1168 safestrncpy(lnode, mptr, sizeof lnode);
1170 safestrncpy(fuser, mptr, sizeof fuser);
1172 cprintf("X-Citadel-Room: %s%s",
1175 safestrncpy(snode, mptr, sizeof snode);
1177 cprintf("To: %s%s", mptr, nl);
1178 else if (i == 'T') {
1179 datestring(datestamp, sizeof datestamp, atol(mptr),
1180 DATESTRING_RFC822 );
1181 cprintf("Date: %s%s", datestamp, nl);
1187 for (i=0; i<strlen(suser); ++i) {
1188 suser[i] = tolower(suser[i]);
1189 if (!isalnum(suser[i])) suser[i]='_';
1192 if (mode == MT_RFC822) {
1193 if (!strcasecmp(snode, NODENAME)) {
1194 strcpy(snode, FQDN);
1197 /* Construct a fun message id */
1198 cprintf("Message-ID: <%s", mid);
1199 if (strchr(mid, '@')==NULL) {
1200 cprintf("@%s", snode);
1204 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1206 if (strlen(fuser) > 0) {
1207 cprintf("From: %s (%s)%s", fuser, luser, nl);
1210 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1213 cprintf("Organization: %s%s", lnode, nl);
1216 /* end header processing loop ... at this point, we're in the text */
1218 mptr = TheMessage->cm_fields['M'];
1220 /* Tell the client about the MIME parts in this message */
1221 if (TheMessage->cm_format_type == FMT_RFC822) {
1222 if (mode == MT_CITADEL) {
1223 mime_parser(mptr, NULL,
1224 *list_this_part, NULL, NULL,
1227 else if (mode == MT_MIME) { /* list parts only */
1228 mime_parser(mptr, NULL,
1229 *list_this_part, NULL, NULL,
1231 if (do_proto) cprintf("000\n");
1234 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1235 /* FIXME ... we have to put some code in here to avoid
1236 * printing duplicate header information when both
1237 * Citadel and RFC822 headers exist. Preference should
1238 * probably be given to the RFC822 headers.
1240 while (ch=*(mptr++), ch!=0) {
1242 else if (ch==10) cprintf("%s", nl);
1243 else cprintf("%c", ch);
1245 if (do_proto) cprintf("000\n");
1251 if (do_proto) cprintf("000\n");
1255 /* signify start of msg text */
1256 if (mode == MT_CITADEL)
1257 if (do_proto) cprintf("text\n");
1258 if (mode == MT_RFC822) {
1259 if (TheMessage->cm_fields['U'] == NULL) {
1260 cprintf("Subject: (no subject)%s", nl);
1265 /* If the format type on disk is 1 (fixed-format), then we want
1266 * everything to be output completely literally ... regardless of
1267 * what message transfer format is in use.
1269 if (TheMessage->cm_format_type == FMT_FIXED) {
1271 while (ch = *mptr++, ch > 0) {
1274 if ((ch == 10) || (strlen(buf) > 250)) {
1275 cprintf("%s%s", buf, nl);
1278 buf[strlen(buf) + 1] = 0;
1279 buf[strlen(buf)] = ch;
1282 if (strlen(buf) > 0)
1283 cprintf("%s%s", buf, nl);
1286 /* If the message on disk is format 0 (Citadel vari-format), we
1287 * output using the formatter at 80 columns. This is the final output
1288 * form if the transfer format is RFC822, but if the transfer format
1289 * is Citadel proprietary, it'll still work, because the indentation
1290 * for new paragraphs is correct and the client will reformat the
1291 * message to the reader's screen width.
1293 if (TheMessage->cm_format_type == FMT_CITADEL) {
1294 memfmout(80, mptr, 0, nl);
1297 /* If the message on disk is format 4 (MIME), we've gotta hand it
1298 * off to the MIME parser. The client has already been told that
1299 * this message is format 1 (fixed format), so the callback function
1300 * we use will display those parts as-is.
1302 if (TheMessage->cm_format_type == FMT_RFC822) {
1303 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1304 memset(ma, 0, sizeof(struct ma_info));
1305 mime_parser(mptr, NULL,
1306 *fixed_output, *fixed_output_pre, *fixed_output_post,
1310 /* now we're done */
1311 if (do_proto) cprintf("000\n");
1318 * display a message (mode 0 - Citadel proprietary)
1320 void cmd_msg0(char *cmdbuf)
1323 int headers_only = 0;
1325 msgid = extract_long(cmdbuf, 0);
1326 headers_only = extract_int(cmdbuf, 1);
1328 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1334 * display a message (mode 2 - RFC822)
1336 void cmd_msg2(char *cmdbuf)
1339 int headers_only = 0;
1341 msgid = extract_long(cmdbuf, 0);
1342 headers_only = extract_int(cmdbuf, 1);
1344 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1350 * display a message (mode 3 - IGnet raw format - internal programs only)
1352 void cmd_msg3(char *cmdbuf)
1355 struct CtdlMessage *msg;
1358 if (CC->internal_pgm == 0) {
1359 cprintf("%d This command is for internal programs only.\n",
1364 msgnum = extract_long(cmdbuf, 0);
1365 msg = CtdlFetchMessage(msgnum);
1367 cprintf("%d Message %ld not found.\n",
1372 serialize_message(&smr, msg);
1373 CtdlFreeMessage(msg);
1376 cprintf("%d Unable to serialize message\n",
1377 ERROR+INTERNAL_ERROR);
1381 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1382 client_write(smr.ser, smr.len);
1389 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1391 void cmd_msg4(char *cmdbuf)
1395 msgid = extract_long(cmdbuf, 0);
1396 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1400 * Open a component of a MIME message as a download file
1402 void cmd_opna(char *cmdbuf)
1406 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1408 msgid = extract_long(cmdbuf, 0);
1409 extract(desired_section, cmdbuf, 1);
1411 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1416 * Save a message pointer into a specified room
1417 * (Returns 0 for success, nonzero for failure)
1418 * roomname may be NULL to use the current room
1420 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1422 char hold_rm[ROOMNAMELEN];
1423 struct cdbdata *cdbfr;
1426 long highest_msg = 0L;
1427 struct CtdlMessage *msg = NULL;
1429 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1430 roomname, msgid, flags);
1432 strcpy(hold_rm, CC->quickroom.QRname);
1434 /* We may need to check to see if this message is real */
1435 if ( (flags & SM_VERIFY_GOODNESS)
1436 || (flags & SM_DO_REPL_CHECK)
1438 msg = CtdlFetchMessage(msgid);
1439 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1442 /* Perform replication checks if necessary */
1443 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1445 if (getroom(&CC->quickroom,
1446 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1448 lprintf(9, "No such room <%s>\n", roomname);
1449 if (msg != NULL) CtdlFreeMessage(msg);
1450 return(ERROR + ROOM_NOT_FOUND);
1453 if (ReplicationChecks(msg) != 0) {
1454 getroom(&CC->quickroom, hold_rm);
1455 if (msg != NULL) CtdlFreeMessage(msg);
1456 lprintf(9, "Did replication, and newer exists\n");
1461 /* Now the regular stuff */
1462 if (lgetroom(&CC->quickroom,
1463 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1465 lprintf(9, "No such room <%s>\n", roomname);
1466 if (msg != NULL) CtdlFreeMessage(msg);
1467 return(ERROR + ROOM_NOT_FOUND);
1470 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1471 if (cdbfr == NULL) {
1475 msglist = mallok(cdbfr->len);
1476 if (msglist == NULL)
1477 lprintf(3, "ERROR malloc msglist!\n");
1478 num_msgs = cdbfr->len / sizeof(long);
1479 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1484 /* Make sure the message doesn't already exist in this room. It
1485 * is absolutely taboo to have more than one reference to the same
1486 * message in a room.
1488 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1489 if (msglist[i] == msgid) {
1490 lputroom(&CC->quickroom); /* unlock the room */
1491 getroom(&CC->quickroom, hold_rm);
1492 if (msg != NULL) CtdlFreeMessage(msg);
1493 return(ERROR + ALREADY_EXISTS);
1497 /* Now add the new message */
1499 msglist = reallok(msglist,
1500 (num_msgs * sizeof(long)));
1502 if (msglist == NULL) {
1503 lprintf(3, "ERROR: can't realloc message list!\n");
1505 msglist[num_msgs - 1] = msgid;
1507 /* Sort the message list, so all the msgid's are in order */
1508 num_msgs = sort_msglist(msglist, num_msgs);
1510 /* Determine the highest message number */
1511 highest_msg = msglist[num_msgs - 1];
1513 /* Write it back to disk. */
1514 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1515 msglist, num_msgs * sizeof(long));
1517 /* Free up the memory we used. */
1520 /* Update the highest-message pointer and unlock the room. */
1521 CC->quickroom.QRhighest = highest_msg;
1522 lputroom(&CC->quickroom);
1523 getroom(&CC->quickroom, hold_rm);
1525 /* Bump the reference count for this message. */
1526 if ((flags & SM_DONT_BUMP_REF)==0) {
1527 AdjRefCount(msgid, +1);
1530 /* Return success. */
1531 if (msg != NULL) CtdlFreeMessage(msg);
1538 * Message base operation to send a message to the master file
1539 * (returns new message number)
1541 * This is the back end for CtdlSubmitMsg() and should not be directly
1542 * called by server-side modules.
1545 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1546 FILE *save_a_copy) /* save a copy to disk? */
1553 /* Get a new message number */
1554 newmsgid = get_new_message_number();
1555 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1557 /* Generate an ID if we don't have one already */
1558 if (msg->cm_fields['I']==NULL) {
1559 msg->cm_fields['I'] = strdoop(msgidbuf);
1562 serialize_message(&smr, msg);
1565 cprintf("%d Unable to serialize message\n",
1566 ERROR+INTERNAL_ERROR);
1570 /* Write our little bundle of joy into the message base */
1571 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1572 smr.ser, smr.len) < 0) {
1573 lprintf(2, "Can't store message\n");
1579 /* If the caller specified that a copy should be saved to a particular
1580 * file handle, do that now too.
1582 if (save_a_copy != NULL) {
1583 fwrite(smr.ser, smr.len, 1, save_a_copy);
1586 /* Free the memory we used for the serialized message */
1589 /* Return the *local* message ID to the caller
1590 * (even if we're storing an incoming network message)
1598 * Serialize a struct CtdlMessage into the format used on disk and network.
1600 * This function loads up a "struct ser_ret" (defined in server.h) which
1601 * contains the length of the serialized message and a pointer to the
1602 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1604 void serialize_message(struct ser_ret *ret, /* return values */
1605 struct CtdlMessage *msg) /* unserialized msg */
1609 static char *forder = FORDER;
1611 if (is_valid_message(msg) == 0) return; /* self check */
1614 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1615 ret->len = ret->len +
1616 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1618 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1619 ret->ser = mallok(ret->len);
1620 if (ret->ser == NULL) {
1626 ret->ser[1] = msg->cm_anon_type;
1627 ret->ser[2] = msg->cm_format_type;
1630 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1631 ret->ser[wlen++] = (char)forder[i];
1632 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1633 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1635 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1636 (long)ret->len, (long)wlen);
1644 * Back end for the ReplicationChecks() function
1646 void check_repl(long msgnum, void *userdata) {
1647 struct CtdlMessage *msg;
1648 time_t timestamp = (-1L);
1650 lprintf(9, "check_repl() found message %ld\n", msgnum);
1651 msg = CtdlFetchMessage(msgnum);
1652 if (msg == NULL) return;
1653 if (msg->cm_fields['T'] != NULL) {
1654 timestamp = atol(msg->cm_fields['T']);
1656 CtdlFreeMessage(msg);
1658 if (timestamp > msg_repl->highest) {
1659 msg_repl->highest = timestamp; /* newer! */
1660 lprintf(9, "newer!\n");
1663 lprintf(9, "older!\n");
1665 /* Existing isn't newer? Then delete the old one(s). */
1666 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1671 * Check to see if any messages already exist which carry the same Extended ID
1675 * -> With older timestamps: delete them and return 0. Message will be saved.
1676 * -> With newer timestamps: return 1. Message save will be aborted.
1678 int ReplicationChecks(struct CtdlMessage *msg) {
1679 struct CtdlMessage *template;
1682 lprintf(9, "ReplicationChecks() started\n");
1683 /* No extended id? Don't do anything. */
1684 if (msg->cm_fields['E'] == NULL) return 0;
1685 if (strlen(msg->cm_fields['E']) == 0) return 0;
1686 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1688 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1689 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1690 msg_repl->highest = atol(msg->cm_fields['T']);
1692 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1693 memset(template, 0, sizeof(struct CtdlMessage));
1694 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1696 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1698 /* If a newer message exists with the same Extended ID, abort
1701 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1705 CtdlFreeMessage(template);
1706 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1714 * Save a message to disk and submit it into the delivery system.
1716 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1717 struct recptypes *recps, /* recipients (if mail) */
1718 char *force /* force a particular room? */
1721 char hold_rm[ROOMNAMELEN];
1722 char actual_rm[ROOMNAMELEN];
1723 char force_room[ROOMNAMELEN];
1724 char content_type[SIZ]; /* We have to learn this */
1725 char recipient[SIZ];
1728 struct usersupp userbuf;
1730 struct MetaData smi;
1731 FILE *network_fp = NULL;
1732 static int seqnum = 1;
1733 struct CtdlMessage *imsg = NULL;
1736 char *hold_R, *hold_D;
1738 lprintf(9, "CtdlSubmitMsg() called\n");
1739 if (is_valid_message(msg) == 0) return(-1); /* self check */
1741 /* If this message has no timestamp, we take the liberty of
1742 * giving it one, right now.
1744 if (msg->cm_fields['T'] == NULL) {
1745 lprintf(9, "Generating timestamp\n");
1746 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1747 msg->cm_fields['T'] = strdoop(aaa);
1750 /* If this message has no path, we generate one.
1752 if (msg->cm_fields['P'] == NULL) {
1753 lprintf(9, "Generating path\n");
1754 if (msg->cm_fields['A'] != NULL) {
1755 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1756 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1757 if (isspace(msg->cm_fields['P'][a])) {
1758 msg->cm_fields['P'][a] = ' ';
1763 msg->cm_fields['P'] = strdoop("unknown");
1767 strcpy(force_room, force);
1769 /* Learn about what's inside, because it's what's inside that counts */
1770 lprintf(9, "Learning what's inside\n");
1771 if (msg->cm_fields['M'] == NULL) {
1772 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1775 switch (msg->cm_format_type) {
1777 strcpy(content_type, "text/x-citadel-variformat");
1780 strcpy(content_type, "text/plain");
1783 strcpy(content_type, "text/plain");
1784 /* advance past header fields */
1785 mptr = msg->cm_fields['M'];
1788 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1789 safestrncpy(content_type, mptr,
1790 sizeof(content_type));
1791 strcpy(content_type, &content_type[14]);
1792 for (a = 0; a < strlen(content_type); ++a)
1793 if ((content_type[a] == ';')
1794 || (content_type[a] == ' ')
1795 || (content_type[a] == 13)
1796 || (content_type[a] == 10))
1797 content_type[a] = 0;
1804 /* Goto the correct room */
1805 lprintf(9, "Switching rooms\n");
1806 strcpy(hold_rm, CC->quickroom.QRname);
1807 strcpy(actual_rm, CC->quickroom.QRname);
1808 if (recps != NULL) {
1809 strcpy(actual_rm, SENTITEMS);
1812 /* If the user is a twit, move to the twit room for posting */
1813 lprintf(9, "Handling twit stuff\n");
1815 if (CC->usersupp.axlevel == 2) {
1816 strcpy(hold_rm, actual_rm);
1817 strcpy(actual_rm, config.c_twitroom);
1821 /* ...or if this message is destined for Aide> then go there. */
1822 if (strlen(force_room) > 0) {
1823 strcpy(actual_rm, force_room);
1826 lprintf(9, "Possibly relocating\n");
1827 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1828 getroom(&CC->quickroom, actual_rm);
1832 * If this message has no O (room) field, generate one.
1834 if (msg->cm_fields['O'] == NULL) {
1835 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1838 /* Perform "before save" hooks (aborting if any return nonzero) */
1839 lprintf(9, "Performing before-save hooks\n");
1840 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1842 /* If this message has an Extended ID, perform replication checks */
1843 lprintf(9, "Performing replication checks\n");
1844 if (ReplicationChecks(msg) > 0) return(-1);
1846 /* Save it to disk */
1847 lprintf(9, "Saving to disk\n");
1848 newmsgid = send_message(msg, NULL);
1849 if (newmsgid <= 0L) return(-1);
1851 /* Write a supplemental message info record. This doesn't have to
1852 * be a critical section because nobody else knows about this message
1855 lprintf(9, "Creating MetaData record\n");
1856 memset(&smi, 0, sizeof(struct MetaData));
1857 smi.meta_msgnum = newmsgid;
1858 smi.meta_refcount = 0;
1859 safestrncpy(smi.meta_content_type, content_type, 64);
1862 /* Now figure out where to store the pointers */
1863 lprintf(9, "Storing pointers\n");
1865 /* If this is being done by the networker delivering a private
1866 * message, we want to BYPASS saving the sender's copy (because there
1867 * is no local sender; it would otherwise go to the Trashcan).
1869 if ((!CC->internal_pgm) || (recps == NULL)) {
1870 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1871 lprintf(3, "ERROR saving message pointer!\n");
1872 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1876 /* For internet mail, drop a copy in the outbound queue room */
1878 if (recps->num_internet > 0) {
1879 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1882 /* If other rooms are specified, drop them there too. */
1884 if (recps->num_room > 0)
1885 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1886 extract(recipient, recps->recp_room, i);
1887 lprintf(9, "Delivering to local room <%s>\n", recipient);
1888 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1891 /* Bump this user's messages posted counter. */
1892 lprintf(9, "Updating user\n");
1893 lgetuser(&CC->usersupp, CC->curr_user);
1894 CC->usersupp.posted = CC->usersupp.posted + 1;
1895 lputuser(&CC->usersupp);
1897 /* If this is private, local mail, make a copy in the
1898 * recipient's mailbox and bump the reference count.
1901 if (recps->num_local > 0)
1902 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1903 extract(recipient, recps->recp_local, i);
1904 lprintf(9, "Delivering private local mail to <%s>\n",
1906 if (getuser(&userbuf, recipient) == 0) {
1907 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
1908 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1911 lprintf(9, "No user <%s>\n", recipient);
1912 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1916 /* Perform "after save" hooks */
1917 lprintf(9, "Performing after-save hooks\n");
1918 PerformMessageHooks(msg, EVT_AFTERSAVE);
1920 /* For IGnet mail, we have to save a new copy into the spooler for
1921 * each recipient, with the R and D fields set to the recipient and
1922 * destination-node. This has two ugly side effects: all other
1923 * recipients end up being unlisted in this recipient's copy of the
1924 * message, and it has to deliver multiple messages to the same
1925 * node. We'll revisit this again in a year or so when everyone has
1926 * a network spool receiver that can handle the new style messages.
1929 if (recps->num_ignet > 0)
1930 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1931 extract(recipient, recps->recp_ignet, i);
1933 hold_R = msg->cm_fields['R'];
1934 hold_D = msg->cm_fields['D'];
1935 msg->cm_fields['R'] = mallok(SIZ);
1936 msg->cm_fields['D'] = mallok(SIZ);
1937 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1938 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1940 serialize_message(&smr, msg);
1942 snprintf(aaa, sizeof aaa,
1943 "./network/spoolin/netmail.%04lx.%04x.%04x",
1944 (long) getpid(), CC->cs_pid, ++seqnum);
1945 network_fp = fopen(aaa, "wb+");
1946 if (network_fp != NULL) {
1947 fwrite(smr.ser, smr.len, 1, network_fp);
1953 phree(msg->cm_fields['R']);
1954 phree(msg->cm_fields['D']);
1955 msg->cm_fields['R'] = hold_R;
1956 msg->cm_fields['D'] = hold_D;
1959 /* Go back to the room we started from */
1960 lprintf(9, "Returning to original room\n");
1961 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1962 getroom(&CC->quickroom, hold_rm);
1964 /* For internet mail, generate delivery instructions.
1965 * Yes, this is recursive. Deal with it. Infinite recursion does
1966 * not happen because the delivery instructions message does not
1967 * contain a recipient.
1970 if (recps->num_internet > 0) {
1971 lprintf(9, "Generating delivery instructions\n");
1972 instr = mallok(SIZ * 2);
1973 snprintf(instr, SIZ * 2,
1974 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1976 SPOOLMIME, newmsgid, (long)time(NULL),
1977 msg->cm_fields['A'], msg->cm_fields['N']
1980 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
1981 size_t tmp = strlen(instr);
1982 extract(recipient, recps->recp_internet, i);
1983 snprintf(&instr[tmp], SIZ * 2 - tmp,
1984 "remote|%s|0||\n", recipient);
1987 imsg = mallok(sizeof(struct CtdlMessage));
1988 memset(imsg, 0, sizeof(struct CtdlMessage));
1989 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1990 imsg->cm_anon_type = MES_NORMAL;
1991 imsg->cm_format_type = FMT_RFC822;
1992 imsg->cm_fields['A'] = strdoop("Citadel");
1993 imsg->cm_fields['M'] = instr;
1994 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
1995 CtdlFreeMessage(imsg);
2004 * Convenience function for generating small administrative messages.
2006 void quickie_message(char *from, char *to, char *room, char *text)
2008 struct CtdlMessage *msg;
2010 msg = mallok(sizeof(struct CtdlMessage));
2011 memset(msg, 0, sizeof(struct CtdlMessage));
2012 msg->cm_magic = CTDLMESSAGE_MAGIC;
2013 msg->cm_anon_type = MES_NORMAL;
2014 msg->cm_format_type = 0;
2015 msg->cm_fields['A'] = strdoop(from);
2016 msg->cm_fields['O'] = strdoop(room);
2017 msg->cm_fields['N'] = strdoop(NODENAME);
2019 msg->cm_fields['R'] = strdoop(to);
2020 msg->cm_fields['M'] = strdoop(text);
2022 CtdlSubmitMsg(msg, NULL, room);
2023 CtdlFreeMessage(msg);
2024 syslog(LOG_NOTICE, text);
2030 * Back end function used by make_message() and similar functions
2032 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2033 size_t maxlen, /* maximum message length */
2034 char *exist /* if non-null, append to it;
2035 exist is ALWAYS freed */
2039 size_t message_len = 0;
2040 size_t buffer_len = 0;
2044 if (exist == NULL) {
2051 message_len = strlen(exist);
2052 buffer_len = message_len + 4096;
2053 m = reallok(exist, buffer_len);
2060 /* flush the input if we have nowhere to store it */
2062 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2066 /* read in the lines of message text one by one */
2067 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2069 /* strip trailing newline type stuff */
2070 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2071 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2073 linelen = strlen(buf);
2075 /* augment the buffer if we have to */
2076 if ((message_len + linelen + 2) > buffer_len) {
2077 lprintf(9, "realloking\n");
2078 ptr = reallok(m, (buffer_len * 2) );
2079 if (ptr == NULL) { /* flush if can't allocate */
2080 while ( (client_gets(buf)>0) &&
2081 strcmp(buf, terminator)) ;;
2084 buffer_len = (buffer_len * 2);
2086 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2090 /* Add the new line to the buffer. NOTE: this loop must avoid
2091 * using functions like strcat() and strlen() because they
2092 * traverse the entire buffer upon every call, and doing that
2093 * for a multi-megabyte message slows it down beyond usability.
2095 strcpy(&m[message_len], buf);
2096 m[message_len + linelen] = '\n';
2097 m[message_len + linelen + 1] = 0;
2098 message_len = message_len + linelen + 1;
2100 /* if we've hit the max msg length, flush the rest */
2101 if (message_len >= maxlen) {
2102 while ( (client_gets(buf)>0)
2103 && strcmp(buf, terminator)) ;;
2114 * Build a binary message to be saved on disk.
2117 static struct CtdlMessage *make_message(
2118 struct usersupp *author, /* author's usersupp structure */
2119 char *recipient, /* NULL if it's not mail */
2120 char *room, /* room where it's going */
2121 int type, /* see MES_ types in header file */
2122 int format_type, /* variformat, plain text, MIME... */
2123 char *fake_name, /* who we're masquerading as */
2124 char *subject /* Subject (optional) */
2126 char dest_node[SIZ];
2128 struct CtdlMessage *msg;
2130 msg = mallok(sizeof(struct CtdlMessage));
2131 memset(msg, 0, sizeof(struct CtdlMessage));
2132 msg->cm_magic = CTDLMESSAGE_MAGIC;
2133 msg->cm_anon_type = type;
2134 msg->cm_format_type = format_type;
2136 /* Don't confuse the poor folks if it's not routed mail. */
2137 strcpy(dest_node, "");
2141 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2142 msg->cm_fields['P'] = strdoop(buf);
2144 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2145 msg->cm_fields['T'] = strdoop(buf);
2147 if (fake_name[0]) /* author */
2148 msg->cm_fields['A'] = strdoop(fake_name);
2150 msg->cm_fields['A'] = strdoop(author->fullname);
2152 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2153 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2156 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2159 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2160 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2162 if (recipient[0] != 0) {
2163 msg->cm_fields['R'] = strdoop(recipient);
2165 if (dest_node[0] != 0) {
2166 msg->cm_fields['D'] = strdoop(dest_node);
2169 if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
2170 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2173 if (subject != NULL) {
2175 if (strlen(subject) > 0) {
2176 msg->cm_fields['U'] = strdoop(subject);
2180 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2181 config.c_maxmsglen, NULL);
2188 * Check to see whether we have permission to post a message in the current
2189 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2190 * returns 0 on success.
2192 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2194 if (!(CC->logged_in)) {
2195 snprintf(errmsgbuf, n, "Not logged in.");
2196 return (ERROR + NOT_LOGGED_IN);
2199 if ((CC->usersupp.axlevel < 2)
2200 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2201 snprintf(errmsgbuf, n, "Need to be validated to enter "
2202 "(except in %s> to sysop)", MAILROOM);
2203 return (ERROR + HIGHER_ACCESS_REQUIRED);
2206 if ((CC->usersupp.axlevel < 4)
2207 && (CC->quickroom.QRflags & QR_NETWORK)) {
2208 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2209 return (ERROR + HIGHER_ACCESS_REQUIRED);
2212 if ((CC->usersupp.axlevel < 6)
2213 && (CC->quickroom.QRflags & QR_READONLY)) {
2214 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2215 return (ERROR + HIGHER_ACCESS_REQUIRED);
2218 strcpy(errmsgbuf, "Ok");
2224 * Validate recipients, count delivery types and errors, and handle aliasing
2225 * FIXME check for dupes!!!!!
2227 struct recptypes *validate_recipients(char *recipients) {
2228 struct recptypes *ret;
2229 char this_recp[SIZ];
2230 char this_recp_cooked[SIZ];
2236 struct usersupp tempUS;
2237 struct quickroom tempQR;
2240 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2241 if (ret == NULL) return(NULL);
2242 memset(ret, 0, sizeof(struct recptypes));
2245 ret->num_internet = 0;
2250 if (recipients == NULL) {
2253 else if (strlen(recipients) == 0) {
2257 /* Change all valid separator characters to commas */
2258 for (i=0; i<strlen(recipients); ++i) {
2259 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2260 recipients[i] = ',';
2265 num_recps = num_tokens(recipients, ',');
2268 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2269 extract_token(this_recp, recipients, i, ',');
2271 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2272 mailtype = alias(this_recp);
2273 mailtype = alias(this_recp);
2274 mailtype = alias(this_recp);
2275 for (j=0; j<=strlen(this_recp); ++j) {
2276 if (this_recp[j]=='_') {
2277 this_recp_cooked[j] = ' ';
2280 this_recp_cooked[j] = this_recp[j];
2286 if (!strcasecmp(this_recp, "sysop")) {
2288 strcpy(this_recp, AIDEROOM);
2289 if (strlen(ret->recp_room) > 0) {
2290 strcat(ret->recp_room, "|");
2292 strcat(ret->recp_room, this_recp);
2294 else if (getuser(&tempUS, this_recp) == 0) {
2296 strcpy(this_recp, tempUS.fullname);
2297 if (strlen(ret->recp_local) > 0) {
2298 strcat(ret->recp_local, "|");
2300 strcat(ret->recp_local, this_recp);
2302 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2304 strcpy(this_recp, tempUS.fullname);
2305 if (strlen(ret->recp_local) > 0) {
2306 strcat(ret->recp_local, "|");
2308 strcat(ret->recp_local, this_recp);
2310 else if ( (!strncasecmp(this_recp, "room_", 5))
2311 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2313 if (strlen(ret->recp_room) > 0) {
2314 strcat(ret->recp_room, "|");
2316 strcat(ret->recp_room, &this_recp_cooked[5]);
2324 ++ret->num_internet;
2325 if (strlen(ret->recp_internet) > 0) {
2326 strcat(ret->recp_internet, "|");
2328 strcat(ret->recp_internet, this_recp);
2332 if (strlen(ret->recp_ignet) > 0) {
2333 strcat(ret->recp_ignet, "|");
2335 strcat(ret->recp_ignet, this_recp);
2343 if (strlen(ret->errormsg) == 0) {
2344 snprintf(append, sizeof append,
2345 "Invalid recipient: %s",
2349 snprintf(append, sizeof append,
2352 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2353 strcat(ret->errormsg, append);
2357 if (strlen(ret->display_recp) == 0) {
2358 strcpy(append, this_recp);
2361 snprintf(append, sizeof append, ", %s",
2364 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2365 strcat(ret->display_recp, append);
2370 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2371 ret->num_room + ret->num_error) == 0) {
2373 strcpy(ret->errormsg, "No recipients specified.");
2376 lprintf(9, "validate_recipients()\n");
2377 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2378 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2379 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2380 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2381 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2389 * message entry - mode 0 (normal)
2391 void cmd_ent0(char *entargs)
2395 char masquerade_as[SIZ];
2397 int format_type = 0;
2398 char newusername[SIZ];
2399 struct CtdlMessage *msg;
2403 struct recptypes *valid = NULL;
2406 post = extract_int(entargs, 0);
2407 extract(recp, entargs, 1);
2408 anon_flag = extract_int(entargs, 2);
2409 format_type = extract_int(entargs, 3);
2410 extract(subject, entargs, 4);
2412 /* first check to make sure the request is valid. */
2414 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2416 cprintf("%d %s\n", err, errmsg);
2420 /* Check some other permission type things. */
2423 if (CC->usersupp.axlevel < 6) {
2424 cprintf("%d You don't have permission to masquerade.\n",
2425 ERROR + HIGHER_ACCESS_REQUIRED);
2428 extract(newusername, entargs, 4);
2429 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2430 safestrncpy(CC->fake_postname, newusername,
2431 sizeof(CC->fake_postname) );
2432 cprintf("%d ok\n", CIT_OK);
2435 CC->cs_flags |= CS_POSTING;
2437 /* In the Mail> room we have to behave a little differently --
2438 * make sure the user has specified at least one recipient. Then
2439 * validate the recipient(s).
2441 if ( (CC->quickroom.QRflags & QR_MAILBOX)
2442 && (!strcasecmp(&CC->quickroom.QRname[11], MAILROOM)) ) {
2444 if (CC->usersupp.axlevel < 2) {
2445 strcpy(recp, "sysop");
2448 valid = validate_recipients(recp);
2449 if (valid->num_error > 0) {
2451 ERROR + NO_SUCH_USER, valid->errormsg);
2456 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2457 && (CC->usersupp.axlevel < 4) ) {
2458 cprintf("%d Higher access required for network mail.\n",
2459 ERROR + HIGHER_ACCESS_REQUIRED);
2464 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2465 && ((CC->usersupp.flags & US_INTERNET) == 0)
2466 && (!CC->internal_pgm)) {
2467 cprintf("%d You don't have access to Internet mail.\n",
2468 ERROR + HIGHER_ACCESS_REQUIRED);
2475 /* Is this a room which has anonymous-only or anonymous-option? */
2476 anonymous = MES_NORMAL;
2477 if (CC->quickroom.QRflags & QR_ANONONLY) {
2478 anonymous = MES_ANONONLY;
2480 if (CC->quickroom.QRflags & QR_ANONOPT) {
2481 if (anon_flag == 1) { /* only if the user requested it */
2482 anonymous = MES_ANONOPT;
2486 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2490 /* If we're only checking the validity of the request, return
2491 * success without creating the message.
2494 cprintf("%d %s\n", CIT_OK,
2495 ((valid != NULL) ? valid->display_recp : "") );
2500 /* Handle author masquerading */
2501 if (CC->fake_postname[0]) {
2502 strcpy(masquerade_as, CC->fake_postname);
2504 else if (CC->fake_username[0]) {
2505 strcpy(masquerade_as, CC->fake_username);
2508 strcpy(masquerade_as, "");
2511 /* Read in the message from the client. */
2512 cprintf("%d send message\n", SEND_LISTING);
2513 msg = make_message(&CC->usersupp, recp,
2514 CC->quickroom.QRname, anonymous, format_type,
2515 masquerade_as, subject);
2518 CtdlSubmitMsg(msg, valid, "");
2519 CtdlFreeMessage(msg);
2521 CC->fake_postname[0] = '\0';
2529 * API function to delete messages which match a set of criteria
2530 * (returns the actual number of messages deleted)
2532 int CtdlDeleteMessages(char *room_name, /* which room */
2533 long dmsgnum, /* or "0" for any */
2534 char *content_type /* or "" for any */
2538 struct quickroom qrbuf;
2539 struct cdbdata *cdbfr;
2540 long *msglist = NULL;
2541 long *dellist = NULL;
2544 int num_deleted = 0;
2546 struct MetaData smi;
2548 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2549 room_name, dmsgnum, content_type);
2551 /* get room record, obtaining a lock... */
2552 if (lgetroom(&qrbuf, room_name) != 0) {
2553 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2555 return (0); /* room not found */
2557 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2559 if (cdbfr != NULL) {
2560 msglist = mallok(cdbfr->len);
2561 dellist = mallok(cdbfr->len);
2562 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2563 num_msgs = cdbfr->len / sizeof(long);
2567 for (i = 0; i < num_msgs; ++i) {
2570 /* Set/clear a bit for each criterion */
2572 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2573 delete_this |= 0x01;
2575 if (strlen(content_type) == 0) {
2576 delete_this |= 0x02;
2578 GetMetaData(&smi, msglist[i]);
2579 if (!strcasecmp(smi.meta_content_type,
2581 delete_this |= 0x02;
2585 /* Delete message only if all bits are set */
2586 if (delete_this == 0x03) {
2587 dellist[num_deleted++] = msglist[i];
2592 num_msgs = sort_msglist(msglist, num_msgs);
2593 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2594 msglist, (num_msgs * sizeof(long)));
2596 qrbuf.QRhighest = msglist[num_msgs - 1];
2600 /* Go through the messages we pulled out of the index, and decrement
2601 * their reference counts by 1. If this is the only room the message
2602 * was in, the reference count will reach zero and the message will
2603 * automatically be deleted from the database. We do this in a
2604 * separate pass because there might be plug-in hooks getting called,
2605 * and we don't want that happening during an S_QUICKROOM critical
2608 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2609 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2610 AdjRefCount(dellist[i], -1);
2613 /* Now free the memory we used, and go away. */
2614 if (msglist != NULL) phree(msglist);
2615 if (dellist != NULL) phree(dellist);
2616 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2617 return (num_deleted);
2623 * Check whether the current user has permission to delete messages from
2624 * the current room (returns 1 for yes, 0 for no)
2626 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2627 getuser(&CC->usersupp, CC->curr_user);
2628 if ((CC->usersupp.axlevel < 6)
2629 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2630 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2631 && (!(CC->internal_pgm))) {
2640 * Delete message from current room
2642 void cmd_dele(char *delstr)
2647 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2648 cprintf("%d Higher access required.\n",
2649 ERROR + HIGHER_ACCESS_REQUIRED);
2652 delnum = extract_long(delstr, 0);
2654 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2657 cprintf("%d %d message%s deleted.\n", CIT_OK,
2658 num_deleted, ((num_deleted != 1) ? "s" : ""));
2660 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2666 * Back end API function for moves and deletes
2668 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2671 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2672 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2673 if (err != 0) return(err);
2681 * move or copy a message to another room
2683 void cmd_move(char *args)
2687 struct quickroom qtemp;
2691 num = extract_long(args, 0);
2692 extract(targ, args, 1);
2693 targ[ROOMNAMELEN - 1] = 0;
2694 is_copy = extract_int(args, 2);
2696 if (getroom(&qtemp, targ) != 0) {
2697 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2701 getuser(&CC->usersupp, CC->curr_user);
2702 /* Aides can move/copy */
2703 if ((CC->usersupp.axlevel < 6)
2704 /* Roomaides can move/copy */
2705 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2706 /* Permit move/copy to/from personal rooms */
2707 && (!((CC->quickroom.QRflags & QR_MAILBOX)
2708 && (qtemp.QRflags & QR_MAILBOX)))
2709 /* Permit only copy from public to personal room */
2710 && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2711 && (qtemp.QRflags & QR_MAILBOX)))) {
2712 cprintf("%d Higher access required.\n",
2713 ERROR + HIGHER_ACCESS_REQUIRED);
2717 err = CtdlCopyMsgToRoom(num, targ);
2719 cprintf("%d Cannot store message in %s: error %d\n",
2724 /* Now delete the message from the source room,
2725 * if this is a 'move' rather than a 'copy' operation.
2728 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2731 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2737 * GetMetaData() - Get the supplementary record for a message
2739 void GetMetaData(struct MetaData *smibuf, long msgnum)
2742 struct cdbdata *cdbsmi;
2745 memset(smibuf, 0, sizeof(struct MetaData));
2746 smibuf->meta_msgnum = msgnum;
2747 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2749 /* Use the negative of the message number for its supp record index */
2750 TheIndex = (0L - msgnum);
2752 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2753 if (cdbsmi == NULL) {
2754 return; /* record not found; go with defaults */
2756 memcpy(smibuf, cdbsmi->ptr,
2757 ((cdbsmi->len > sizeof(struct MetaData)) ?
2758 sizeof(struct MetaData) : cdbsmi->len));
2765 * PutMetaData() - (re)write supplementary record for a message
2767 void PutMetaData(struct MetaData *smibuf)
2771 /* Use the negative of the message number for the metadata db index */
2772 TheIndex = (0L - smibuf->meta_msgnum);
2774 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2775 smibuf->meta_msgnum, smibuf->meta_refcount);
2777 cdb_store(CDB_MSGMAIN,
2778 &TheIndex, sizeof(long),
2779 smibuf, sizeof(struct MetaData));
2784 * AdjRefCount - change the reference count for a message;
2785 * delete the message if it reaches zero
2787 void AdjRefCount(long msgnum, int incr)
2790 struct MetaData smi;
2793 /* This is a *tight* critical section; please keep it that way, as
2794 * it may get called while nested in other critical sections.
2795 * Complicating this any further will surely cause deadlock!
2797 begin_critical_section(S_SUPPMSGMAIN);
2798 GetMetaData(&smi, msgnum);
2799 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2800 msgnum, smi.meta_refcount);
2801 smi.meta_refcount += incr;
2803 end_critical_section(S_SUPPMSGMAIN);
2804 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2805 msgnum, smi.meta_refcount);
2807 /* If the reference count is now zero, delete the message
2808 * (and its supplementary record as well).
2810 if (smi.meta_refcount == 0) {
2811 lprintf(9, "Deleting message <%ld>\n", msgnum);
2813 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2815 /* We have to delete the metadata record too! */
2816 delnum = (0L - msgnum);
2817 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2822 * Write a generic object to this room
2824 * Note: this could be much more efficient. Right now we use two temporary
2825 * files, and still pull the message into memory as with all others.
2827 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2828 char *content_type, /* MIME type of this object */
2829 char *tempfilename, /* Where to fetch it from */
2830 struct usersupp *is_mailbox, /* Mailbox room? */
2831 int is_binary, /* Is encoding necessary? */
2832 int is_unique, /* Del others of this type? */
2833 unsigned int flags /* Internal save flags */
2838 char filename[PATH_MAX];
2841 struct quickroom qrbuf;
2842 char roomname[ROOMNAMELEN];
2843 struct CtdlMessage *msg;
2846 if (is_mailbox != NULL)
2847 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
2849 safestrncpy(roomname, req_room, sizeof(roomname));
2850 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2852 strcpy(filename, tmpnam(NULL));
2853 fp = fopen(filename, "w");
2857 tempfp = fopen(tempfilename, "r");
2858 if (tempfp == NULL) {
2864 fprintf(fp, "Content-type: %s\n", content_type);
2865 lprintf(9, "Content-type: %s\n", content_type);
2867 if (is_binary == 0) {
2868 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2869 while (ch = getc(tempfp), ch > 0)
2875 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2878 snprintf(cmdbuf, sizeof cmdbuf, "./base64 -e <%s >>%s",
2879 tempfilename, filename);
2883 lprintf(9, "Allocating\n");
2884 msg = mallok(sizeof(struct CtdlMessage));
2885 memset(msg, 0, sizeof(struct CtdlMessage));
2886 msg->cm_magic = CTDLMESSAGE_MAGIC;
2887 msg->cm_anon_type = MES_NORMAL;
2888 msg->cm_format_type = 4;
2889 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2890 msg->cm_fields['O'] = strdoop(req_room);
2891 msg->cm_fields['N'] = strdoop(config.c_nodename);
2892 msg->cm_fields['H'] = strdoop(config.c_humannode);
2893 msg->cm_flags = flags;
2895 lprintf(9, "Loading\n");
2896 fp = fopen(filename, "rb");
2897 fseek(fp, 0L, SEEK_END);
2900 msg->cm_fields['M'] = mallok(len);
2901 fread(msg->cm_fields['M'], len, 1, fp);
2905 /* Create the requested room if we have to. */
2906 if (getroom(&qrbuf, roomname) != 0) {
2907 create_room(roomname,
2908 ( (is_mailbox != NULL) ? 5 : 3 ),
2911 /* If the caller specified this object as unique, delete all
2912 * other objects of this type that are currently in the room.
2915 lprintf(9, "Deleted %d other msgs of this type\n",
2916 CtdlDeleteMessages(roomname, 0L, content_type));
2918 /* Now write the data */
2919 CtdlSubmitMsg(msg, NULL, roomname);
2920 CtdlFreeMessage(msg);
2928 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2929 config_msgnum = msgnum;
2933 char *CtdlGetSysConfig(char *sysconfname) {
2934 char hold_rm[ROOMNAMELEN];
2937 struct CtdlMessage *msg;
2940 strcpy(hold_rm, CC->quickroom.QRname);
2941 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2942 getroom(&CC->quickroom, hold_rm);
2947 /* We want the last (and probably only) config in this room */
2948 begin_critical_section(S_CONFIG);
2949 config_msgnum = (-1L);
2950 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2951 CtdlGetSysConfigBackend, NULL);
2952 msgnum = config_msgnum;
2953 end_critical_section(S_CONFIG);
2959 msg = CtdlFetchMessage(msgnum);
2961 conf = strdoop(msg->cm_fields['M']);
2962 CtdlFreeMessage(msg);
2969 getroom(&CC->quickroom, hold_rm);
2971 if (conf != NULL) do {
2972 extract_token(buf, conf, 0, '\n');
2973 strcpy(conf, &conf[strlen(buf)+1]);
2974 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2979 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2980 char temp[PATH_MAX];
2983 strcpy(temp, tmpnam(NULL));
2985 fp = fopen(temp, "w");
2986 if (fp == NULL) return;
2987 fprintf(fp, "%s", sysconfdata);
2990 /* this handy API function does all the work for us */
2991 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);