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);
1909 BumpNewMailCounter(userbuf.usernum);
1912 lprintf(9, "No user <%s>\n", recipient);
1913 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1917 /* Perform "after save" hooks */
1918 lprintf(9, "Performing after-save hooks\n");
1919 PerformMessageHooks(msg, EVT_AFTERSAVE);
1921 /* For IGnet mail, we have to save a new copy into the spooler for
1922 * each recipient, with the R and D fields set to the recipient and
1923 * destination-node. This has two ugly side effects: all other
1924 * recipients end up being unlisted in this recipient's copy of the
1925 * message, and it has to deliver multiple messages to the same
1926 * node. We'll revisit this again in a year or so when everyone has
1927 * a network spool receiver that can handle the new style messages.
1930 if (recps->num_ignet > 0)
1931 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1932 extract(recipient, recps->recp_ignet, i);
1934 hold_R = msg->cm_fields['R'];
1935 hold_D = msg->cm_fields['D'];
1936 msg->cm_fields['R'] = mallok(SIZ);
1937 msg->cm_fields['D'] = mallok(SIZ);
1938 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1939 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1941 serialize_message(&smr, msg);
1943 snprintf(aaa, sizeof aaa,
1944 "./network/spoolin/netmail.%04lx.%04x.%04x",
1945 (long) getpid(), CC->cs_pid, ++seqnum);
1946 network_fp = fopen(aaa, "wb+");
1947 if (network_fp != NULL) {
1948 fwrite(smr.ser, smr.len, 1, network_fp);
1954 phree(msg->cm_fields['R']);
1955 phree(msg->cm_fields['D']);
1956 msg->cm_fields['R'] = hold_R;
1957 msg->cm_fields['D'] = hold_D;
1960 /* Go back to the room we started from */
1961 lprintf(9, "Returning to original room\n");
1962 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1963 getroom(&CC->quickroom, hold_rm);
1965 /* For internet mail, generate delivery instructions.
1966 * Yes, this is recursive. Deal with it. Infinite recursion does
1967 * not happen because the delivery instructions message does not
1968 * contain a recipient.
1971 if (recps->num_internet > 0) {
1972 lprintf(9, "Generating delivery instructions\n");
1973 instr = mallok(SIZ * 2);
1974 snprintf(instr, SIZ * 2,
1975 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1977 SPOOLMIME, newmsgid, (long)time(NULL),
1978 msg->cm_fields['A'], msg->cm_fields['N']
1981 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
1982 size_t tmp = strlen(instr);
1983 extract(recipient, recps->recp_internet, i);
1984 snprintf(&instr[tmp], SIZ * 2 - tmp,
1985 "remote|%s|0||\n", recipient);
1988 imsg = mallok(sizeof(struct CtdlMessage));
1989 memset(imsg, 0, sizeof(struct CtdlMessage));
1990 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1991 imsg->cm_anon_type = MES_NORMAL;
1992 imsg->cm_format_type = FMT_RFC822;
1993 imsg->cm_fields['A'] = strdoop("Citadel");
1994 imsg->cm_fields['M'] = instr;
1995 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
1996 CtdlFreeMessage(imsg);
2005 * Convenience function for generating small administrative messages.
2007 void quickie_message(char *from, char *to, char *room, char *text)
2009 struct CtdlMessage *msg;
2011 msg = mallok(sizeof(struct CtdlMessage));
2012 memset(msg, 0, sizeof(struct CtdlMessage));
2013 msg->cm_magic = CTDLMESSAGE_MAGIC;
2014 msg->cm_anon_type = MES_NORMAL;
2015 msg->cm_format_type = 0;
2016 msg->cm_fields['A'] = strdoop(from);
2017 msg->cm_fields['O'] = strdoop(room);
2018 msg->cm_fields['N'] = strdoop(NODENAME);
2020 msg->cm_fields['R'] = strdoop(to);
2021 msg->cm_fields['M'] = strdoop(text);
2023 CtdlSubmitMsg(msg, NULL, room);
2024 CtdlFreeMessage(msg);
2025 syslog(LOG_NOTICE, text);
2031 * Back end function used by make_message() and similar functions
2033 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2034 size_t maxlen, /* maximum message length */
2035 char *exist /* if non-null, append to it;
2036 exist is ALWAYS freed */
2040 size_t message_len = 0;
2041 size_t buffer_len = 0;
2045 if (exist == NULL) {
2052 message_len = strlen(exist);
2053 buffer_len = message_len + 4096;
2054 m = reallok(exist, buffer_len);
2061 /* flush the input if we have nowhere to store it */
2063 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2067 /* read in the lines of message text one by one */
2068 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2070 /* strip trailing newline type stuff */
2071 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2072 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2074 linelen = strlen(buf);
2076 /* augment the buffer if we have to */
2077 if ((message_len + linelen + 2) > buffer_len) {
2078 lprintf(9, "realloking\n");
2079 ptr = reallok(m, (buffer_len * 2) );
2080 if (ptr == NULL) { /* flush if can't allocate */
2081 while ( (client_gets(buf)>0) &&
2082 strcmp(buf, terminator)) ;;
2085 buffer_len = (buffer_len * 2);
2087 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2091 /* Add the new line to the buffer. NOTE: this loop must avoid
2092 * using functions like strcat() and strlen() because they
2093 * traverse the entire buffer upon every call, and doing that
2094 * for a multi-megabyte message slows it down beyond usability.
2096 strcpy(&m[message_len], buf);
2097 m[message_len + linelen] = '\n';
2098 m[message_len + linelen + 1] = 0;
2099 message_len = message_len + linelen + 1;
2101 /* if we've hit the max msg length, flush the rest */
2102 if (message_len >= maxlen) {
2103 while ( (client_gets(buf)>0)
2104 && strcmp(buf, terminator)) ;;
2115 * Build a binary message to be saved on disk.
2118 static struct CtdlMessage *make_message(
2119 struct usersupp *author, /* author's usersupp structure */
2120 char *recipient, /* NULL if it's not mail */
2121 char *room, /* room where it's going */
2122 int type, /* see MES_ types in header file */
2123 int format_type, /* variformat, plain text, MIME... */
2124 char *fake_name, /* who we're masquerading as */
2125 char *subject /* Subject (optional) */
2127 char dest_node[SIZ];
2129 struct CtdlMessage *msg;
2131 msg = mallok(sizeof(struct CtdlMessage));
2132 memset(msg, 0, sizeof(struct CtdlMessage));
2133 msg->cm_magic = CTDLMESSAGE_MAGIC;
2134 msg->cm_anon_type = type;
2135 msg->cm_format_type = format_type;
2137 /* Don't confuse the poor folks if it's not routed mail. */
2138 strcpy(dest_node, "");
2142 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2143 msg->cm_fields['P'] = strdoop(buf);
2145 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2146 msg->cm_fields['T'] = strdoop(buf);
2148 if (fake_name[0]) /* author */
2149 msg->cm_fields['A'] = strdoop(fake_name);
2151 msg->cm_fields['A'] = strdoop(author->fullname);
2153 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2154 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2157 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2160 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2161 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2163 if (recipient[0] != 0) {
2164 msg->cm_fields['R'] = strdoop(recipient);
2166 if (dest_node[0] != 0) {
2167 msg->cm_fields['D'] = strdoop(dest_node);
2170 if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
2171 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2174 if (subject != NULL) {
2176 if (strlen(subject) > 0) {
2177 msg->cm_fields['U'] = strdoop(subject);
2181 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2182 config.c_maxmsglen, NULL);
2189 * Check to see whether we have permission to post a message in the current
2190 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2191 * returns 0 on success.
2193 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2195 if (!(CC->logged_in)) {
2196 snprintf(errmsgbuf, n, "Not logged in.");
2197 return (ERROR + NOT_LOGGED_IN);
2200 if ((CC->usersupp.axlevel < 2)
2201 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2202 snprintf(errmsgbuf, n, "Need to be validated to enter "
2203 "(except in %s> to sysop)", MAILROOM);
2204 return (ERROR + HIGHER_ACCESS_REQUIRED);
2207 if ((CC->usersupp.axlevel < 4)
2208 && (CC->quickroom.QRflags & QR_NETWORK)) {
2209 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2210 return (ERROR + HIGHER_ACCESS_REQUIRED);
2213 if ((CC->usersupp.axlevel < 6)
2214 && (CC->quickroom.QRflags & QR_READONLY)) {
2215 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2216 return (ERROR + HIGHER_ACCESS_REQUIRED);
2219 strcpy(errmsgbuf, "Ok");
2225 * Validate recipients, count delivery types and errors, and handle aliasing
2226 * FIXME check for dupes!!!!!
2228 struct recptypes *validate_recipients(char *recipients) {
2229 struct recptypes *ret;
2230 char this_recp[SIZ];
2231 char this_recp_cooked[SIZ];
2237 struct usersupp tempUS;
2238 struct quickroom tempQR;
2241 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2242 if (ret == NULL) return(NULL);
2243 memset(ret, 0, sizeof(struct recptypes));
2246 ret->num_internet = 0;
2251 if (recipients == NULL) {
2254 else if (strlen(recipients) == 0) {
2258 /* Change all valid separator characters to commas */
2259 for (i=0; i<strlen(recipients); ++i) {
2260 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2261 recipients[i] = ',';
2266 num_recps = num_tokens(recipients, ',');
2269 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2270 extract_token(this_recp, recipients, i, ',');
2272 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2273 mailtype = alias(this_recp);
2274 mailtype = alias(this_recp);
2275 mailtype = alias(this_recp);
2276 for (j=0; j<=strlen(this_recp); ++j) {
2277 if (this_recp[j]=='_') {
2278 this_recp_cooked[j] = ' ';
2281 this_recp_cooked[j] = this_recp[j];
2287 if (!strcasecmp(this_recp, "sysop")) {
2289 strcpy(this_recp, AIDEROOM);
2290 if (strlen(ret->recp_room) > 0) {
2291 strcat(ret->recp_room, "|");
2293 strcat(ret->recp_room, this_recp);
2295 else if (getuser(&tempUS, this_recp) == 0) {
2297 strcpy(this_recp, tempUS.fullname);
2298 if (strlen(ret->recp_local) > 0) {
2299 strcat(ret->recp_local, "|");
2301 strcat(ret->recp_local, this_recp);
2303 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2305 strcpy(this_recp, tempUS.fullname);
2306 if (strlen(ret->recp_local) > 0) {
2307 strcat(ret->recp_local, "|");
2309 strcat(ret->recp_local, this_recp);
2311 else if ( (!strncasecmp(this_recp, "room_", 5))
2312 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2314 if (strlen(ret->recp_room) > 0) {
2315 strcat(ret->recp_room, "|");
2317 strcat(ret->recp_room, &this_recp_cooked[5]);
2325 ++ret->num_internet;
2326 if (strlen(ret->recp_internet) > 0) {
2327 strcat(ret->recp_internet, "|");
2329 strcat(ret->recp_internet, this_recp);
2333 if (strlen(ret->recp_ignet) > 0) {
2334 strcat(ret->recp_ignet, "|");
2336 strcat(ret->recp_ignet, this_recp);
2344 if (strlen(ret->errormsg) == 0) {
2345 snprintf(append, sizeof append,
2346 "Invalid recipient: %s",
2350 snprintf(append, sizeof append,
2353 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2354 strcat(ret->errormsg, append);
2358 if (strlen(ret->display_recp) == 0) {
2359 strcpy(append, this_recp);
2362 snprintf(append, sizeof append, ", %s",
2365 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2366 strcat(ret->display_recp, append);
2371 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2372 ret->num_room + ret->num_error) == 0) {
2374 strcpy(ret->errormsg, "No recipients specified.");
2377 lprintf(9, "validate_recipients()\n");
2378 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2379 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2380 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2381 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2382 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2390 * message entry - mode 0 (normal)
2392 void cmd_ent0(char *entargs)
2396 char masquerade_as[SIZ];
2398 int format_type = 0;
2399 char newusername[SIZ];
2400 struct CtdlMessage *msg;
2404 struct recptypes *valid = NULL;
2407 post = extract_int(entargs, 0);
2408 extract(recp, entargs, 1);
2409 anon_flag = extract_int(entargs, 2);
2410 format_type = extract_int(entargs, 3);
2411 extract(subject, entargs, 4);
2413 /* first check to make sure the request is valid. */
2415 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2417 cprintf("%d %s\n", err, errmsg);
2421 /* Check some other permission type things. */
2424 if (CC->usersupp.axlevel < 6) {
2425 cprintf("%d You don't have permission to masquerade.\n",
2426 ERROR + HIGHER_ACCESS_REQUIRED);
2429 extract(newusername, entargs, 4);
2430 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2431 safestrncpy(CC->fake_postname, newusername,
2432 sizeof(CC->fake_postname) );
2433 cprintf("%d ok\n", CIT_OK);
2436 CC->cs_flags |= CS_POSTING;
2438 /* In the Mail> room we have to behave a little differently --
2439 * make sure the user has specified at least one recipient. Then
2440 * validate the recipient(s).
2442 if ( (CC->quickroom.QRflags & QR_MAILBOX)
2443 && (!strcasecmp(&CC->quickroom.QRname[11], MAILROOM)) ) {
2445 if (CC->usersupp.axlevel < 2) {
2446 strcpy(recp, "sysop");
2449 valid = validate_recipients(recp);
2450 if (valid->num_error > 0) {
2452 ERROR + NO_SUCH_USER, valid->errormsg);
2457 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2458 && (CC->usersupp.axlevel < 4) ) {
2459 cprintf("%d Higher access required for network mail.\n",
2460 ERROR + HIGHER_ACCESS_REQUIRED);
2465 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2466 && ((CC->usersupp.flags & US_INTERNET) == 0)
2467 && (!CC->internal_pgm)) {
2468 cprintf("%d You don't have access to Internet mail.\n",
2469 ERROR + HIGHER_ACCESS_REQUIRED);
2476 /* Is this a room which has anonymous-only or anonymous-option? */
2477 anonymous = MES_NORMAL;
2478 if (CC->quickroom.QRflags & QR_ANONONLY) {
2479 anonymous = MES_ANONONLY;
2481 if (CC->quickroom.QRflags & QR_ANONOPT) {
2482 if (anon_flag == 1) { /* only if the user requested it */
2483 anonymous = MES_ANONOPT;
2487 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2491 /* If we're only checking the validity of the request, return
2492 * success without creating the message.
2495 cprintf("%d %s\n", CIT_OK,
2496 ((valid != NULL) ? valid->display_recp : "") );
2501 /* Handle author masquerading */
2502 if (CC->fake_postname[0]) {
2503 strcpy(masquerade_as, CC->fake_postname);
2505 else if (CC->fake_username[0]) {
2506 strcpy(masquerade_as, CC->fake_username);
2509 strcpy(masquerade_as, "");
2512 /* Read in the message from the client. */
2513 cprintf("%d send message\n", SEND_LISTING);
2514 msg = make_message(&CC->usersupp, recp,
2515 CC->quickroom.QRname, anonymous, format_type,
2516 masquerade_as, subject);
2519 CtdlSubmitMsg(msg, valid, "");
2520 CtdlFreeMessage(msg);
2522 CC->fake_postname[0] = '\0';
2530 * API function to delete messages which match a set of criteria
2531 * (returns the actual number of messages deleted)
2533 int CtdlDeleteMessages(char *room_name, /* which room */
2534 long dmsgnum, /* or "0" for any */
2535 char *content_type /* or "" for any */
2539 struct quickroom qrbuf;
2540 struct cdbdata *cdbfr;
2541 long *msglist = NULL;
2542 long *dellist = NULL;
2545 int num_deleted = 0;
2547 struct MetaData smi;
2549 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2550 room_name, dmsgnum, content_type);
2552 /* get room record, obtaining a lock... */
2553 if (lgetroom(&qrbuf, room_name) != 0) {
2554 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2556 return (0); /* room not found */
2558 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2560 if (cdbfr != NULL) {
2561 msglist = mallok(cdbfr->len);
2562 dellist = mallok(cdbfr->len);
2563 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2564 num_msgs = cdbfr->len / sizeof(long);
2568 for (i = 0; i < num_msgs; ++i) {
2571 /* Set/clear a bit for each criterion */
2573 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2574 delete_this |= 0x01;
2576 if (strlen(content_type) == 0) {
2577 delete_this |= 0x02;
2579 GetMetaData(&smi, msglist[i]);
2580 if (!strcasecmp(smi.meta_content_type,
2582 delete_this |= 0x02;
2586 /* Delete message only if all bits are set */
2587 if (delete_this == 0x03) {
2588 dellist[num_deleted++] = msglist[i];
2593 num_msgs = sort_msglist(msglist, num_msgs);
2594 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2595 msglist, (num_msgs * sizeof(long)));
2597 qrbuf.QRhighest = msglist[num_msgs - 1];
2601 /* Go through the messages we pulled out of the index, and decrement
2602 * their reference counts by 1. If this is the only room the message
2603 * was in, the reference count will reach zero and the message will
2604 * automatically be deleted from the database. We do this in a
2605 * separate pass because there might be plug-in hooks getting called,
2606 * and we don't want that happening during an S_QUICKROOM critical
2609 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2610 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2611 AdjRefCount(dellist[i], -1);
2614 /* Now free the memory we used, and go away. */
2615 if (msglist != NULL) phree(msglist);
2616 if (dellist != NULL) phree(dellist);
2617 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2618 return (num_deleted);
2624 * Check whether the current user has permission to delete messages from
2625 * the current room (returns 1 for yes, 0 for no)
2627 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2628 getuser(&CC->usersupp, CC->curr_user);
2629 if ((CC->usersupp.axlevel < 6)
2630 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2631 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2632 && (!(CC->internal_pgm))) {
2641 * Delete message from current room
2643 void cmd_dele(char *delstr)
2648 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2649 cprintf("%d Higher access required.\n",
2650 ERROR + HIGHER_ACCESS_REQUIRED);
2653 delnum = extract_long(delstr, 0);
2655 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2658 cprintf("%d %d message%s deleted.\n", CIT_OK,
2659 num_deleted, ((num_deleted != 1) ? "s" : ""));
2661 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2667 * Back end API function for moves and deletes
2669 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2672 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2673 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2674 if (err != 0) return(err);
2682 * move or copy a message to another room
2684 void cmd_move(char *args)
2688 struct quickroom qtemp;
2692 num = extract_long(args, 0);
2693 extract(targ, args, 1);
2694 targ[ROOMNAMELEN - 1] = 0;
2695 is_copy = extract_int(args, 2);
2697 if (getroom(&qtemp, targ) != 0) {
2698 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2702 getuser(&CC->usersupp, CC->curr_user);
2703 /* Aides can move/copy */
2704 if ((CC->usersupp.axlevel < 6)
2705 /* Roomaides can move/copy */
2706 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2707 /* Permit move/copy to/from personal rooms */
2708 && (!((CC->quickroom.QRflags & QR_MAILBOX)
2709 && (qtemp.QRflags & QR_MAILBOX)))
2710 /* Permit only copy from public to personal room */
2711 && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2712 && (qtemp.QRflags & QR_MAILBOX)))) {
2713 cprintf("%d Higher access required.\n",
2714 ERROR + HIGHER_ACCESS_REQUIRED);
2718 err = CtdlCopyMsgToRoom(num, targ);
2720 cprintf("%d Cannot store message in %s: error %d\n",
2725 /* Now delete the message from the source room,
2726 * if this is a 'move' rather than a 'copy' operation.
2729 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2732 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2738 * GetMetaData() - Get the supplementary record for a message
2740 void GetMetaData(struct MetaData *smibuf, long msgnum)
2743 struct cdbdata *cdbsmi;
2746 memset(smibuf, 0, sizeof(struct MetaData));
2747 smibuf->meta_msgnum = msgnum;
2748 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2750 /* Use the negative of the message number for its supp record index */
2751 TheIndex = (0L - msgnum);
2753 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2754 if (cdbsmi == NULL) {
2755 return; /* record not found; go with defaults */
2757 memcpy(smibuf, cdbsmi->ptr,
2758 ((cdbsmi->len > sizeof(struct MetaData)) ?
2759 sizeof(struct MetaData) : cdbsmi->len));
2766 * PutMetaData() - (re)write supplementary record for a message
2768 void PutMetaData(struct MetaData *smibuf)
2772 /* Use the negative of the message number for the metadata db index */
2773 TheIndex = (0L - smibuf->meta_msgnum);
2775 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2776 smibuf->meta_msgnum, smibuf->meta_refcount);
2778 cdb_store(CDB_MSGMAIN,
2779 &TheIndex, sizeof(long),
2780 smibuf, sizeof(struct MetaData));
2785 * AdjRefCount - change the reference count for a message;
2786 * delete the message if it reaches zero
2788 void AdjRefCount(long msgnum, int incr)
2791 struct MetaData smi;
2794 /* This is a *tight* critical section; please keep it that way, as
2795 * it may get called while nested in other critical sections.
2796 * Complicating this any further will surely cause deadlock!
2798 begin_critical_section(S_SUPPMSGMAIN);
2799 GetMetaData(&smi, msgnum);
2800 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2801 msgnum, smi.meta_refcount);
2802 smi.meta_refcount += incr;
2804 end_critical_section(S_SUPPMSGMAIN);
2805 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2806 msgnum, smi.meta_refcount);
2808 /* If the reference count is now zero, delete the message
2809 * (and its supplementary record as well).
2811 if (smi.meta_refcount == 0) {
2812 lprintf(9, "Deleting message <%ld>\n", msgnum);
2814 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2816 /* We have to delete the metadata record too! */
2817 delnum = (0L - msgnum);
2818 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2823 * Write a generic object to this room
2825 * Note: this could be much more efficient. Right now we use two temporary
2826 * files, and still pull the message into memory as with all others.
2828 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2829 char *content_type, /* MIME type of this object */
2830 char *tempfilename, /* Where to fetch it from */
2831 struct usersupp *is_mailbox, /* Mailbox room? */
2832 int is_binary, /* Is encoding necessary? */
2833 int is_unique, /* Del others of this type? */
2834 unsigned int flags /* Internal save flags */
2839 char filename[PATH_MAX];
2842 struct quickroom qrbuf;
2843 char roomname[ROOMNAMELEN];
2844 struct CtdlMessage *msg;
2847 if (is_mailbox != NULL)
2848 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
2850 safestrncpy(roomname, req_room, sizeof(roomname));
2851 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2853 strcpy(filename, tmpnam(NULL));
2854 fp = fopen(filename, "w");
2858 tempfp = fopen(tempfilename, "r");
2859 if (tempfp == NULL) {
2865 fprintf(fp, "Content-type: %s\n", content_type);
2866 lprintf(9, "Content-type: %s\n", content_type);
2868 if (is_binary == 0) {
2869 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2870 while (ch = getc(tempfp), ch > 0)
2876 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2879 snprintf(cmdbuf, sizeof cmdbuf, "./base64 -e <%s >>%s",
2880 tempfilename, filename);
2884 lprintf(9, "Allocating\n");
2885 msg = mallok(sizeof(struct CtdlMessage));
2886 memset(msg, 0, sizeof(struct CtdlMessage));
2887 msg->cm_magic = CTDLMESSAGE_MAGIC;
2888 msg->cm_anon_type = MES_NORMAL;
2889 msg->cm_format_type = 4;
2890 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2891 msg->cm_fields['O'] = strdoop(req_room);
2892 msg->cm_fields['N'] = strdoop(config.c_nodename);
2893 msg->cm_fields['H'] = strdoop(config.c_humannode);
2894 msg->cm_flags = flags;
2896 lprintf(9, "Loading\n");
2897 fp = fopen(filename, "rb");
2898 fseek(fp, 0L, SEEK_END);
2901 msg->cm_fields['M'] = mallok(len);
2902 fread(msg->cm_fields['M'], len, 1, fp);
2906 /* Create the requested room if we have to. */
2907 if (getroom(&qrbuf, roomname) != 0) {
2908 create_room(roomname,
2909 ( (is_mailbox != NULL) ? 5 : 3 ),
2912 /* If the caller specified this object as unique, delete all
2913 * other objects of this type that are currently in the room.
2916 lprintf(9, "Deleted %d other msgs of this type\n",
2917 CtdlDeleteMessages(roomname, 0L, content_type));
2919 /* Now write the data */
2920 CtdlSubmitMsg(msg, NULL, roomname);
2921 CtdlFreeMessage(msg);
2929 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2930 config_msgnum = msgnum;
2934 char *CtdlGetSysConfig(char *sysconfname) {
2935 char hold_rm[ROOMNAMELEN];
2938 struct CtdlMessage *msg;
2941 strcpy(hold_rm, CC->quickroom.QRname);
2942 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2943 getroom(&CC->quickroom, hold_rm);
2948 /* We want the last (and probably only) config in this room */
2949 begin_critical_section(S_CONFIG);
2950 config_msgnum = (-1L);
2951 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2952 CtdlGetSysConfigBackend, NULL);
2953 msgnum = config_msgnum;
2954 end_critical_section(S_CONFIG);
2960 msg = CtdlFetchMessage(msgnum);
2962 conf = strdoop(msg->cm_fields['M']);
2963 CtdlFreeMessage(msg);
2970 getroom(&CC->quickroom, hold_rm);
2972 if (conf != NULL) do {
2973 extract_token(buf, conf, 0, '\n');
2974 strcpy(conf, &conf[strlen(buf)+1]);
2975 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2980 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2981 char temp[PATH_MAX];
2984 strcpy(temp, tmpnam(NULL));
2986 fp = fopen(temp, "w");
2987 if (fp == NULL) return;
2988 fprintf(fp, "%s", sysconfdata);
2991 /* this handy API function does all the work for us */
2992 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);