4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
39 #include "dynloader.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
55 #define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION))
56 #define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO))
57 #define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL))
59 extern struct config config;
63 * These are the four-character field headers we use when outputting
64 * messages in Citadel format (as opposed to RFC822 format).
67 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
68 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
69 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
70 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
71 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
72 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
101 * This function is self explanatory.
102 * (What can I say, I'm in a weird mood today...)
104 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
108 for (i = 0; i < strlen(name); ++i) {
109 if (name[i] == '@') {
110 while (isspace(name[i - 1]) && i > 0) {
111 strcpy(&name[i - 1], &name[i]);
114 while (isspace(name[i + 1])) {
115 strcpy(&name[i + 1], &name[i + 2]);
123 * Aliasing for network mail.
124 * (Error messages have been commented out, because this is a server.)
126 int alias(char *name)
127 { /* process alias and routing info for mail */
130 char aaa[SIZ], bbb[SIZ];
131 char *ignetcfg = NULL;
132 char *ignetmap = NULL;
139 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
141 fp = fopen("network/mail.aliases", "r");
143 fp = fopen("/dev/null", "r");
150 while (fgets(aaa, sizeof aaa, fp) != NULL) {
151 while (isspace(name[0]))
152 strcpy(name, &name[1]);
153 aaa[strlen(aaa) - 1] = 0;
155 for (a = 0; a < strlen(aaa); ++a) {
157 strcpy(bbb, &aaa[a + 1]);
161 if (!strcasecmp(name, aaa))
166 /* Hit the Global Address Book */
167 if (CtdlDirectoryLookup(aaa, name) == 0) {
171 lprintf(7, "Mail is being forwarded to %s\n", name);
173 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
174 for (a=0; a<strlen(name); ++a) {
175 if (name[a] == '@') {
176 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
178 lprintf(7, "Changed to <%s>\n", name);
183 /* determine local or remote type, see citadel.h */
184 at = haschar(name, '@');
185 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
186 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
187 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
189 /* figure out the delivery mode */
190 extract_token(node, name, 1, '@');
192 /* If there are one or more dots in the nodename, we assume that it
193 * is an FQDN and will attempt SMTP delivery to the Internet.
195 if (haschar(node, '.') > 0) {
196 return(MES_INTERNET);
199 /* Otherwise we look in the IGnet maps for a valid Citadel node.
200 * Try directly-connected nodes first...
202 ignetcfg = CtdlGetSysConfig(IGNETCFG);
203 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
204 extract_token(buf, ignetcfg, i, '\n');
205 extract_token(testnode, buf, 0, '|');
206 if (!strcasecmp(node, testnode)) {
214 * Then try nodes that are two or more hops away.
216 ignetmap = CtdlGetSysConfig(IGNETMAP);
217 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
218 extract_token(buf, ignetmap, i, '\n');
219 extract_token(testnode, buf, 0, '|');
220 if (!strcasecmp(node, testnode)) {
227 /* If we get to this point it's an invalid node name */
236 fp = fopen("citadel.control", "r");
237 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
243 void simple_listing(long msgnum, void *userdata)
245 cprintf("%ld\n", msgnum);
250 /* Determine if a given message matches the fields in a message template.
251 * Return 0 for a successful match.
253 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
256 /* If there aren't any fields in the template, all messages will
259 if (template == NULL) return(0);
261 /* Null messages are bogus. */
262 if (msg == NULL) return(1);
264 for (i='A'; i<='Z'; ++i) {
265 if (template->cm_fields[i] != NULL) {
266 if (msg->cm_fields[i] == NULL) {
269 if (strcasecmp(msg->cm_fields[i],
270 template->cm_fields[i])) return 1;
274 /* All compares succeeded: we have a match! */
280 * Manipulate the "seen msgs" string.
282 void CtdlSetSeen(long target_msgnum, int target_setting) {
284 struct cdbdata *cdbfr;
294 /* Learn about the user and room in question */
296 getuser(&CC->usersupp, CC->curr_user);
297 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
299 /* Load the message list */
300 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
302 msglist = mallok(cdbfr->len);
303 memcpy(msglist, cdbfr->ptr, cdbfr->len);
304 num_msgs = cdbfr->len / sizeof(long);
307 return; /* No messages at all? No further action. */
310 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
313 for (i=0; i<num_msgs; ++i) {
316 if (msglist[i] == target_msgnum) {
317 is_seen = target_setting;
320 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
326 if (lo < 0L) lo = msglist[i];
329 if ( ((is_seen == 0) && (was_seen == 1))
330 || ((is_seen == 1) && (i == num_msgs-1)) ) {
331 if ( (strlen(newseen) + 20) > SIZ) {
332 strcpy(newseen, &newseen[20]);
335 if (strlen(newseen) > 0) strcat(newseen, ",");
337 sprintf(&newseen[strlen(newseen)], "%ld", lo);
340 sprintf(&newseen[strlen(newseen)], "%ld:%ld",
349 safestrncpy(vbuf.v_seen, newseen, SIZ);
350 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
352 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
357 * API function to perform an operation for each qualifying message in the
358 * current room. (Returns the number of messages processed.)
360 int CtdlForEachMessage(int mode, long ref,
361 int moderation_level,
363 struct CtdlMessage *compare,
364 void (*CallBack) (long, void *),
370 struct cdbdata *cdbfr;
371 long *msglist = NULL;
373 int num_processed = 0;
376 struct CtdlMessage *msg;
379 int printed_lastold = 0;
381 /* Learn about the user and room in question */
383 getuser(&CC->usersupp, CC->curr_user);
384 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
386 /* Load the message list */
387 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
389 msglist = mallok(cdbfr->len);
390 memcpy(msglist, cdbfr->ptr, cdbfr->len);
391 num_msgs = cdbfr->len / sizeof(long);
394 return 0; /* No messages at all? No further action. */
399 * Now begin the traversal.
401 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
402 GetMetaData(&smi, msglist[a]);
404 /* Filter out messages that are moderated below the level
405 * currently being viewed at.
407 if (smi.meta_mod < moderation_level) {
411 /* If the caller is looking for a specific MIME type, filter
412 * out all messages which are not of the type requested.
414 if (content_type != NULL) if (strlen(content_type) > 0) {
415 if (strcasecmp(smi.meta_content_type, content_type)) {
421 num_msgs = sort_msglist(msglist, num_msgs);
423 /* If a template was supplied, filter out the messages which
424 * don't match. (This could induce some delays!)
427 if (compare != NULL) {
428 for (a = 0; a < num_msgs; ++a) {
429 msg = CtdlFetchMessage(msglist[a]);
431 if (CtdlMsgCmp(msg, compare)) {
434 CtdlFreeMessage(msg);
442 * Now iterate through the message list, according to the
443 * criteria supplied by the caller.
446 for (a = 0; a < num_msgs; ++a) {
447 thismsg = msglist[a];
448 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
449 if (is_seen) lastold = thismsg;
454 || ((mode == MSGS_OLD) && (is_seen))
455 || ((mode == MSGS_NEW) && (!is_seen))
456 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
457 || ((mode == MSGS_FIRST) && (a < ref))
458 || ((mode == MSGS_GT) && (thismsg > ref))
459 || ((mode == MSGS_EQ) && (thismsg == ref))
462 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
464 CallBack(lastold, userdata);
468 if (CallBack) CallBack(thismsg, userdata);
472 phree(msglist); /* Clean up */
473 return num_processed;
479 * cmd_msgs() - get list of message #'s in this room
480 * implements the MSGS server command using CtdlForEachMessage()
482 void cmd_msgs(char *cmdbuf)
491 int with_template = 0;
492 struct CtdlMessage *template = NULL;
494 extract(which, cmdbuf, 0);
495 cm_ref = extract_int(cmdbuf, 1);
496 with_template = extract_int(cmdbuf, 2);
500 if (!strncasecmp(which, "OLD", 3))
502 else if (!strncasecmp(which, "NEW", 3))
504 else if (!strncasecmp(which, "FIRST", 5))
506 else if (!strncasecmp(which, "LAST", 4))
508 else if (!strncasecmp(which, "GT", 2))
511 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
512 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
517 cprintf("%d Send template then receive message list\n",
519 template = (struct CtdlMessage *)
520 mallok(sizeof(struct CtdlMessage));
521 memset(template, 0, sizeof(struct CtdlMessage));
522 while(client_gets(buf), strcmp(buf,"000")) {
523 extract(tfield, buf, 0);
524 extract(tvalue, buf, 1);
525 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
526 if (!strcasecmp(tfield, msgkeys[i])) {
527 template->cm_fields[i] =
534 cprintf("%d Message list...\n", LISTING_FOLLOWS);
537 CtdlForEachMessage(mode, cm_ref,
538 CC->usersupp.moderation_filter,
539 NULL, template, simple_listing, NULL);
540 if (template != NULL) CtdlFreeMessage(template);
548 * help_subst() - support routine for help file viewer
550 void help_subst(char *strbuf, char *source, char *dest)
555 while (p = pattern2(strbuf, source), (p >= 0)) {
556 strcpy(workbuf, &strbuf[p + strlen(source)]);
557 strcpy(&strbuf[p], dest);
558 strcat(strbuf, workbuf);
563 void do_help_subst(char *buffer)
567 help_subst(buffer, "^nodename", config.c_nodename);
568 help_subst(buffer, "^humannode", config.c_humannode);
569 help_subst(buffer, "^fqdn", config.c_fqdn);
570 help_subst(buffer, "^username", CC->usersupp.fullname);
571 sprintf(buf2, "%ld", CC->usersupp.usernum);
572 help_subst(buffer, "^usernum", buf2);
573 help_subst(buffer, "^sysadm", config.c_sysadm);
574 help_subst(buffer, "^variantname", CITADEL);
575 sprintf(buf2, "%d", config.c_maxsessions);
576 help_subst(buffer, "^maxsessions", buf2);
582 * memfmout() - Citadel text formatter and paginator.
583 * Although the original purpose of this routine was to format
584 * text to the reader's screen width, all we're really using it
585 * for here is to format text out to 80 columns before sending it
586 * to the client. The client software may reformat it again.
589 int width, /* screen width to use */
590 char *mptr, /* where are we going to get our text from? */
591 char subst, /* nonzero if we should do substitutions */
592 char *nl) /* string to terminate lines with */
604 c = 1; /* c is the current pos */
608 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
610 buffer[strlen(buffer) + 1] = 0;
611 buffer[strlen(buffer)] = ch;
614 if (buffer[0] == '^')
615 do_help_subst(buffer);
617 buffer[strlen(buffer) + 1] = 0;
619 strcpy(buffer, &buffer[1]);
627 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
629 if (((old == 13) || (old == 10)) && (isspace(real))) {
637 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
638 cprintf("%s%s", nl, aaa);
647 if ((strlen(aaa) + c) > (width - 5)) {
656 if ((ch == 13) || (ch == 10)) {
657 cprintf("%s%s", aaa, nl);
664 cprintf("%s%s", aaa, nl);
670 * Callback function for mime parser that simply lists the part
672 void list_this_part(char *name, char *filename, char *partnum, char *disp,
673 void *content, char *cbtype, size_t length, char *encoding,
677 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
678 name, filename, partnum, disp, cbtype, (long)length);
683 * Callback function for mime parser that opens a section for downloading
685 void mime_download(char *name, char *filename, char *partnum, char *disp,
686 void *content, char *cbtype, size_t length, char *encoding,
690 /* Silently go away if there's already a download open... */
691 if (CC->download_fp != NULL)
694 /* ...or if this is not the desired section */
695 if (strcasecmp(desired_section, partnum))
698 CC->download_fp = tmpfile();
699 if (CC->download_fp == NULL)
702 fwrite(content, length, 1, CC->download_fp);
703 fflush(CC->download_fp);
704 rewind(CC->download_fp);
706 OpenCmdResult(filename, cbtype);
712 * Load a message from disk into memory.
713 * This is used by CtdlOutputMsg() and other fetch functions.
715 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
716 * using the CtdlMessageFree() function.
718 struct CtdlMessage *CtdlFetchMessage(long msgnum)
720 struct cdbdata *dmsgtext;
721 struct CtdlMessage *ret = NULL;
724 CIT_UBYTE field_header;
727 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
728 if (dmsgtext == NULL) {
731 mptr = dmsgtext->ptr;
733 /* Parse the three bytes that begin EVERY message on disk.
734 * The first is always 0xFF, the on-disk magic number.
735 * The second is the anonymous/public type byte.
736 * The third is the format type byte (vari, fixed, or MIME).
740 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
744 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
745 memset(ret, 0, sizeof(struct CtdlMessage));
747 ret->cm_magic = CTDLMESSAGE_MAGIC;
748 ret->cm_anon_type = *mptr++; /* Anon type byte */
749 ret->cm_format_type = *mptr++; /* Format type byte */
752 * The rest is zero or more arbitrary fields. Load them in.
753 * We're done when we encounter either a zero-length field or
754 * have just processed the 'M' (message text) field.
757 field_length = strlen(mptr);
758 if (field_length == 0)
760 field_header = *mptr++;
761 ret->cm_fields[field_header] = mallok(field_length);
762 strcpy(ret->cm_fields[field_header], mptr);
764 while (*mptr++ != 0); /* advance to next field */
766 } while ((field_length > 0) && (field_header != 'M'));
770 /* Always make sure there's something in the msg text field */
771 if (ret->cm_fields['M'] == NULL)
772 ret->cm_fields['M'] = strdoop("<no text>\n");
774 /* Perform "before read" hooks (aborting if any return nonzero) */
775 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
776 CtdlFreeMessage(ret);
785 * Returns 1 if the supplied pointer points to a valid Citadel message.
786 * If the pointer is NULL or the magic number check fails, returns 0.
788 int is_valid_message(struct CtdlMessage *msg) {
791 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
792 lprintf(3, "is_valid_message() -- self-check failed\n");
800 * 'Destructor' for struct CtdlMessage
802 void CtdlFreeMessage(struct CtdlMessage *msg)
806 if (is_valid_message(msg) == 0) return;
808 for (i = 0; i < 256; ++i)
809 if (msg->cm_fields[i] != NULL) {
810 phree(msg->cm_fields[i]);
813 msg->cm_magic = 0; /* just in case */
819 * Pre callback function for multipart/alternative
821 * NOTE: this differs from the standard behavior for a reason. Normally when
822 * displaying multipart/alternative you want to show the _last_ usable
823 * format in the message. Here we show the _first_ one, because it's
824 * usually text/plain. Since this set of functions is designed for text
825 * output to non-MIME-aware clients, this is the desired behavior.
828 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
829 void *content, char *cbtype, size_t length, char *encoding,
832 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);
833 if (!strcasecmp(cbtype, "multipart/alternative")) {
841 * Post callback function for multipart/alternative
843 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
844 void *content, char *cbtype, size_t length, char *encoding,
847 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);
848 if (!strcasecmp(cbtype, "multipart/alternative")) {
856 * Inline callback function for mime parser that wants to display text
858 void fixed_output(char *name, char *filename, char *partnum, char *disp,
859 void *content, char *cbtype, size_t length, char *encoding,
867 lprintf(9, "fixed_output() type=<%s>\n", cbtype);
870 * If we're in the middle of a multipart/alternative scope and
871 * we've already printed another section, skip this one.
873 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
874 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
879 if ( (!strcasecmp(cbtype, "text/plain"))
880 || (strlen(cbtype)==0) ) {
886 if (ch==10) cprintf("\r\n");
887 else cprintf("%c", ch);
891 if (ch != '\n') cprintf("\n");
893 else if (!strcasecmp(cbtype, "text/html")) {
894 ptr = html_to_ascii(content, 80, 0);
899 if (ch==10) cprintf("\r\n");
900 else cprintf("%c", ch);
904 else if (strncasecmp(cbtype, "multipart/", 10)) {
905 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
906 partnum, filename, cbtype, (long)length);
912 * Get a message off disk. (returns om_* values found in msgbase.h)
915 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
916 int mode, /* how would you like that message? */
917 int headers_only, /* eschew the message body? */
918 int do_proto, /* do Citadel protocol responses? */
919 int crlf /* Use CRLF newlines instead of LF? */
921 struct CtdlMessage *TheMessage;
924 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
929 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
930 if (do_proto) cprintf("%d Not logged in.\n",
931 ERROR + NOT_LOGGED_IN);
932 return(om_not_logged_in);
935 /* FIXME ... small security issue
936 * We need to check to make sure the requested message is actually
937 * in the current room, and set msg_ok to 1 only if it is. This
938 * functionality is currently missing because I'm in a hurry to replace
939 * broken production code with nonbroken pre-beta code. :( -- ajc
942 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
944 return(om_no_such_msg);
949 * Fetch the message from disk
951 TheMessage = CtdlFetchMessage(msg_num);
952 if (TheMessage == NULL) {
953 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
955 return(om_no_such_msg);
958 retcode = CtdlOutputPreLoadedMsg(
959 TheMessage, msg_num, mode,
960 headers_only, do_proto, crlf);
962 CtdlFreeMessage(TheMessage);
968 * Get a message off disk. (returns om_* values found in msgbase.h)
971 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
973 int mode, /* how would you like that message? */
974 int headers_only, /* eschew the message body? */
975 int do_proto, /* do Citadel protocol responses? */
976 int crlf /* Use CRLF newlines instead of LF? */
982 char display_name[SIZ];
984 char *nl; /* newline string */
986 /* buffers needed for RFC822 translation */
996 sprintf(mid, "%ld", msg_num);
997 nl = (crlf ? "\r\n" : "\n");
999 if (!is_valid_message(TheMessage)) {
1000 lprintf(1, "ERROR: invalid preloaded message for output\n");
1001 return(om_no_such_msg);
1004 /* Are we downloading a MIME component? */
1005 if (mode == MT_DOWNLOAD) {
1006 if (TheMessage->cm_format_type != FMT_RFC822) {
1008 cprintf("%d This is not a MIME message.\n",
1010 } else if (CC->download_fp != NULL) {
1011 if (do_proto) cprintf(
1012 "%d You already have a download open.\n",
1015 /* Parse the message text component */
1016 mptr = TheMessage->cm_fields['M'];
1017 mime_parser(mptr, NULL,
1018 *mime_download, NULL, NULL,
1020 /* If there's no file open by this time, the requested
1021 * section wasn't found, so print an error
1023 if (CC->download_fp == NULL) {
1024 if (do_proto) cprintf(
1025 "%d Section %s not found.\n",
1026 ERROR + FILE_NOT_FOUND,
1030 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1033 /* now for the user-mode message reading loops */
1034 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1036 /* Tell the client which format type we're using. If this is a
1037 * MIME message, *lie* about it and tell the user it's fixed-format.
1039 if (mode == MT_CITADEL) {
1040 if (TheMessage->cm_format_type == FMT_RFC822) {
1041 if (do_proto) cprintf("type=1\n");
1044 if (do_proto) cprintf("type=%d\n",
1045 TheMessage->cm_format_type);
1049 /* nhdr=yes means that we're only displaying headers, no body */
1050 if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1051 if (do_proto) cprintf("nhdr=yes\n");
1054 /* begin header processing loop for Citadel message format */
1056 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1058 strcpy(display_name, "<unknown>");
1059 if (TheMessage->cm_fields['A']) {
1060 strcpy(buf, TheMessage->cm_fields['A']);
1061 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1062 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1063 strcpy(display_name, "****");
1065 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1066 strcpy(display_name, "anonymous");
1069 strcpy(display_name, buf);
1071 if ((is_room_aide())
1072 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1073 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1074 sprintf(&display_name[strlen(display_name)],
1079 strcpy(allkeys, FORDER);
1080 for (i=0; i<strlen(allkeys); ++i) {
1081 k = (int) allkeys[i];
1083 if ( (TheMessage->cm_fields[k] != NULL)
1084 && (msgkeys[k] != NULL) ) {
1086 if (do_proto) cprintf("%s=%s\n",
1090 /* Don't show Internet address for
1093 else if (k == 'F') {
1094 if (do_proto) if (TheMessage->cm_fields['N'] != NULL) if (strcasecmp(TheMessage->cm_fields['N'], config.c_nodename)) {
1097 TheMessage->cm_fields[k]
1102 /* Masquerade display name if needed */
1104 if (do_proto) cprintf("%s=%s\n",
1106 TheMessage->cm_fields[k]
1115 /* begin header processing loop for RFC822 transfer format */
1120 strcpy(snode, NODENAME);
1121 strcpy(lnode, HUMANNODE);
1122 if (mode == MT_RFC822) {
1123 cprintf("X-UIDL: %ld%s", msg_num, nl);
1124 for (i = 0; i < 256; ++i) {
1125 if (TheMessage->cm_fields[i]) {
1126 mptr = TheMessage->cm_fields[i];
1129 strcpy(luser, mptr);
1130 strcpy(suser, mptr);
1133 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1134 into thinking that mail messages are newsgroup messages instead. When we
1135 add NNTP support back into Citadel we'll have to add code to only output
1136 this field when appropriate.
1137 else if (i == 'P') {
1138 cprintf("Path: %s%s", mptr, nl);
1142 cprintf("Subject: %s%s", mptr, nl);
1144 safestrncpy(mid, mptr, sizeof mid);
1146 safestrncpy(lnode, mptr, sizeof lnode);
1148 safestrncpy(fuser, mptr, sizeof fuser);
1150 cprintf("X-Citadel-Room: %s%s",
1153 safestrncpy(snode, mptr, sizeof snode);
1155 cprintf("To: %s%s", mptr, nl);
1156 else if (i == 'T') {
1157 datestring(datestamp, atol(mptr),
1158 DATESTRING_RFC822 );
1159 cprintf("Date: %s%s", datestamp, nl);
1165 for (i=0; i<strlen(suser); ++i) {
1166 suser[i] = tolower(suser[i]);
1167 if (!isalnum(suser[i])) suser[i]='_';
1170 if (mode == MT_RFC822) {
1171 if (!strcasecmp(snode, NODENAME)) {
1172 strcpy(snode, FQDN);
1175 /* Construct a fun message id */
1176 cprintf("Message-ID: <%s", mid);
1177 if (strchr(mid, '@')==NULL) {
1178 cprintf("@%s", snode);
1182 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1184 if (strlen(fuser) > 0) {
1185 cprintf("From: %s (%s)%s", fuser, luser, nl);
1188 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1191 cprintf("Organization: %s%s", lnode, nl);
1194 /* end header processing loop ... at this point, we're in the text */
1196 mptr = TheMessage->cm_fields['M'];
1198 /* Tell the client about the MIME parts in this message */
1199 if (TheMessage->cm_format_type == FMT_RFC822) {
1200 if (mode == MT_CITADEL) {
1201 mime_parser(mptr, NULL,
1202 *list_this_part, NULL, NULL,
1205 else if (mode == MT_MIME) { /* list parts only */
1206 mime_parser(mptr, NULL,
1207 *list_this_part, NULL, NULL,
1209 if (do_proto) cprintf("000\n");
1212 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1213 /* FIXME ... we have to put some code in here to avoid
1214 * printing duplicate header information when both
1215 * Citadel and RFC822 headers exist. Preference should
1216 * probably be given to the RFC822 headers.
1218 while (ch=*(mptr++), ch!=0) {
1220 else if (ch==10) cprintf("%s", nl);
1221 else cprintf("%c", ch);
1223 if (do_proto) cprintf("000\n");
1229 if (do_proto) cprintf("000\n");
1233 /* signify start of msg text */
1234 if (mode == MT_CITADEL)
1235 if (do_proto) cprintf("text\n");
1236 if (mode == MT_RFC822) {
1237 if (TheMessage->cm_fields['U'] == NULL) {
1238 cprintf("Subject: (no subject)%s", nl);
1243 /* If the format type on disk is 1 (fixed-format), then we want
1244 * everything to be output completely literally ... regardless of
1245 * what message transfer format is in use.
1247 if (TheMessage->cm_format_type == FMT_FIXED) {
1249 while (ch = *mptr++, ch > 0) {
1252 if ((ch == 10) || (strlen(buf) > 250)) {
1253 cprintf("%s%s", buf, nl);
1256 buf[strlen(buf) + 1] = 0;
1257 buf[strlen(buf)] = ch;
1260 if (strlen(buf) > 0)
1261 cprintf("%s%s", buf, nl);
1264 /* If the message on disk is format 0 (Citadel vari-format), we
1265 * output using the formatter at 80 columns. This is the final output
1266 * form if the transfer format is RFC822, but if the transfer format
1267 * is Citadel proprietary, it'll still work, because the indentation
1268 * for new paragraphs is correct and the client will reformat the
1269 * message to the reader's screen width.
1271 if (TheMessage->cm_format_type == FMT_CITADEL) {
1272 memfmout(80, mptr, 0, nl);
1275 /* If the message on disk is format 4 (MIME), we've gotta hand it
1276 * off to the MIME parser. The client has already been told that
1277 * this message is format 1 (fixed format), so the callback function
1278 * we use will display those parts as-is.
1280 if (TheMessage->cm_format_type == FMT_RFC822) {
1281 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1282 memset(ma, 0, sizeof(struct ma_info));
1283 mime_parser(mptr, NULL,
1284 *fixed_output, *fixed_output_pre, *fixed_output_post,
1288 /* now we're done */
1289 if (do_proto) cprintf("000\n");
1296 * display a message (mode 0 - Citadel proprietary)
1298 void cmd_msg0(char *cmdbuf)
1301 int headers_only = 0;
1303 msgid = extract_long(cmdbuf, 0);
1304 headers_only = extract_int(cmdbuf, 1);
1306 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1312 * display a message (mode 2 - RFC822)
1314 void cmd_msg2(char *cmdbuf)
1317 int headers_only = 0;
1319 msgid = extract_long(cmdbuf, 0);
1320 headers_only = extract_int(cmdbuf, 1);
1322 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1328 * display a message (mode 3 - IGnet raw format - internal programs only)
1330 void cmd_msg3(char *cmdbuf)
1333 struct CtdlMessage *msg;
1336 if (CC->internal_pgm == 0) {
1337 cprintf("%d This command is for internal programs only.\n",
1342 msgnum = extract_long(cmdbuf, 0);
1343 msg = CtdlFetchMessage(msgnum);
1345 cprintf("%d Message %ld not found.\n",
1350 serialize_message(&smr, msg);
1351 CtdlFreeMessage(msg);
1354 cprintf("%d Unable to serialize message\n",
1355 ERROR+INTERNAL_ERROR);
1359 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1360 client_write(smr.ser, smr.len);
1367 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1369 void cmd_msg4(char *cmdbuf)
1373 msgid = extract_long(cmdbuf, 0);
1374 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1378 * Open a component of a MIME message as a download file
1380 void cmd_opna(char *cmdbuf)
1384 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1386 msgid = extract_long(cmdbuf, 0);
1387 extract(desired_section, cmdbuf, 1);
1389 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1394 * Save a message pointer into a specified room
1395 * (Returns 0 for success, nonzero for failure)
1396 * roomname may be NULL to use the current room
1398 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1400 char hold_rm[ROOMNAMELEN];
1401 struct cdbdata *cdbfr;
1404 long highest_msg = 0L;
1405 struct CtdlMessage *msg = NULL;
1407 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1408 roomname, msgid, flags);
1410 strcpy(hold_rm, CC->quickroom.QRname);
1412 /* We may need to check to see if this message is real */
1413 if ( (flags & SM_VERIFY_GOODNESS)
1414 || (flags & SM_DO_REPL_CHECK)
1416 msg = CtdlFetchMessage(msgid);
1417 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1420 /* Perform replication checks if necessary */
1421 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1423 if (getroom(&CC->quickroom,
1424 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1426 lprintf(9, "No such room <%s>\n", roomname);
1427 if (msg != NULL) CtdlFreeMessage(msg);
1428 return(ERROR + ROOM_NOT_FOUND);
1431 if (ReplicationChecks(msg) != 0) {
1432 getroom(&CC->quickroom, hold_rm);
1433 if (msg != NULL) CtdlFreeMessage(msg);
1434 lprintf(9, "Did replication, and newer exists\n");
1439 /* Now the regular stuff */
1440 if (lgetroom(&CC->quickroom,
1441 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1443 lprintf(9, "No such room <%s>\n", roomname);
1444 if (msg != NULL) CtdlFreeMessage(msg);
1445 return(ERROR + ROOM_NOT_FOUND);
1448 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1449 if (cdbfr == NULL) {
1453 msglist = mallok(cdbfr->len);
1454 if (msglist == NULL)
1455 lprintf(3, "ERROR malloc msglist!\n");
1456 num_msgs = cdbfr->len / sizeof(long);
1457 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1462 /* Make sure the message doesn't already exist in this room. It
1463 * is absolutely taboo to have more than one reference to the same
1464 * message in a room.
1466 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1467 if (msglist[i] == msgid) {
1468 lputroom(&CC->quickroom); /* unlock the room */
1469 getroom(&CC->quickroom, hold_rm);
1470 if (msg != NULL) CtdlFreeMessage(msg);
1471 return(ERROR + ALREADY_EXISTS);
1475 /* Now add the new message */
1477 msglist = reallok(msglist,
1478 (num_msgs * sizeof(long)));
1480 if (msglist == NULL) {
1481 lprintf(3, "ERROR: can't realloc message list!\n");
1483 msglist[num_msgs - 1] = msgid;
1485 /* Sort the message list, so all the msgid's are in order */
1486 num_msgs = sort_msglist(msglist, num_msgs);
1488 /* Determine the highest message number */
1489 highest_msg = msglist[num_msgs - 1];
1491 /* Write it back to disk. */
1492 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1493 msglist, num_msgs * sizeof(long));
1495 /* Free up the memory we used. */
1498 /* Update the highest-message pointer and unlock the room. */
1499 CC->quickroom.QRhighest = highest_msg;
1500 lputroom(&CC->quickroom);
1501 getroom(&CC->quickroom, hold_rm);
1503 /* Bump the reference count for this message. */
1504 if ((flags & SM_DONT_BUMP_REF)==0) {
1505 AdjRefCount(msgid, +1);
1508 /* Return success. */
1509 if (msg != NULL) CtdlFreeMessage(msg);
1516 * Message base operation to send a message to the master file
1517 * (returns new message number)
1519 * This is the back end for CtdlSubmitMsg() and should not be directly
1520 * called by server-side modules.
1523 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1524 FILE *save_a_copy) /* save a copy to disk? */
1531 /* Get a new message number */
1532 newmsgid = get_new_message_number();
1533 sprintf(msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1535 /* Generate an ID if we don't have one already */
1536 if (msg->cm_fields['I']==NULL) {
1537 msg->cm_fields['I'] = strdoop(msgidbuf);
1540 serialize_message(&smr, msg);
1543 cprintf("%d Unable to serialize message\n",
1544 ERROR+INTERNAL_ERROR);
1548 /* Write our little bundle of joy into the message base */
1549 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1550 smr.ser, smr.len) < 0) {
1551 lprintf(2, "Can't store message\n");
1557 /* If the caller specified that a copy should be saved to a particular
1558 * file handle, do that now too.
1560 if (save_a_copy != NULL) {
1561 fwrite(smr.ser, smr.len, 1, save_a_copy);
1564 /* Free the memory we used for the serialized message */
1567 /* Return the *local* message ID to the caller
1568 * (even if we're storing an incoming network message)
1576 * Serialize a struct CtdlMessage into the format used on disk and network.
1578 * This function loads up a "struct ser_ret" (defined in server.h) which
1579 * contains the length of the serialized message and a pointer to the
1580 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1582 void serialize_message(struct ser_ret *ret, /* return values */
1583 struct CtdlMessage *msg) /* unserialized msg */
1587 static char *forder = FORDER;
1589 if (is_valid_message(msg) == 0) return; /* self check */
1592 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1593 ret->len = ret->len +
1594 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1596 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1597 ret->ser = mallok(ret->len);
1598 if (ret->ser == NULL) {
1604 ret->ser[1] = msg->cm_anon_type;
1605 ret->ser[2] = msg->cm_format_type;
1608 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1609 ret->ser[wlen++] = (char)forder[i];
1610 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1611 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1613 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1614 (long)ret->len, (long)wlen);
1622 * Back end for the ReplicationChecks() function
1624 void check_repl(long msgnum, void *userdata) {
1625 struct CtdlMessage *msg;
1626 time_t timestamp = (-1L);
1628 lprintf(9, "check_repl() found message %ld\n", msgnum);
1629 msg = CtdlFetchMessage(msgnum);
1630 if (msg == NULL) return;
1631 if (msg->cm_fields['T'] != NULL) {
1632 timestamp = atol(msg->cm_fields['T']);
1634 CtdlFreeMessage(msg);
1636 if (timestamp > msg_repl->highest) {
1637 msg_repl->highest = timestamp; /* newer! */
1638 lprintf(9, "newer!\n");
1641 lprintf(9, "older!\n");
1643 /* Existing isn't newer? Then delete the old one(s). */
1644 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1649 * Check to see if any messages already exist which carry the same Extended ID
1653 * -> With older timestamps: delete them and return 0. Message will be saved.
1654 * -> With newer timestamps: return 1. Message save will be aborted.
1656 int ReplicationChecks(struct CtdlMessage *msg) {
1657 struct CtdlMessage *template;
1660 lprintf(9, "ReplicationChecks() started\n");
1661 /* No extended id? Don't do anything. */
1662 if (msg->cm_fields['E'] == NULL) return 0;
1663 if (strlen(msg->cm_fields['E']) == 0) return 0;
1664 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1666 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1667 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1668 msg_repl->highest = atol(msg->cm_fields['T']);
1670 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1671 memset(template, 0, sizeof(struct CtdlMessage));
1672 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1674 CtdlForEachMessage(MSGS_ALL, 0L, (-127), NULL, template,
1677 /* If a newer message exists with the same Extended ID, abort
1680 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1684 CtdlFreeMessage(template);
1685 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1693 * Save a message to disk and submit it into the delivery system.
1695 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1696 struct recptypes *recps, /* recipients (if mail) */
1697 char *force /* force a particular room? */
1700 char hold_rm[ROOMNAMELEN];
1701 char actual_rm[ROOMNAMELEN];
1702 char force_room[ROOMNAMELEN];
1703 char content_type[SIZ]; /* We have to learn this */
1704 char recipient[SIZ];
1707 struct usersupp userbuf;
1709 struct MetaData smi;
1710 FILE *network_fp = NULL;
1711 static int seqnum = 1;
1712 struct CtdlMessage *imsg = NULL;
1715 char *hold_R, *hold_D;
1717 lprintf(9, "CtdlSubmitMsg() called\n");
1718 if (is_valid_message(msg) == 0) return(-1); /* self check */
1720 /* If this message has no timestamp, we take the liberty of
1721 * giving it one, right now.
1723 if (msg->cm_fields['T'] == NULL) {
1724 lprintf(9, "Generating timestamp\n");
1725 sprintf(aaa, "%ld", (long)time(NULL));
1726 msg->cm_fields['T'] = strdoop(aaa);
1729 /* If this message has no path, we generate one.
1731 if (msg->cm_fields['P'] == NULL) {
1732 lprintf(9, "Generating path\n");
1733 if (msg->cm_fields['A'] != NULL) {
1734 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1735 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1736 if (isspace(msg->cm_fields['P'][a])) {
1737 msg->cm_fields['P'][a] = ' ';
1742 msg->cm_fields['P'] = strdoop("unknown");
1746 strcpy(force_room, force);
1748 /* Learn about what's inside, because it's what's inside that counts */
1749 lprintf(9, "Learning what's inside\n");
1750 if (msg->cm_fields['M'] == NULL) {
1751 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1754 switch (msg->cm_format_type) {
1756 strcpy(content_type, "text/x-citadel-variformat");
1759 strcpy(content_type, "text/plain");
1762 strcpy(content_type, "text/plain");
1763 /* advance past header fields */
1764 mptr = msg->cm_fields['M'];
1767 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1768 safestrncpy(content_type, mptr,
1769 sizeof(content_type));
1770 strcpy(content_type, &content_type[14]);
1771 for (a = 0; a < strlen(content_type); ++a)
1772 if ((content_type[a] == ';')
1773 || (content_type[a] == ' ')
1774 || (content_type[a] == 13)
1775 || (content_type[a] == 10))
1776 content_type[a] = 0;
1783 /* Goto the correct room */
1784 lprintf(9, "Switching rooms\n");
1785 strcpy(hold_rm, CC->quickroom.QRname);
1786 strcpy(actual_rm, CC->quickroom.QRname);
1787 if (recps != NULL) {
1788 strcpy(actual_rm, SENTITEMS);
1791 /* If the user is a twit, move to the twit room for posting */
1792 lprintf(9, "Handling twit stuff\n");
1794 if (CC->usersupp.axlevel == 2) {
1795 strcpy(hold_rm, actual_rm);
1796 strcpy(actual_rm, config.c_twitroom);
1800 /* ...or if this message is destined for Aide> then go there. */
1801 if (strlen(force_room) > 0) {
1802 strcpy(actual_rm, force_room);
1805 lprintf(9, "Possibly relocating\n");
1806 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1807 getroom(&CC->quickroom, actual_rm);
1811 * If this message has no O (room) field, generate one.
1813 if (msg->cm_fields['O'] == NULL) {
1814 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1817 /* Perform "before save" hooks (aborting if any return nonzero) */
1818 lprintf(9, "Performing before-save hooks\n");
1819 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1821 /* If this message has an Extended ID, perform replication checks */
1822 lprintf(9, "Performing replication checks\n");
1823 if (ReplicationChecks(msg) > 0) return(-1);
1825 /* Save it to disk */
1826 lprintf(9, "Saving to disk\n");
1827 newmsgid = send_message(msg, NULL);
1828 if (newmsgid <= 0L) return(-1);
1830 /* Write a supplemental message info record. This doesn't have to
1831 * be a critical section because nobody else knows about this message
1834 lprintf(9, "Creating MetaData record\n");
1835 memset(&smi, 0, sizeof(struct MetaData));
1836 smi.meta_msgnum = newmsgid;
1837 smi.meta_refcount = 0;
1838 safestrncpy(smi.meta_content_type, content_type, 64);
1841 /* Now figure out where to store the pointers */
1842 lprintf(9, "Storing pointers\n");
1844 /* If this is being done by the networker delivering a private
1845 * message, we want to BYPASS saving the sender's copy (because there
1846 * is no local sender; it would otherwise go to the Trashcan).
1848 if ((!CC->internal_pgm) || (recps == NULL)) {
1849 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1850 lprintf(3, "ERROR saving message pointer!\n");
1851 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1855 /* For internet mail, drop a copy in the outbound queue room */
1857 if (recps->num_internet > 0) {
1858 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1861 /* If other rooms are specified, drop them there too. */
1863 if (recps->num_room > 0)
1864 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1865 extract(recipient, recps->recp_room, i);
1866 lprintf(9, "Delivering to local room <%s>\n", recipient);
1867 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1870 /* Bump this user's messages posted counter. */
1871 lprintf(9, "Updating user\n");
1872 lgetuser(&CC->usersupp, CC->curr_user);
1873 CC->usersupp.posted = CC->usersupp.posted + 1;
1874 lputuser(&CC->usersupp);
1876 /* If this is private, local mail, make a copy in the
1877 * recipient's mailbox and bump the reference count.
1880 if (recps->num_local > 0)
1881 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1882 extract(recipient, recps->recp_local, i);
1883 lprintf(9, "Delivering private local mail to <%s>\n",
1885 if (getuser(&userbuf, recipient) == 0) {
1886 MailboxName(actual_rm, &userbuf, MAILROOM);
1887 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1890 lprintf(9, "No user <%s>\n", recipient);
1891 CtdlSaveMsgPointerInRoom(AIDEROOM, newmsgid, 0);
1895 /* Perform "after save" hooks */
1896 lprintf(9, "Performing after-save hooks\n");
1897 PerformMessageHooks(msg, EVT_AFTERSAVE);
1899 /* For IGnet mail, we have to save a new copy into the spooler for
1900 * each recipient, with the R and D fields set to the recipient and
1901 * destination-node. This has two ugly side effects: all other
1902 * recipients end up being unlisted in this recipient's copy of the
1903 * message, and it has to deliver multiple messages to the same
1904 * node. We'll revisit this again in a year or so when everyone has
1905 * a network spool receiver that can handle the new style messages.
1908 if (recps->num_ignet > 0)
1909 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1910 extract(recipient, recps->recp_ignet, i);
1912 hold_R = msg->cm_fields['R'];
1913 hold_D = msg->cm_fields['D'];
1914 msg->cm_fields['R'] = mallok(SIZ);
1915 msg->cm_fields['D'] = mallok(SIZ);
1916 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1917 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1919 serialize_message(&smr, msg);
1922 "./network/spoolin/netmail.%04lx.%04x.%04x",
1923 (long) getpid(), CC->cs_pid, ++seqnum);
1924 network_fp = fopen(aaa, "wb+");
1925 if (network_fp != NULL) {
1926 fwrite(smr.ser, smr.len, 1, network_fp);
1932 phree(msg->cm_fields['R']);
1933 phree(msg->cm_fields['D']);
1934 msg->cm_fields['R'] = hold_R;
1935 msg->cm_fields['D'] = hold_D;
1938 /* Go back to the room we started from */
1939 lprintf(9, "Returning to original room\n");
1940 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1941 getroom(&CC->quickroom, hold_rm);
1943 /* For internet mail, generate delivery instructions.
1944 * Yes, this is recursive. Deal with it. Infinite recursion does
1945 * not happen because the delivery instructions message does not
1946 * contain a recipient.
1949 if (recps->num_internet > 0) {
1950 lprintf(9, "Generating delivery instructions\n");
1951 instr = mallok(SIZ * 2);
1953 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
1955 SPOOLMIME, newmsgid, (long)time(NULL),
1956 msg->cm_fields['A'], msg->cm_fields['N']
1959 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
1960 extract(recipient, recps->recp_internet, i);
1961 sprintf(&instr[strlen(instr)],
1962 "remote|%s|0||\n", recipient);
1965 imsg = mallok(sizeof(struct CtdlMessage));
1966 memset(imsg, 0, sizeof(struct CtdlMessage));
1967 imsg->cm_magic = CTDLMESSAGE_MAGIC;
1968 imsg->cm_anon_type = MES_NORMAL;
1969 imsg->cm_format_type = FMT_RFC822;
1970 imsg->cm_fields['A'] = strdoop("Citadel");
1971 imsg->cm_fields['M'] = instr;
1972 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
1973 CtdlFreeMessage(imsg);
1982 * Convenience function for generating small administrative messages.
1984 void quickie_message(char *from, char *to, char *room, char *text)
1986 struct CtdlMessage *msg;
1988 msg = mallok(sizeof(struct CtdlMessage));
1989 memset(msg, 0, sizeof(struct CtdlMessage));
1990 msg->cm_magic = CTDLMESSAGE_MAGIC;
1991 msg->cm_anon_type = MES_NORMAL;
1992 msg->cm_format_type = 0;
1993 msg->cm_fields['A'] = strdoop(from);
1994 msg->cm_fields['O'] = strdoop(room);
1995 msg->cm_fields['N'] = strdoop(NODENAME);
1997 msg->cm_fields['R'] = strdoop(to);
1998 msg->cm_fields['M'] = strdoop(text);
2000 CtdlSubmitMsg(msg, NULL, room);
2001 CtdlFreeMessage(msg);
2002 syslog(LOG_NOTICE, text);
2008 * Back end function used by make_message() and similar functions
2010 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2011 size_t maxlen, /* maximum message length */
2012 char *exist /* if non-null, append to it;
2013 exist is ALWAYS freed */
2017 size_t message_len = 0;
2018 size_t buffer_len = 0;
2022 if (exist == NULL) {
2026 m = reallok(exist, strlen(exist) + 4096);
2027 if (m == NULL) phree(exist);
2030 /* flush the input if we have nowhere to store it */
2032 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2036 /* otherwise read it into memory */
2042 /* read in the lines of message text one by one */
2043 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2045 /* strip trailing newline type stuff */
2046 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2047 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2049 linelen = strlen(buf);
2051 /* augment the buffer if we have to */
2052 if ((message_len + linelen + 2) > buffer_len) {
2053 lprintf(9, "realloking\n");
2054 ptr = reallok(m, (buffer_len * 2) );
2055 if (ptr == NULL) { /* flush if can't allocate */
2056 while ( (client_gets(buf)>0) &&
2057 strcmp(buf, terminator)) ;;
2060 buffer_len = (buffer_len * 2);
2062 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2066 /* Add the new line to the buffer. NOTE: this loop must avoid
2067 * using functions like strcat() and strlen() because they
2068 * traverse the entire buffer upon every call, and doing that
2069 * for a multi-megabyte message slows it down beyond usability.
2071 strcpy(&m[message_len], buf);
2072 m[message_len + linelen] = '\n';
2073 m[message_len + linelen + 1] = 0;
2074 message_len = message_len + linelen + 1;
2076 /* if we've hit the max msg length, flush the rest */
2077 if (message_len >= maxlen) {
2078 while ( (client_gets(buf)>0)
2079 && strcmp(buf, terminator)) ;;
2090 * Build a binary message to be saved on disk.
2093 static struct CtdlMessage *make_message(
2094 struct usersupp *author, /* author's usersupp structure */
2095 char *recipient, /* NULL if it's not mail */
2096 char *room, /* room where it's going */
2097 int type, /* see MES_ types in header file */
2098 int format_type, /* variformat, plain text, MIME... */
2099 char *fake_name, /* who we're masquerading as */
2100 char *subject /* Subject (optional) */
2102 char dest_node[SIZ];
2104 struct CtdlMessage *msg;
2106 msg = mallok(sizeof(struct CtdlMessage));
2107 memset(msg, 0, sizeof(struct CtdlMessage));
2108 msg->cm_magic = CTDLMESSAGE_MAGIC;
2109 msg->cm_anon_type = type;
2110 msg->cm_format_type = format_type;
2112 /* Don't confuse the poor folks if it's not routed mail. */
2113 strcpy(dest_node, "");
2117 sprintf(buf, "cit%ld", author->usernum); /* Path */
2118 msg->cm_fields['P'] = strdoop(buf);
2120 sprintf(buf, "%ld", (long)time(NULL)); /* timestamp */
2121 msg->cm_fields['T'] = strdoop(buf);
2123 if (fake_name[0]) /* author */
2124 msg->cm_fields['A'] = strdoop(fake_name);
2126 msg->cm_fields['A'] = strdoop(author->fullname);
2128 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2129 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2132 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2135 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2136 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2138 if (recipient[0] != 0) {
2139 msg->cm_fields['R'] = strdoop(recipient);
2141 if (dest_node[0] != 0) {
2142 msg->cm_fields['D'] = strdoop(dest_node);
2145 if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
2146 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2149 if (subject != NULL) {
2151 if (strlen(subject) > 0) {
2152 msg->cm_fields['U'] = strdoop(subject);
2156 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2157 config.c_maxmsglen, NULL);
2164 * Check to see whether we have permission to post a message in the current
2165 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2166 * returns 0 on success.
2168 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf) {
2170 if (!(CC->logged_in)) {
2171 sprintf(errmsgbuf, "Not logged in.");
2172 return (ERROR + NOT_LOGGED_IN);
2175 if ((CC->usersupp.axlevel < 2)
2176 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2177 sprintf(errmsgbuf, "Need to be validated to enter "
2178 "(except in %s> to sysop)", MAILROOM);
2179 return (ERROR + HIGHER_ACCESS_REQUIRED);
2182 if ((CC->usersupp.axlevel < 4)
2183 && (CC->quickroom.QRflags & QR_NETWORK)) {
2184 sprintf(errmsgbuf, "Need net privileges to enter here.");
2185 return (ERROR + HIGHER_ACCESS_REQUIRED);
2188 if ((CC->usersupp.axlevel < 6)
2189 && (CC->quickroom.QRflags & QR_READONLY)) {
2190 sprintf(errmsgbuf, "Sorry, this is a read-only room.");
2191 return (ERROR + HIGHER_ACCESS_REQUIRED);
2194 strcpy(errmsgbuf, "Ok");
2200 * Validate recipients, count delivery types and errors, and handle aliasing
2201 * FIXME check for dupes!!!!!
2203 struct recptypes *validate_recipients(char *recipients) {
2204 struct recptypes *ret;
2205 char this_recp[SIZ];
2206 char this_recp_cooked[SIZ];
2212 struct usersupp tempUS;
2213 struct quickroom tempQR;
2216 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2217 if (ret == NULL) return(NULL);
2218 memset(ret, 0, sizeof(struct recptypes));
2221 ret->num_internet = 0;
2226 if (recipients == NULL) {
2229 else if (strlen(recipients) == 0) {
2233 /* Change all valid separator characters to commas */
2234 for (i=0; i<strlen(recipients); ++i) {
2235 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2236 recipients[i] = ',';
2241 num_recps = num_tokens(recipients, ',');
2244 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2245 extract_token(this_recp, recipients, i, ',');
2247 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2248 mailtype = alias(this_recp);
2249 mailtype = alias(this_recp);
2250 mailtype = alias(this_recp);
2251 for (j=0; j<=strlen(this_recp); ++j) {
2252 if (this_recp[j]=='_') {
2253 this_recp_cooked[j] = ' ';
2256 this_recp_cooked[j] = this_recp[j];
2262 if (!strcasecmp(this_recp, "sysop")) {
2264 strcpy(this_recp, AIDEROOM);
2265 if (strlen(ret->recp_room) > 0) {
2266 strcat(ret->recp_room, "|");
2268 strcat(ret->recp_room, this_recp);
2270 else if (getuser(&tempUS, this_recp) == 0) {
2272 strcpy(this_recp, tempUS.fullname);
2273 if (strlen(ret->recp_local) > 0) {
2274 strcat(ret->recp_local, "|");
2276 strcat(ret->recp_local, this_recp);
2278 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2280 strcpy(this_recp, tempUS.fullname);
2281 if (strlen(ret->recp_local) > 0) {
2282 strcat(ret->recp_local, "|");
2284 strcat(ret->recp_local, this_recp);
2286 else if ( (!strncasecmp(this_recp, "room_", 5))
2287 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2289 if (strlen(ret->recp_room) > 0) {
2290 strcat(ret->recp_room, "|");
2292 strcat(ret->recp_room, &this_recp_cooked[5]);
2300 ++ret->num_internet;
2301 if (strlen(ret->recp_internet) > 0) {
2302 strcat(ret->recp_internet, "|");
2304 strcat(ret->recp_internet, this_recp);
2308 if (strlen(ret->recp_ignet) > 0) {
2309 strcat(ret->recp_ignet, "|");
2311 strcat(ret->recp_ignet, this_recp);
2319 if (strlen(ret->errormsg) == 0) {
2321 "Invalid recipient: %s",
2328 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2329 strcat(ret->errormsg, append);
2333 if (strlen(ret->display_recp) == 0) {
2334 strcpy(append, this_recp);
2337 sprintf(append, ", %s", this_recp);
2339 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2340 strcat(ret->display_recp, append);
2345 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2346 ret->num_room + ret->num_error) == 0) {
2348 strcpy(ret->errormsg, "No recipients specified.");
2351 lprintf(9, "validate_recipients()\n");
2352 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2353 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2354 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2355 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2356 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2364 * message entry - mode 0 (normal)
2366 void cmd_ent0(char *entargs)
2370 char masquerade_as[SIZ];
2372 int format_type = 0;
2373 char newusername[SIZ];
2374 struct CtdlMessage *msg;
2378 struct recptypes *valid = NULL;
2381 post = extract_int(entargs, 0);
2382 extract(recp, entargs, 1);
2383 anon_flag = extract_int(entargs, 2);
2384 format_type = extract_int(entargs, 3);
2385 extract(subject, entargs, 4);
2387 /* first check to make sure the request is valid. */
2389 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg);
2391 cprintf("%d %s\n", err, errmsg);
2395 /* Check some other permission type things. */
2398 if (CC->usersupp.axlevel < 6) {
2399 cprintf("%d You don't have permission to masquerade.\n",
2400 ERROR + HIGHER_ACCESS_REQUIRED);
2403 extract(newusername, entargs, 4);
2404 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2405 safestrncpy(CC->fake_postname, newusername,
2406 sizeof(CC->fake_postname) );
2407 cprintf("%d ok\n", OK);
2410 CC->cs_flags |= CS_POSTING;
2412 if (CC->quickroom.QRflags & QR_MAILBOX) {
2413 if (CC->usersupp.axlevel < 2) {
2414 strcpy(recp, "sysop");
2417 valid = validate_recipients(recp);
2418 if (valid->num_error > 0) {
2420 ERROR + NO_SUCH_USER, valid->errormsg);
2425 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2426 && (CC->usersupp.axlevel < 4) ) {
2427 cprintf("%d Higher access required for network mail.\n",
2428 ERROR + HIGHER_ACCESS_REQUIRED);
2433 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2434 && ((CC->usersupp.flags & US_INTERNET) == 0)
2435 && (!CC->internal_pgm)) {
2436 cprintf("%d You don't have access to Internet mail.\n",
2437 ERROR + HIGHER_ACCESS_REQUIRED);
2444 /* Is this a room which has anonymous-only or anonymous-option? */
2445 anonymous = MES_NORMAL;
2446 if (CC->quickroom.QRflags & QR_ANONONLY) {
2447 anonymous = MES_ANONONLY;
2449 if (CC->quickroom.QRflags & QR_ANONOPT) {
2450 if (anon_flag == 1) { /* only if the user requested it */
2451 anonymous = MES_ANONOPT;
2455 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2459 /* If we're only checking the validity of the request, return
2460 * success without creating the message.
2463 cprintf("%d %s\n", OK,
2464 ((valid != NULL) ? valid->display_recp : "") );
2469 /* Handle author masquerading */
2470 if (CC->fake_postname[0]) {
2471 strcpy(masquerade_as, CC->fake_postname);
2473 else if (CC->fake_username[0]) {
2474 strcpy(masquerade_as, CC->fake_username);
2477 strcpy(masquerade_as, "");
2480 /* Read in the message from the client. */
2481 cprintf("%d send message\n", SEND_LISTING);
2482 msg = make_message(&CC->usersupp, recp,
2483 CC->quickroom.QRname, anonymous, format_type,
2484 masquerade_as, subject);
2487 CtdlSubmitMsg(msg, valid, "");
2488 CtdlFreeMessage(msg);
2490 CC->fake_postname[0] = '\0';
2498 * API function to delete messages which match a set of criteria
2499 * (returns the actual number of messages deleted)
2501 int CtdlDeleteMessages(char *room_name, /* which room */
2502 long dmsgnum, /* or "0" for any */
2503 char *content_type /* or "" for any */
2507 struct quickroom qrbuf;
2508 struct cdbdata *cdbfr;
2509 long *msglist = NULL;
2510 long *dellist = NULL;
2513 int num_deleted = 0;
2515 struct MetaData smi;
2517 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2518 room_name, dmsgnum, content_type);
2520 /* get room record, obtaining a lock... */
2521 if (lgetroom(&qrbuf, room_name) != 0) {
2522 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2524 return (0); /* room not found */
2526 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2528 if (cdbfr != NULL) {
2529 msglist = mallok(cdbfr->len);
2530 dellist = mallok(cdbfr->len);
2531 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2532 num_msgs = cdbfr->len / sizeof(long);
2536 for (i = 0; i < num_msgs; ++i) {
2539 /* Set/clear a bit for each criterion */
2541 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2542 delete_this |= 0x01;
2544 if (strlen(content_type) == 0) {
2545 delete_this |= 0x02;
2547 GetMetaData(&smi, msglist[i]);
2548 if (!strcasecmp(smi.meta_content_type,
2550 delete_this |= 0x02;
2554 /* Delete message only if all bits are set */
2555 if (delete_this == 0x03) {
2556 dellist[num_deleted++] = msglist[i];
2561 num_msgs = sort_msglist(msglist, num_msgs);
2562 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2563 msglist, (num_msgs * sizeof(long)));
2565 qrbuf.QRhighest = msglist[num_msgs - 1];
2569 /* Go through the messages we pulled out of the index, and decrement
2570 * their reference counts by 1. If this is the only room the message
2571 * was in, the reference count will reach zero and the message will
2572 * automatically be deleted from the database. We do this in a
2573 * separate pass because there might be plug-in hooks getting called,
2574 * and we don't want that happening during an S_QUICKROOM critical
2577 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2578 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2579 AdjRefCount(dellist[i], -1);
2582 /* Now free the memory we used, and go away. */
2583 if (msglist != NULL) phree(msglist);
2584 if (dellist != NULL) phree(dellist);
2585 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2586 return (num_deleted);
2592 * Check whether the current user has permission to delete messages from
2593 * the current room (returns 1 for yes, 0 for no)
2595 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2596 getuser(&CC->usersupp, CC->curr_user);
2597 if ((CC->usersupp.axlevel < 6)
2598 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2599 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2600 && (!(CC->internal_pgm))) {
2609 * Delete message from current room
2611 void cmd_dele(char *delstr)
2616 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2617 cprintf("%d Higher access required.\n",
2618 ERROR + HIGHER_ACCESS_REQUIRED);
2621 delnum = extract_long(delstr, 0);
2623 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2626 cprintf("%d %d message%s deleted.\n", OK,
2627 num_deleted, ((num_deleted != 1) ? "s" : ""));
2629 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2635 * Back end API function for moves and deletes
2637 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2640 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2641 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2642 if (err != 0) return(err);
2650 * move or copy a message to another room
2652 void cmd_move(char *args)
2656 struct quickroom qtemp;
2660 num = extract_long(args, 0);
2661 extract(targ, args, 1);
2662 targ[ROOMNAMELEN - 1] = 0;
2663 is_copy = extract_int(args, 2);
2665 if (getroom(&qtemp, targ) != 0) {
2666 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2670 getuser(&CC->usersupp, CC->curr_user);
2671 /* Aides can move/copy */
2672 if ((CC->usersupp.axlevel < 6)
2673 /* Roomaides can move/copy */
2674 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2675 /* Permit move/copy to/from personal rooms */
2676 && (!((CC->quickroom.QRflags & QR_MAILBOX)
2677 && (qtemp.QRflags & QR_MAILBOX)))
2678 /* Permit only copy from public to personal room */
2679 && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2680 && (qtemp.QRflags & QR_MAILBOX)))) {
2681 cprintf("%d Higher access required.\n",
2682 ERROR + HIGHER_ACCESS_REQUIRED);
2686 err = CtdlCopyMsgToRoom(num, targ);
2688 cprintf("%d Cannot store message in %s: error %d\n",
2693 /* Now delete the message from the source room,
2694 * if this is a 'move' rather than a 'copy' operation.
2697 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2700 cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") );
2706 * GetMetaData() - Get the supplementary record for a message
2708 void GetMetaData(struct MetaData *smibuf, long msgnum)
2711 struct cdbdata *cdbsmi;
2714 memset(smibuf, 0, sizeof(struct MetaData));
2715 smibuf->meta_msgnum = msgnum;
2716 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2718 /* Use the negative of the message number for its supp record index */
2719 TheIndex = (0L - msgnum);
2721 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2722 if (cdbsmi == NULL) {
2723 return; /* record not found; go with defaults */
2725 memcpy(smibuf, cdbsmi->ptr,
2726 ((cdbsmi->len > sizeof(struct MetaData)) ?
2727 sizeof(struct MetaData) : cdbsmi->len));
2734 * PutMetaData() - (re)write supplementary record for a message
2736 void PutMetaData(struct MetaData *smibuf)
2740 /* Use the negative of the message number for the metadata db index */
2741 TheIndex = (0L - smibuf->meta_msgnum);
2743 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2744 smibuf->meta_msgnum, smibuf->meta_refcount);
2746 cdb_store(CDB_MSGMAIN,
2747 &TheIndex, sizeof(long),
2748 smibuf, sizeof(struct MetaData));
2753 * AdjRefCount - change the reference count for a message;
2754 * delete the message if it reaches zero
2756 void AdjRefCount(long msgnum, int incr)
2759 struct MetaData smi;
2762 /* This is a *tight* critical section; please keep it that way, as
2763 * it may get called while nested in other critical sections.
2764 * Complicating this any further will surely cause deadlock!
2766 begin_critical_section(S_SUPPMSGMAIN);
2767 GetMetaData(&smi, msgnum);
2768 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2769 msgnum, smi.meta_refcount);
2770 smi.meta_refcount += incr;
2772 end_critical_section(S_SUPPMSGMAIN);
2773 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2774 msgnum, smi.meta_refcount);
2776 /* If the reference count is now zero, delete the message
2777 * (and its supplementary record as well).
2779 if (smi.meta_refcount == 0) {
2780 lprintf(9, "Deleting message <%ld>\n", msgnum);
2782 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2784 /* We have to delete the metadata record too! */
2785 delnum = (0L - msgnum);
2786 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2791 * Write a generic object to this room
2793 * Note: this could be much more efficient. Right now we use two temporary
2794 * files, and still pull the message into memory as with all others.
2796 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2797 char *content_type, /* MIME type of this object */
2798 char *tempfilename, /* Where to fetch it from */
2799 struct usersupp *is_mailbox, /* Mailbox room? */
2800 int is_binary, /* Is encoding necessary? */
2801 int is_unique, /* Del others of this type? */
2802 unsigned int flags /* Internal save flags */
2807 char filename[PATH_MAX];
2810 struct quickroom qrbuf;
2811 char roomname[ROOMNAMELEN];
2812 struct CtdlMessage *msg;
2815 if (is_mailbox != NULL)
2816 MailboxName(roomname, is_mailbox, req_room);
2818 safestrncpy(roomname, req_room, sizeof(roomname));
2819 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2821 strcpy(filename, tmpnam(NULL));
2822 fp = fopen(filename, "w");
2826 tempfp = fopen(tempfilename, "r");
2827 if (tempfp == NULL) {
2833 fprintf(fp, "Content-type: %s\n", content_type);
2834 lprintf(9, "Content-type: %s\n", content_type);
2836 if (is_binary == 0) {
2837 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2838 while (ch = getc(tempfp), ch > 0)
2844 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2847 sprintf(cmdbuf, "./base64 -e <%s >>%s",
2848 tempfilename, filename);
2852 lprintf(9, "Allocating\n");
2853 msg = mallok(sizeof(struct CtdlMessage));
2854 memset(msg, 0, sizeof(struct CtdlMessage));
2855 msg->cm_magic = CTDLMESSAGE_MAGIC;
2856 msg->cm_anon_type = MES_NORMAL;
2857 msg->cm_format_type = 4;
2858 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2859 msg->cm_fields['O'] = strdoop(req_room);
2860 msg->cm_fields['N'] = strdoop(config.c_nodename);
2861 msg->cm_fields['H'] = strdoop(config.c_humannode);
2862 msg->cm_flags = flags;
2864 lprintf(9, "Loading\n");
2865 fp = fopen(filename, "rb");
2866 fseek(fp, 0L, SEEK_END);
2869 msg->cm_fields['M'] = mallok(len);
2870 fread(msg->cm_fields['M'], len, 1, fp);
2874 /* Create the requested room if we have to. */
2875 if (getroom(&qrbuf, roomname) != 0) {
2876 create_room(roomname,
2877 ( (is_mailbox != NULL) ? 5 : 3 ),
2880 /* If the caller specified this object as unique, delete all
2881 * other objects of this type that are currently in the room.
2884 lprintf(9, "Deleted %d other msgs of this type\n",
2885 CtdlDeleteMessages(roomname, 0L, content_type));
2887 /* Now write the data */
2888 CtdlSubmitMsg(msg, NULL, roomname);
2889 CtdlFreeMessage(msg);
2897 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2898 config_msgnum = msgnum;
2902 char *CtdlGetSysConfig(char *sysconfname) {
2903 char hold_rm[ROOMNAMELEN];
2906 struct CtdlMessage *msg;
2909 strcpy(hold_rm, CC->quickroom.QRname);
2910 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2911 getroom(&CC->quickroom, hold_rm);
2916 /* We want the last (and probably only) config in this room */
2917 begin_critical_section(S_CONFIG);
2918 config_msgnum = (-1L);
2919 CtdlForEachMessage(MSGS_LAST, 1, (-127), sysconfname, NULL,
2920 CtdlGetSysConfigBackend, NULL);
2921 msgnum = config_msgnum;
2922 end_critical_section(S_CONFIG);
2928 msg = CtdlFetchMessage(msgnum);
2930 conf = strdoop(msg->cm_fields['M']);
2931 CtdlFreeMessage(msg);
2938 getroom(&CC->quickroom, hold_rm);
2940 if (conf != NULL) do {
2941 extract_token(buf, conf, 0, '\n');
2942 strcpy(conf, &conf[strlen(buf)+1]);
2943 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
2948 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
2949 char temp[PATH_MAX];
2952 strcpy(temp, tmpnam(NULL));
2954 fp = fopen(temp, "w");
2955 if (fp == NULL) return;
2956 fprintf(fp, "%s", sysconfdata);
2959 /* this handy API function does all the work for us */
2960 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);