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! */
289 * Retrieve the "seen" message list for the current room.
291 void CtdlGetSeen(char *buf) {
294 /* Learn about the user and room in question */
295 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
297 safestrncpy(buf, vbuf.v_seen, SIZ);
303 * Manipulate the "seen msgs" string.
305 void CtdlSetSeen(long target_msgnum, int target_setting) {
307 struct cdbdata *cdbfr;
317 /* Learn about the user and room in question */
318 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
320 /* Load the message list */
321 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
323 msglist = mallok(cdbfr->len);
324 memcpy(msglist, cdbfr->ptr, cdbfr->len);
325 num_msgs = cdbfr->len / sizeof(long);
328 return; /* No messages at all? No further action. */
331 lprintf(9, "before optimize: %s\n", vbuf.v_seen);
334 for (i=0; i<num_msgs; ++i) {
337 if (msglist[i] == target_msgnum) {
338 is_seen = target_setting;
341 if (is_msg_in_mset(vbuf.v_seen, msglist[i])) {
347 if (lo < 0L) lo = msglist[i];
350 if ( ((is_seen == 0) && (was_seen == 1))
351 || ((is_seen == 1) && (i == num_msgs-1)) ) {
354 if ( (strlen(newseen) + 20) > SIZ) {
355 strcpy(newseen, &newseen[20]);
358 tmp = strlen(newseen);
360 strcat(newseen, ",");
364 snprintf(&newseen[tmp], sizeof newseen - tmp,
368 snprintf(&newseen[tmp], sizeof newseen - tmp,
377 safestrncpy(vbuf.v_seen, newseen, SIZ);
378 lprintf(9, " after optimize: %s\n", vbuf.v_seen);
380 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
385 * API function to perform an operation for each qualifying message in the
386 * current room. (Returns the number of messages processed.)
388 int CtdlForEachMessage(int mode, long ref,
390 struct CtdlMessage *compare,
391 void (*CallBack) (long, void *),
397 struct cdbdata *cdbfr;
398 long *msglist = NULL;
400 int num_processed = 0;
403 struct CtdlMessage *msg;
406 int printed_lastold = 0;
408 /* Learn about the user and room in question */
410 getuser(&CC->usersupp, CC->curr_user);
411 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
413 /* Load the message list */
414 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
416 msglist = mallok(cdbfr->len);
417 memcpy(msglist, cdbfr->ptr, cdbfr->len);
418 num_msgs = cdbfr->len / sizeof(long);
421 return 0; /* No messages at all? No further action. */
426 * Now begin the traversal.
428 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
430 /* If the caller is looking for a specific MIME type, filter
431 * out all messages which are not of the type requested.
433 if (content_type != NULL) if (strlen(content_type) > 0) {
435 /* This call to GetMetaData() sits inside this loop
436 * so that we only do the extra database read per msg
437 * if we need to. Doing the extra read all the time
438 * really kills the server. If we ever need to use
439 * metadata for another search criterion, we need to
440 * move the read somewhere else -- but still be smart
441 * enough to only do the read if the caller has
442 * specified something that will need it.
444 GetMetaData(&smi, msglist[a]);
446 if (strcasecmp(smi.meta_content_type, content_type)) {
452 num_msgs = sort_msglist(msglist, num_msgs);
454 /* If a template was supplied, filter out the messages which
455 * don't match. (This could induce some delays!)
458 if (compare != NULL) {
459 for (a = 0; a < num_msgs; ++a) {
460 msg = CtdlFetchMessage(msglist[a]);
462 if (CtdlMsgCmp(msg, compare)) {
465 CtdlFreeMessage(msg);
473 * Now iterate through the message list, according to the
474 * criteria supplied by the caller.
477 for (a = 0; a < num_msgs; ++a) {
478 thismsg = msglist[a];
479 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
480 if (is_seen) lastold = thismsg;
485 || ((mode == MSGS_OLD) && (is_seen))
486 || ((mode == MSGS_NEW) && (!is_seen))
487 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
488 || ((mode == MSGS_FIRST) && (a < ref))
489 || ((mode == MSGS_GT) && (thismsg > ref))
490 || ((mode == MSGS_EQ) && (thismsg == ref))
493 if ((mode == MSGS_NEW) && (CC->usersupp.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
495 CallBack(lastold, userdata);
499 if (CallBack) CallBack(thismsg, userdata);
503 phree(msglist); /* Clean up */
504 return num_processed;
510 * cmd_msgs() - get list of message #'s in this room
511 * implements the MSGS server command using CtdlForEachMessage()
513 void cmd_msgs(char *cmdbuf)
522 int with_template = 0;
523 struct CtdlMessage *template = NULL;
525 extract(which, cmdbuf, 0);
526 cm_ref = extract_int(cmdbuf, 1);
527 with_template = extract_int(cmdbuf, 2);
531 if (!strncasecmp(which, "OLD", 3))
533 else if (!strncasecmp(which, "NEW", 3))
535 else if (!strncasecmp(which, "FIRST", 5))
537 else if (!strncasecmp(which, "LAST", 4))
539 else if (!strncasecmp(which, "GT", 2))
542 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
543 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
548 cprintf("%d Send template then receive message list\n",
550 template = (struct CtdlMessage *)
551 mallok(sizeof(struct CtdlMessage));
552 memset(template, 0, sizeof(struct CtdlMessage));
553 while(client_gets(buf), strcmp(buf,"000")) {
554 extract(tfield, buf, 0);
555 extract(tvalue, buf, 1);
556 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
557 if (!strcasecmp(tfield, msgkeys[i])) {
558 template->cm_fields[i] =
565 cprintf("%d Message list...\n", LISTING_FOLLOWS);
568 CtdlForEachMessage(mode, cm_ref,
569 NULL, template, simple_listing, NULL);
570 if (template != NULL) CtdlFreeMessage(template);
578 * help_subst() - support routine for help file viewer
580 void help_subst(char *strbuf, char *source, char *dest)
585 while (p = pattern2(strbuf, source), (p >= 0)) {
586 strcpy(workbuf, &strbuf[p + strlen(source)]);
587 strcpy(&strbuf[p], dest);
588 strcat(strbuf, workbuf);
593 void do_help_subst(char *buffer)
597 help_subst(buffer, "^nodename", config.c_nodename);
598 help_subst(buffer, "^humannode", config.c_humannode);
599 help_subst(buffer, "^fqdn", config.c_fqdn);
600 help_subst(buffer, "^username", CC->usersupp.fullname);
601 snprintf(buf2, sizeof buf2, "%ld", CC->usersupp.usernum);
602 help_subst(buffer, "^usernum", buf2);
603 help_subst(buffer, "^sysadm", config.c_sysadm);
604 help_subst(buffer, "^variantname", CITADEL);
605 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
606 help_subst(buffer, "^maxsessions", buf2);
612 * memfmout() - Citadel text formatter and paginator.
613 * Although the original purpose of this routine was to format
614 * text to the reader's screen width, all we're really using it
615 * for here is to format text out to 80 columns before sending it
616 * to the client. The client software may reformat it again.
619 int width, /* screen width to use */
620 char *mptr, /* where are we going to get our text from? */
621 char subst, /* nonzero if we should do substitutions */
622 char *nl) /* string to terminate lines with */
634 c = 1; /* c is the current pos */
638 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
640 buffer[strlen(buffer) + 1] = 0;
641 buffer[strlen(buffer)] = ch;
644 if (buffer[0] == '^')
645 do_help_subst(buffer);
647 buffer[strlen(buffer) + 1] = 0;
649 strcpy(buffer, &buffer[1]);
657 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
659 if (((old == 13) || (old == 10)) && (isspace(real))) {
667 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
668 cprintf("%s%s", nl, aaa);
677 if ((strlen(aaa) + c) > (width - 5)) {
686 if ((ch == 13) || (ch == 10)) {
687 cprintf("%s%s", aaa, nl);
694 cprintf("%s%s", aaa, nl);
700 * Callback function for mime parser that simply lists the part
702 void list_this_part(char *name, char *filename, char *partnum, char *disp,
703 void *content, char *cbtype, size_t length, char *encoding,
707 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
708 name, filename, partnum, disp, cbtype, (long)length);
712 * Callback function for multipart prefix
714 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
715 void *content, char *cbtype, size_t length, char *encoding,
718 cprintf("pref=%s|%s\n", partnum, cbtype);
722 * Callback function for multipart sufffix
724 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
725 void *content, char *cbtype, size_t length, char *encoding,
728 cprintf("suff=%s|%s\n", partnum, cbtype);
733 * Callback function for mime parser that opens a section for downloading
735 void mime_download(char *name, char *filename, char *partnum, char *disp,
736 void *content, char *cbtype, size_t length, char *encoding,
740 /* Silently go away if there's already a download open... */
741 if (CC->download_fp != NULL)
744 /* ...or if this is not the desired section */
745 if (strcasecmp(desired_section, partnum))
748 CC->download_fp = tmpfile();
749 if (CC->download_fp == NULL)
752 fwrite(content, length, 1, CC->download_fp);
753 fflush(CC->download_fp);
754 rewind(CC->download_fp);
756 OpenCmdResult(filename, cbtype);
762 * Load a message from disk into memory.
763 * This is used by CtdlOutputMsg() and other fetch functions.
765 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
766 * using the CtdlMessageFree() function.
768 struct CtdlMessage *CtdlFetchMessage(long msgnum)
770 struct cdbdata *dmsgtext;
771 struct CtdlMessage *ret = NULL;
774 CIT_UBYTE field_header;
777 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
778 if (dmsgtext == NULL) {
781 mptr = dmsgtext->ptr;
783 /* Parse the three bytes that begin EVERY message on disk.
784 * The first is always 0xFF, the on-disk magic number.
785 * The second is the anonymous/public type byte.
786 * The third is the format type byte (vari, fixed, or MIME).
790 lprintf(5, "Message %ld appears to be corrupted.\n", msgnum);
794 ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
795 memset(ret, 0, sizeof(struct CtdlMessage));
797 ret->cm_magic = CTDLMESSAGE_MAGIC;
798 ret->cm_anon_type = *mptr++; /* Anon type byte */
799 ret->cm_format_type = *mptr++; /* Format type byte */
802 * The rest is zero or more arbitrary fields. Load them in.
803 * We're done when we encounter either a zero-length field or
804 * have just processed the 'M' (message text) field.
807 field_length = strlen(mptr);
808 if (field_length == 0)
810 field_header = *mptr++;
811 ret->cm_fields[field_header] = mallok(field_length);
812 strcpy(ret->cm_fields[field_header], mptr);
814 while (*mptr++ != 0); /* advance to next field */
816 } while ((field_length > 0) && (field_header != 'M'));
820 /* Always make sure there's something in the msg text field */
821 if (ret->cm_fields['M'] == NULL)
822 ret->cm_fields['M'] = strdoop("<no text>\n");
824 /* Perform "before read" hooks (aborting if any return nonzero) */
825 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
826 CtdlFreeMessage(ret);
835 * Returns 1 if the supplied pointer points to a valid Citadel message.
836 * If the pointer is NULL or the magic number check fails, returns 0.
838 int is_valid_message(struct CtdlMessage *msg) {
841 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
842 lprintf(3, "is_valid_message() -- self-check failed\n");
850 * 'Destructor' for struct CtdlMessage
852 void CtdlFreeMessage(struct CtdlMessage *msg)
856 if (is_valid_message(msg) == 0) return;
858 for (i = 0; i < 256; ++i)
859 if (msg->cm_fields[i] != NULL) {
860 phree(msg->cm_fields[i]);
863 msg->cm_magic = 0; /* just in case */
869 * Pre callback function for multipart/alternative
871 * NOTE: this differs from the standard behavior for a reason. Normally when
872 * displaying multipart/alternative you want to show the _last_ usable
873 * format in the message. Here we show the _first_ one, because it's
874 * usually text/plain. Since this set of functions is designed for text
875 * output to non-MIME-aware clients, this is the desired behavior.
878 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
879 void *content, char *cbtype, size_t length, char *encoding,
882 lprintf(9, "fixed_output_pre() type=<%s>\n", cbtype);
883 if (!strcasecmp(cbtype, "multipart/alternative")) {
891 * Post callback function for multipart/alternative
893 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
894 void *content, char *cbtype, size_t length, char *encoding,
897 lprintf(9, "fixed_output_post() type=<%s>\n", cbtype);
898 if (!strcasecmp(cbtype, "multipart/alternative")) {
906 * Inline callback function for mime parser that wants to display text
908 void fixed_output(char *name, char *filename, char *partnum, char *disp,
909 void *content, char *cbtype, size_t length, char *encoding,
916 lprintf(9, "fixed_output() type=<%s>\n", cbtype);
919 * If we're in the middle of a multipart/alternative scope and
920 * we've already printed another section, skip this one.
922 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
923 lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype);
928 if ( (!strcasecmp(cbtype, "text/plain"))
929 || (strlen(cbtype)==0) ) {
932 client_write(wptr, length);
933 if (wptr[length-1] != '\n') {
938 else if (!strcasecmp(cbtype, "text/html")) {
939 ptr = html_to_ascii(content, 80, 0);
941 client_write(ptr, wlen);
942 if (ptr[wlen-1] != '\n') {
947 else if (strncasecmp(cbtype, "multipart/", 10)) {
948 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
949 partnum, filename, cbtype, (long)length);
955 * Get a message off disk. (returns om_* values found in msgbase.h)
958 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
959 int mode, /* how would you like that message? */
960 int headers_only, /* eschew the message body? */
961 int do_proto, /* do Citadel protocol responses? */
962 int crlf /* Use CRLF newlines instead of LF? */
964 struct CtdlMessage *TheMessage;
967 lprintf(7, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
972 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
973 if (do_proto) cprintf("%d Not logged in.\n",
974 ERROR + NOT_LOGGED_IN);
975 return(om_not_logged_in);
978 /* FIXME ... small security issue
979 * We need to check to make sure the requested message is actually
980 * in the current room, and set msg_ok to 1 only if it is. This
981 * functionality is currently missing because I'm in a hurry to replace
982 * broken production code with nonbroken pre-beta code. :( -- ajc
985 if (do_proto) cprintf("%d Message %ld is not in this room.\n",
987 return(om_no_such_msg);
992 * Fetch the message from disk. We also keep the most recently
993 * read message in memory, in case we want to read it again, or fetch
994 * MIME parts out of it, or whatever.
996 if ( (CC->cached_msg != NULL) && (CC->cached_msgnum == msg_num) ) {
997 TheMessage = CC->cached_msg;
1000 TheMessage = CtdlFetchMessage(msg_num);
1001 if (CC->cached_msg != NULL) {
1002 phree(CC->cached_msg);
1004 CC->cached_msg = TheMessage;
1005 CC->cached_msgnum = msg_num;
1008 if (TheMessage == NULL) {
1009 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1011 return(om_no_such_msg);
1014 retcode = CtdlOutputPreLoadedMsg(
1015 TheMessage, msg_num, mode,
1016 headers_only, do_proto, crlf);
1018 /* don't free the memory; we're keeping it in the cache */
1019 /* CtdlFreeMessage(TheMessage); */
1026 * Get a message off disk. (returns om_* values found in msgbase.h)
1029 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
1031 int mode, /* how would you like that message? */
1032 int headers_only, /* eschew the message body? */
1033 int do_proto, /* do Citadel protocol responses? */
1034 int crlf /* Use CRLF newlines instead of LF? */
1040 char display_name[SIZ];
1042 char *nl; /* newline string */
1045 /* buffers needed for RFC822 translation */
1052 char datestamp[SIZ];
1055 snprintf(mid, sizeof mid, "%ld", msg_num);
1056 nl = (crlf ? "\r\n" : "\n");
1058 if (!is_valid_message(TheMessage)) {
1059 lprintf(1, "ERROR: invalid preloaded message for output\n");
1060 return(om_no_such_msg);
1063 /* Are we downloading a MIME component? */
1064 if (mode == MT_DOWNLOAD) {
1065 if (TheMessage->cm_format_type != FMT_RFC822) {
1067 cprintf("%d This is not a MIME message.\n",
1069 } else if (CC->download_fp != NULL) {
1070 if (do_proto) cprintf(
1071 "%d You already have a download open.\n",
1074 /* Parse the message text component */
1075 mptr = TheMessage->cm_fields['M'];
1076 mime_parser(mptr, NULL,
1077 *mime_download, NULL, NULL,
1079 /* If there's no file open by this time, the requested
1080 * section wasn't found, so print an error
1082 if (CC->download_fp == NULL) {
1083 if (do_proto) cprintf(
1084 "%d Section %s not found.\n",
1085 ERROR + FILE_NOT_FOUND,
1089 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1092 /* now for the user-mode message reading loops */
1093 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1095 /******** (we don't do this anymore)
1096 * Tell the client which format type we're using. If this is a
1097 * MIME message, *lie* about it and tell the user it's fixed-format.
1098 if (mode == MT_CITADEL) {
1099 if (TheMessage->cm_format_type == FMT_RFC822) {
1100 if (do_proto) cprintf("type=1\n");
1103 if (do_proto) cprintf("type=%d\n",
1104 TheMessage->cm_format_type);
1109 /* Tell the client the truth about which format type we're using. */
1110 if ( (mode == MT_CITADEL) && (do_proto) ) {
1111 cprintf("type=%d\n", TheMessage->cm_format_type);
1114 /* nhdr=yes means that we're only displaying headers, no body */
1115 if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1116 if (do_proto) cprintf("nhdr=yes\n");
1119 /* begin header processing loop for Citadel message format */
1121 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1123 strcpy(display_name, "<unknown>");
1124 if (TheMessage->cm_fields['A']) {
1125 strcpy(buf, TheMessage->cm_fields['A']);
1126 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1127 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1128 strcpy(display_name, "****");
1130 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1131 strcpy(display_name, "anonymous");
1134 strcpy(display_name, buf);
1136 if ((is_room_aide())
1137 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1138 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1139 size_t tmp = strlen(display_name);
1140 snprintf(&display_name[tmp],
1141 sizeof display_name - tmp,
1146 /* Don't show Internet address for users on the
1147 * local Citadel network.
1150 if (TheMessage->cm_fields['N'] != NULL)
1151 if (strlen(TheMessage->cm_fields['N']) > 0)
1152 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1156 /* Now spew the header fields in the order we like them. */
1157 strcpy(allkeys, FORDER);
1158 for (i=0; i<strlen(allkeys); ++i) {
1159 k = (int) allkeys[i];
1161 if ( (TheMessage->cm_fields[k] != NULL)
1162 && (msgkeys[k] != NULL) ) {
1164 if (do_proto) cprintf("%s=%s\n",
1168 else if ((k == 'F') && (suppress_f)) {
1171 /* Masquerade display name if needed */
1173 if (do_proto) cprintf("%s=%s\n",
1175 TheMessage->cm_fields[k]
1184 /* begin header processing loop for RFC822 transfer format */
1189 strcpy(snode, NODENAME);
1190 strcpy(lnode, HUMANNODE);
1191 if (mode == MT_RFC822) {
1192 cprintf("X-UIDL: %ld%s", msg_num, nl);
1193 for (i = 0; i < 256; ++i) {
1194 if (TheMessage->cm_fields[i]) {
1195 mptr = TheMessage->cm_fields[i];
1198 strcpy(luser, mptr);
1199 strcpy(suser, mptr);
1202 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1203 into thinking that mail messages are newsgroup messages instead. When we
1204 add NNTP support back into Citadel we'll have to add code to only output
1205 this field when appropriate.
1206 else if (i == 'P') {
1207 cprintf("Path: %s%s", mptr, nl);
1211 cprintf("Subject: %s%s", mptr, nl);
1213 safestrncpy(mid, mptr, sizeof mid);
1215 safestrncpy(lnode, mptr, sizeof lnode);
1217 safestrncpy(fuser, mptr, sizeof fuser);
1219 cprintf("X-Citadel-Room: %s%s",
1222 safestrncpy(snode, mptr, sizeof snode);
1224 cprintf("To: %s%s", mptr, nl);
1225 else if (i == 'T') {
1226 datestring(datestamp, sizeof datestamp, atol(mptr),
1227 DATESTRING_RFC822 );
1228 cprintf("Date: %s%s", datestamp, nl);
1234 for (i=0; i<strlen(suser); ++i) {
1235 suser[i] = tolower(suser[i]);
1236 if (!isalnum(suser[i])) suser[i]='_';
1239 if (mode == MT_RFC822) {
1240 if (!strcasecmp(snode, NODENAME)) {
1241 strcpy(snode, FQDN);
1244 /* Construct a fun message id */
1245 cprintf("Message-ID: <%s", mid);
1246 if (strchr(mid, '@')==NULL) {
1247 cprintf("@%s", snode);
1251 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1253 if (strlen(fuser) > 0) {
1254 cprintf("From: %s (%s)%s", fuser, luser, nl);
1257 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1260 cprintf("Organization: %s%s", lnode, nl);
1263 /* end header processing loop ... at this point, we're in the text */
1265 mptr = TheMessage->cm_fields['M'];
1267 /* Tell the client about the MIME parts in this message */
1268 if (TheMessage->cm_format_type == FMT_RFC822) {
1269 if (mode == MT_CITADEL) {
1270 mime_parser(mptr, NULL,
1276 else if (mode == MT_MIME) { /* list parts only */
1277 mime_parser(mptr, NULL,
1282 if (do_proto) cprintf("000\n");
1285 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1286 /* FIXME ... we have to put some code in here to avoid
1287 * printing duplicate header information when both
1288 * Citadel and RFC822 headers exist. Preference should
1289 * probably be given to the RFC822 headers.
1291 while (ch=*(mptr++), ch!=0) {
1293 else if (ch==10) cprintf("%s", nl);
1294 else cprintf("%c", ch);
1296 if (do_proto) cprintf("000\n");
1302 if (do_proto) cprintf("000\n");
1306 /* signify start of msg text */
1307 if (mode == MT_CITADEL)
1308 if (do_proto) cprintf("text\n");
1309 if (mode == MT_RFC822) {
1310 if (TheMessage->cm_fields['U'] == NULL) {
1311 cprintf("Subject: (no subject)%s", nl);
1316 /* If the format type on disk is 1 (fixed-format), then we want
1317 * everything to be output completely literally ... regardless of
1318 * what message transfer format is in use.
1320 if (TheMessage->cm_format_type == FMT_FIXED) {
1322 while (ch = *mptr++, ch > 0) {
1325 if ((ch == 10) || (strlen(buf) > 250)) {
1326 cprintf("%s%s", buf, nl);
1329 buf[strlen(buf) + 1] = 0;
1330 buf[strlen(buf)] = ch;
1333 if (strlen(buf) > 0)
1334 cprintf("%s%s", buf, nl);
1337 /* If the message on disk is format 0 (Citadel vari-format), we
1338 * output using the formatter at 80 columns. This is the final output
1339 * form if the transfer format is RFC822, but if the transfer format
1340 * is Citadel proprietary, it'll still work, because the indentation
1341 * for new paragraphs is correct and the client will reformat the
1342 * message to the reader's screen width.
1344 if (TheMessage->cm_format_type == FMT_CITADEL) {
1345 memfmout(80, mptr, 0, nl);
1348 /* If the message on disk is format 4 (MIME), we've gotta hand it
1349 * off to the MIME parser. The client has already been told that
1350 * this message is format 1 (fixed format), so the callback function
1351 * we use will display those parts as-is.
1353 if (TheMessage->cm_format_type == FMT_RFC822) {
1354 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1355 memset(ma, 0, sizeof(struct ma_info));
1356 mime_parser(mptr, NULL,
1357 *fixed_output, *fixed_output_pre, *fixed_output_post,
1361 /* now we're done */
1362 if (do_proto) cprintf("000\n");
1369 * display a message (mode 0 - Citadel proprietary)
1371 void cmd_msg0(char *cmdbuf)
1374 int headers_only = 0;
1376 msgid = extract_long(cmdbuf, 0);
1377 headers_only = extract_int(cmdbuf, 1);
1379 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1385 * display a message (mode 2 - RFC822)
1387 void cmd_msg2(char *cmdbuf)
1390 int headers_only = 0;
1392 msgid = extract_long(cmdbuf, 0);
1393 headers_only = extract_int(cmdbuf, 1);
1395 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1401 * display a message (mode 3 - IGnet raw format - internal programs only)
1403 void cmd_msg3(char *cmdbuf)
1406 struct CtdlMessage *msg;
1409 if (CC->internal_pgm == 0) {
1410 cprintf("%d This command is for internal programs only.\n",
1415 msgnum = extract_long(cmdbuf, 0);
1416 msg = CtdlFetchMessage(msgnum);
1418 cprintf("%d Message %ld not found.\n",
1423 serialize_message(&smr, msg);
1424 CtdlFreeMessage(msg);
1427 cprintf("%d Unable to serialize message\n",
1428 ERROR+INTERNAL_ERROR);
1432 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1433 client_write(smr.ser, smr.len);
1440 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1442 void cmd_msg4(char *cmdbuf)
1446 msgid = extract_long(cmdbuf, 0);
1447 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1451 * Open a component of a MIME message as a download file
1453 void cmd_opna(char *cmdbuf)
1457 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1459 msgid = extract_long(cmdbuf, 0);
1460 extract(desired_section, cmdbuf, 1);
1462 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1467 * Save a message pointer into a specified room
1468 * (Returns 0 for success, nonzero for failure)
1469 * roomname may be NULL to use the current room
1471 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1473 char hold_rm[ROOMNAMELEN];
1474 struct cdbdata *cdbfr;
1477 long highest_msg = 0L;
1478 struct CtdlMessage *msg = NULL;
1480 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1481 roomname, msgid, flags);
1483 strcpy(hold_rm, CC->quickroom.QRname);
1485 /* We may need to check to see if this message is real */
1486 if ( (flags & SM_VERIFY_GOODNESS)
1487 || (flags & SM_DO_REPL_CHECK)
1489 msg = CtdlFetchMessage(msgid);
1490 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1493 /* Perform replication checks if necessary */
1494 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1496 if (getroom(&CC->quickroom,
1497 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1499 lprintf(9, "No such room <%s>\n", roomname);
1500 if (msg != NULL) CtdlFreeMessage(msg);
1501 return(ERROR + ROOM_NOT_FOUND);
1504 if (ReplicationChecks(msg) != 0) {
1505 getroom(&CC->quickroom, hold_rm);
1506 if (msg != NULL) CtdlFreeMessage(msg);
1507 lprintf(9, "Did replication, and newer exists\n");
1512 /* Now the regular stuff */
1513 if (lgetroom(&CC->quickroom,
1514 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1516 lprintf(9, "No such room <%s>\n", roomname);
1517 if (msg != NULL) CtdlFreeMessage(msg);
1518 return(ERROR + ROOM_NOT_FOUND);
1521 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1522 if (cdbfr == NULL) {
1526 msglist = mallok(cdbfr->len);
1527 if (msglist == NULL)
1528 lprintf(3, "ERROR malloc msglist!\n");
1529 num_msgs = cdbfr->len / sizeof(long);
1530 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1535 /* Make sure the message doesn't already exist in this room. It
1536 * is absolutely taboo to have more than one reference to the same
1537 * message in a room.
1539 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1540 if (msglist[i] == msgid) {
1541 lputroom(&CC->quickroom); /* unlock the room */
1542 getroom(&CC->quickroom, hold_rm);
1543 if (msg != NULL) CtdlFreeMessage(msg);
1544 return(ERROR + ALREADY_EXISTS);
1548 /* Now add the new message */
1550 msglist = reallok(msglist,
1551 (num_msgs * sizeof(long)));
1553 if (msglist == NULL) {
1554 lprintf(3, "ERROR: can't realloc message list!\n");
1556 msglist[num_msgs - 1] = msgid;
1558 /* Sort the message list, so all the msgid's are in order */
1559 num_msgs = sort_msglist(msglist, num_msgs);
1561 /* Determine the highest message number */
1562 highest_msg = msglist[num_msgs - 1];
1564 /* Write it back to disk. */
1565 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1566 msglist, num_msgs * sizeof(long));
1568 /* Free up the memory we used. */
1571 /* Update the highest-message pointer and unlock the room. */
1572 CC->quickroom.QRhighest = highest_msg;
1573 lputroom(&CC->quickroom);
1574 getroom(&CC->quickroom, hold_rm);
1576 /* Bump the reference count for this message. */
1577 if ((flags & SM_DONT_BUMP_REF)==0) {
1578 AdjRefCount(msgid, +1);
1581 /* Return success. */
1582 if (msg != NULL) CtdlFreeMessage(msg);
1589 * Message base operation to send a message to the master file
1590 * (returns new message number)
1592 * This is the back end for CtdlSubmitMsg() and should not be directly
1593 * called by server-side modules.
1596 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1597 FILE *save_a_copy) /* save a copy to disk? */
1604 /* Get a new message number */
1605 newmsgid = get_new_message_number();
1606 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1608 /* Generate an ID if we don't have one already */
1609 if (msg->cm_fields['I']==NULL) {
1610 msg->cm_fields['I'] = strdoop(msgidbuf);
1613 serialize_message(&smr, msg);
1616 cprintf("%d Unable to serialize message\n",
1617 ERROR+INTERNAL_ERROR);
1621 /* Write our little bundle of joy into the message base */
1622 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1623 smr.ser, smr.len) < 0) {
1624 lprintf(2, "Can't store message\n");
1630 /* If the caller specified that a copy should be saved to a particular
1631 * file handle, do that now too.
1633 if (save_a_copy != NULL) {
1634 fwrite(smr.ser, smr.len, 1, save_a_copy);
1637 /* Free the memory we used for the serialized message */
1640 /* Return the *local* message ID to the caller
1641 * (even if we're storing an incoming network message)
1649 * Serialize a struct CtdlMessage into the format used on disk and network.
1651 * This function loads up a "struct ser_ret" (defined in server.h) which
1652 * contains the length of the serialized message and a pointer to the
1653 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1655 void serialize_message(struct ser_ret *ret, /* return values */
1656 struct CtdlMessage *msg) /* unserialized msg */
1660 static char *forder = FORDER;
1662 if (is_valid_message(msg) == 0) return; /* self check */
1665 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1666 ret->len = ret->len +
1667 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1669 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1670 ret->ser = mallok(ret->len);
1671 if (ret->ser == NULL) {
1677 ret->ser[1] = msg->cm_anon_type;
1678 ret->ser[2] = msg->cm_format_type;
1681 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1682 ret->ser[wlen++] = (char)forder[i];
1683 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1684 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1686 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1687 (long)ret->len, (long)wlen);
1695 * Back end for the ReplicationChecks() function
1697 void check_repl(long msgnum, void *userdata) {
1698 struct CtdlMessage *msg;
1699 time_t timestamp = (-1L);
1701 lprintf(9, "check_repl() found message %ld\n", msgnum);
1702 msg = CtdlFetchMessage(msgnum);
1703 if (msg == NULL) return;
1704 if (msg->cm_fields['T'] != NULL) {
1705 timestamp = atol(msg->cm_fields['T']);
1707 CtdlFreeMessage(msg);
1709 if (timestamp > msg_repl->highest) {
1710 msg_repl->highest = timestamp; /* newer! */
1711 lprintf(9, "newer!\n");
1714 lprintf(9, "older!\n");
1716 /* Existing isn't newer? Then delete the old one(s). */
1717 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1722 * Check to see if any messages already exist which carry the same Extended ID
1726 * -> With older timestamps: delete them and return 0. Message will be saved.
1727 * -> With newer timestamps: return 1. Message save will be aborted.
1729 int ReplicationChecks(struct CtdlMessage *msg) {
1730 struct CtdlMessage *template;
1733 lprintf(9, "ReplicationChecks() started\n");
1734 /* No extended id? Don't do anything. */
1735 if (msg->cm_fields['E'] == NULL) return 0;
1736 if (strlen(msg->cm_fields['E']) == 0) return 0;
1737 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1739 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1740 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1741 msg_repl->highest = atol(msg->cm_fields['T']);
1743 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1744 memset(template, 0, sizeof(struct CtdlMessage));
1745 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1747 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1749 /* If a newer message exists with the same Extended ID, abort
1752 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1756 CtdlFreeMessage(template);
1757 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1765 * Save a message to disk and submit it into the delivery system.
1767 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1768 struct recptypes *recps, /* recipients (if mail) */
1769 char *force /* force a particular room? */
1772 char hold_rm[ROOMNAMELEN];
1773 char actual_rm[ROOMNAMELEN];
1774 char force_room[ROOMNAMELEN];
1775 char content_type[SIZ]; /* We have to learn this */
1776 char recipient[SIZ];
1779 struct usersupp userbuf;
1781 struct MetaData smi;
1782 FILE *network_fp = NULL;
1783 static int seqnum = 1;
1784 struct CtdlMessage *imsg = NULL;
1787 char *hold_R, *hold_D;
1789 lprintf(9, "CtdlSubmitMsg() called\n");
1790 if (is_valid_message(msg) == 0) return(-1); /* self check */
1792 /* If this message has no timestamp, we take the liberty of
1793 * giving it one, right now.
1795 if (msg->cm_fields['T'] == NULL) {
1796 lprintf(9, "Generating timestamp\n");
1797 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1798 msg->cm_fields['T'] = strdoop(aaa);
1801 /* If this message has no path, we generate one.
1803 if (msg->cm_fields['P'] == NULL) {
1804 lprintf(9, "Generating path\n");
1805 if (msg->cm_fields['A'] != NULL) {
1806 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1807 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1808 if (isspace(msg->cm_fields['P'][a])) {
1809 msg->cm_fields['P'][a] = ' ';
1814 msg->cm_fields['P'] = strdoop("unknown");
1818 strcpy(force_room, force);
1820 /* Learn about what's inside, because it's what's inside that counts */
1821 lprintf(9, "Learning what's inside\n");
1822 if (msg->cm_fields['M'] == NULL) {
1823 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1826 switch (msg->cm_format_type) {
1828 strcpy(content_type, "text/x-citadel-variformat");
1831 strcpy(content_type, "text/plain");
1834 strcpy(content_type, "text/plain");
1835 /* advance past header fields */
1836 mptr = msg->cm_fields['M'];
1839 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1840 safestrncpy(content_type, mptr,
1841 sizeof(content_type));
1842 strcpy(content_type, &content_type[14]);
1843 for (a = 0; a < strlen(content_type); ++a)
1844 if ((content_type[a] == ';')
1845 || (content_type[a] == ' ')
1846 || (content_type[a] == 13)
1847 || (content_type[a] == 10))
1848 content_type[a] = 0;
1855 /* Goto the correct room */
1856 lprintf(9, "Switching rooms\n");
1857 strcpy(hold_rm, CC->quickroom.QRname);
1858 strcpy(actual_rm, CC->quickroom.QRname);
1859 if (recps != NULL) {
1860 strcpy(actual_rm, SENTITEMS);
1863 /* If the user is a twit, move to the twit room for posting */
1864 lprintf(9, "Handling twit stuff\n");
1866 if (CC->usersupp.axlevel == 2) {
1867 strcpy(hold_rm, actual_rm);
1868 strcpy(actual_rm, config.c_twitroom);
1872 /* ...or if this message is destined for Aide> then go there. */
1873 if (strlen(force_room) > 0) {
1874 strcpy(actual_rm, force_room);
1877 lprintf(9, "Possibly relocating\n");
1878 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1879 getroom(&CC->quickroom, actual_rm);
1883 * If this message has no O (room) field, generate one.
1885 if (msg->cm_fields['O'] == NULL) {
1886 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1889 /* Perform "before save" hooks (aborting if any return nonzero) */
1890 lprintf(9, "Performing before-save hooks\n");
1891 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1893 /* If this message has an Extended ID, perform replication checks */
1894 lprintf(9, "Performing replication checks\n");
1895 if (ReplicationChecks(msg) > 0) return(-1);
1897 /* Save it to disk */
1898 lprintf(9, "Saving to disk\n");
1899 newmsgid = send_message(msg, NULL);
1900 if (newmsgid <= 0L) return(-1);
1902 /* Write a supplemental message info record. This doesn't have to
1903 * be a critical section because nobody else knows about this message
1906 lprintf(9, "Creating MetaData record\n");
1907 memset(&smi, 0, sizeof(struct MetaData));
1908 smi.meta_msgnum = newmsgid;
1909 smi.meta_refcount = 0;
1910 safestrncpy(smi.meta_content_type, content_type, 64);
1913 /* Now figure out where to store the pointers */
1914 lprintf(9, "Storing pointers\n");
1916 /* If this is being done by the networker delivering a private
1917 * message, we want to BYPASS saving the sender's copy (because there
1918 * is no local sender; it would otherwise go to the Trashcan).
1920 if ((!CC->internal_pgm) || (recps == NULL)) {
1921 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1922 lprintf(3, "ERROR saving message pointer!\n");
1923 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
1927 /* For internet mail, drop a copy in the outbound queue room */
1929 if (recps->num_internet > 0) {
1930 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1933 /* If other rooms are specified, drop them there too. */
1935 if (recps->num_room > 0)
1936 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1937 extract(recipient, recps->recp_room, i);
1938 lprintf(9, "Delivering to local room <%s>\n", recipient);
1939 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1942 /* Bump this user's messages posted counter. */
1943 lprintf(9, "Updating user\n");
1944 lgetuser(&CC->usersupp, CC->curr_user);
1945 CC->usersupp.posted = CC->usersupp.posted + 1;
1946 lputuser(&CC->usersupp);
1948 /* If this is private, local mail, make a copy in the
1949 * recipient's mailbox and bump the reference count.
1952 if (recps->num_local > 0)
1953 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1954 extract(recipient, recps->recp_local, i);
1955 lprintf(9, "Delivering private local mail to <%s>\n",
1957 if (getuser(&userbuf, recipient) == 0) {
1958 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
1959 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1960 BumpNewMailCounter(userbuf.usernum);
1963 lprintf(9, "No user <%s>\n", recipient);
1964 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
1968 /* Perform "after save" hooks */
1969 lprintf(9, "Performing after-save hooks\n");
1970 PerformMessageHooks(msg, EVT_AFTERSAVE);
1972 /* For IGnet mail, we have to save a new copy into the spooler for
1973 * each recipient, with the R and D fields set to the recipient and
1974 * destination-node. This has two ugly side effects: all other
1975 * recipients end up being unlisted in this recipient's copy of the
1976 * message, and it has to deliver multiple messages to the same
1977 * node. We'll revisit this again in a year or so when everyone has
1978 * a network spool receiver that can handle the new style messages.
1981 if (recps->num_ignet > 0)
1982 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1983 extract(recipient, recps->recp_ignet, i);
1985 hold_R = msg->cm_fields['R'];
1986 hold_D = msg->cm_fields['D'];
1987 msg->cm_fields['R'] = mallok(SIZ);
1988 msg->cm_fields['D'] = mallok(SIZ);
1989 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1990 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1992 serialize_message(&smr, msg);
1994 snprintf(aaa, sizeof aaa,
1995 "./network/spoolin/netmail.%04lx.%04x.%04x",
1996 (long) getpid(), CC->cs_pid, ++seqnum);
1997 network_fp = fopen(aaa, "wb+");
1998 if (network_fp != NULL) {
1999 fwrite(smr.ser, smr.len, 1, network_fp);
2005 phree(msg->cm_fields['R']);
2006 phree(msg->cm_fields['D']);
2007 msg->cm_fields['R'] = hold_R;
2008 msg->cm_fields['D'] = hold_D;
2011 /* Go back to the room we started from */
2012 lprintf(9, "Returning to original room\n");
2013 if (strcasecmp(hold_rm, CC->quickroom.QRname))
2014 getroom(&CC->quickroom, hold_rm);
2016 /* For internet mail, generate delivery instructions.
2017 * Yes, this is recursive. Deal with it. Infinite recursion does
2018 * not happen because the delivery instructions message does not
2019 * contain a recipient.
2022 if (recps->num_internet > 0) {
2023 lprintf(9, "Generating delivery instructions\n");
2024 instr = mallok(SIZ * 2);
2025 snprintf(instr, SIZ * 2,
2026 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2028 SPOOLMIME, newmsgid, (long)time(NULL),
2029 msg->cm_fields['A'], msg->cm_fields['N']
2032 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2033 size_t tmp = strlen(instr);
2034 extract(recipient, recps->recp_internet, i);
2035 snprintf(&instr[tmp], SIZ * 2 - tmp,
2036 "remote|%s|0||\n", recipient);
2039 imsg = mallok(sizeof(struct CtdlMessage));
2040 memset(imsg, 0, sizeof(struct CtdlMessage));
2041 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2042 imsg->cm_anon_type = MES_NORMAL;
2043 imsg->cm_format_type = FMT_RFC822;
2044 imsg->cm_fields['A'] = strdoop("Citadel");
2045 imsg->cm_fields['M'] = instr;
2046 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2047 CtdlFreeMessage(imsg);
2056 * Convenience function for generating small administrative messages.
2058 void quickie_message(char *from, char *to, char *room, char *text)
2060 struct CtdlMessage *msg;
2062 msg = mallok(sizeof(struct CtdlMessage));
2063 memset(msg, 0, sizeof(struct CtdlMessage));
2064 msg->cm_magic = CTDLMESSAGE_MAGIC;
2065 msg->cm_anon_type = MES_NORMAL;
2066 msg->cm_format_type = 0;
2067 msg->cm_fields['A'] = strdoop(from);
2068 msg->cm_fields['O'] = strdoop(room);
2069 msg->cm_fields['N'] = strdoop(NODENAME);
2071 msg->cm_fields['R'] = strdoop(to);
2072 msg->cm_fields['M'] = strdoop(text);
2074 CtdlSubmitMsg(msg, NULL, room);
2075 CtdlFreeMessage(msg);
2076 syslog(LOG_NOTICE, text);
2082 * Back end function used by make_message() and similar functions
2084 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2085 size_t maxlen, /* maximum message length */
2086 char *exist /* if non-null, append to it;
2087 exist is ALWAYS freed */
2091 size_t message_len = 0;
2092 size_t buffer_len = 0;
2096 if (exist == NULL) {
2103 message_len = strlen(exist);
2104 buffer_len = message_len + 4096;
2105 m = reallok(exist, buffer_len);
2112 /* flush the input if we have nowhere to store it */
2114 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2118 /* read in the lines of message text one by one */
2119 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2121 /* strip trailing newline type stuff */
2122 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2123 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2125 linelen = strlen(buf);
2127 /* augment the buffer if we have to */
2128 if ((message_len + linelen + 2) > buffer_len) {
2129 lprintf(9, "realloking\n");
2130 ptr = reallok(m, (buffer_len * 2) );
2131 if (ptr == NULL) { /* flush if can't allocate */
2132 while ( (client_gets(buf)>0) &&
2133 strcmp(buf, terminator)) ;;
2136 buffer_len = (buffer_len * 2);
2138 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2142 /* Add the new line to the buffer. NOTE: this loop must avoid
2143 * using functions like strcat() and strlen() because they
2144 * traverse the entire buffer upon every call, and doing that
2145 * for a multi-megabyte message slows it down beyond usability.
2147 strcpy(&m[message_len], buf);
2148 m[message_len + linelen] = '\n';
2149 m[message_len + linelen + 1] = 0;
2150 message_len = message_len + linelen + 1;
2152 /* if we've hit the max msg length, flush the rest */
2153 if (message_len >= maxlen) {
2154 while ( (client_gets(buf)>0)
2155 && strcmp(buf, terminator)) ;;
2166 * Build a binary message to be saved on disk.
2169 static struct CtdlMessage *make_message(
2170 struct usersupp *author, /* author's usersupp structure */
2171 char *recipient, /* NULL if it's not mail */
2172 char *room, /* room where it's going */
2173 int type, /* see MES_ types in header file */
2174 int format_type, /* variformat, plain text, MIME... */
2175 char *fake_name, /* who we're masquerading as */
2176 char *subject /* Subject (optional) */
2178 char dest_node[SIZ];
2180 struct CtdlMessage *msg;
2182 msg = mallok(sizeof(struct CtdlMessage));
2183 memset(msg, 0, sizeof(struct CtdlMessage));
2184 msg->cm_magic = CTDLMESSAGE_MAGIC;
2185 msg->cm_anon_type = type;
2186 msg->cm_format_type = format_type;
2188 /* Don't confuse the poor folks if it's not routed mail. */
2189 strcpy(dest_node, "");
2193 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2194 msg->cm_fields['P'] = strdoop(buf);
2196 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2197 msg->cm_fields['T'] = strdoop(buf);
2199 if (fake_name[0]) /* author */
2200 msg->cm_fields['A'] = strdoop(fake_name);
2202 msg->cm_fields['A'] = strdoop(author->fullname);
2204 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2205 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2208 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2211 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2212 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2214 if (recipient[0] != 0) {
2215 msg->cm_fields['R'] = strdoop(recipient);
2217 if (dest_node[0] != 0) {
2218 msg->cm_fields['D'] = strdoop(dest_node);
2221 if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
2222 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2225 if (subject != NULL) {
2227 if (strlen(subject) > 0) {
2228 msg->cm_fields['U'] = strdoop(subject);
2232 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2233 config.c_maxmsglen, NULL);
2240 * Check to see whether we have permission to post a message in the current
2241 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2242 * returns 0 on success.
2244 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2246 if (!(CC->logged_in)) {
2247 snprintf(errmsgbuf, n, "Not logged in.");
2248 return (ERROR + NOT_LOGGED_IN);
2251 if ((CC->usersupp.axlevel < 2)
2252 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2253 snprintf(errmsgbuf, n, "Need to be validated to enter "
2254 "(except in %s> to sysop)", MAILROOM);
2255 return (ERROR + HIGHER_ACCESS_REQUIRED);
2258 if ((CC->usersupp.axlevel < 4)
2259 && (CC->quickroom.QRflags & QR_NETWORK)) {
2260 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2261 return (ERROR + HIGHER_ACCESS_REQUIRED);
2264 if ((CC->usersupp.axlevel < 6)
2265 && (CC->quickroom.QRflags & QR_READONLY)) {
2266 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2267 return (ERROR + HIGHER_ACCESS_REQUIRED);
2270 strcpy(errmsgbuf, "Ok");
2276 * Validate recipients, count delivery types and errors, and handle aliasing
2277 * FIXME check for dupes!!!!!
2279 struct recptypes *validate_recipients(char *recipients) {
2280 struct recptypes *ret;
2281 char this_recp[SIZ];
2282 char this_recp_cooked[SIZ];
2288 struct usersupp tempUS;
2289 struct quickroom tempQR;
2292 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2293 if (ret == NULL) return(NULL);
2294 memset(ret, 0, sizeof(struct recptypes));
2297 ret->num_internet = 0;
2302 if (recipients == NULL) {
2305 else if (strlen(recipients) == 0) {
2309 /* Change all valid separator characters to commas */
2310 for (i=0; i<strlen(recipients); ++i) {
2311 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2312 recipients[i] = ',';
2317 num_recps = num_tokens(recipients, ',');
2320 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2321 extract_token(this_recp, recipients, i, ',');
2323 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2324 mailtype = alias(this_recp);
2325 mailtype = alias(this_recp);
2326 mailtype = alias(this_recp);
2327 for (j=0; j<=strlen(this_recp); ++j) {
2328 if (this_recp[j]=='_') {
2329 this_recp_cooked[j] = ' ';
2332 this_recp_cooked[j] = this_recp[j];
2338 if (!strcasecmp(this_recp, "sysop")) {
2340 strcpy(this_recp, config.c_aideroom);
2341 if (strlen(ret->recp_room) > 0) {
2342 strcat(ret->recp_room, "|");
2344 strcat(ret->recp_room, this_recp);
2346 else if (getuser(&tempUS, this_recp) == 0) {
2348 strcpy(this_recp, tempUS.fullname);
2349 if (strlen(ret->recp_local) > 0) {
2350 strcat(ret->recp_local, "|");
2352 strcat(ret->recp_local, this_recp);
2354 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2356 strcpy(this_recp, tempUS.fullname);
2357 if (strlen(ret->recp_local) > 0) {
2358 strcat(ret->recp_local, "|");
2360 strcat(ret->recp_local, this_recp);
2362 else if ( (!strncasecmp(this_recp, "room_", 5))
2363 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2365 if (strlen(ret->recp_room) > 0) {
2366 strcat(ret->recp_room, "|");
2368 strcat(ret->recp_room, &this_recp_cooked[5]);
2376 ++ret->num_internet;
2377 if (strlen(ret->recp_internet) > 0) {
2378 strcat(ret->recp_internet, "|");
2380 strcat(ret->recp_internet, this_recp);
2384 if (strlen(ret->recp_ignet) > 0) {
2385 strcat(ret->recp_ignet, "|");
2387 strcat(ret->recp_ignet, this_recp);
2395 if (strlen(ret->errormsg) == 0) {
2396 snprintf(append, sizeof append,
2397 "Invalid recipient: %s",
2401 snprintf(append, sizeof append,
2404 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2405 strcat(ret->errormsg, append);
2409 if (strlen(ret->display_recp) == 0) {
2410 strcpy(append, this_recp);
2413 snprintf(append, sizeof append, ", %s",
2416 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2417 strcat(ret->display_recp, append);
2422 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2423 ret->num_room + ret->num_error) == 0) {
2425 strcpy(ret->errormsg, "No recipients specified.");
2428 lprintf(9, "validate_recipients()\n");
2429 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2430 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2431 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2432 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2433 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2441 * message entry - mode 0 (normal)
2443 void cmd_ent0(char *entargs)
2447 char masquerade_as[SIZ];
2449 int format_type = 0;
2450 char newusername[SIZ];
2451 struct CtdlMessage *msg;
2455 struct recptypes *valid = NULL;
2458 post = extract_int(entargs, 0);
2459 extract(recp, entargs, 1);
2460 anon_flag = extract_int(entargs, 2);
2461 format_type = extract_int(entargs, 3);
2462 extract(subject, entargs, 4);
2464 /* first check to make sure the request is valid. */
2466 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2468 cprintf("%d %s\n", err, errmsg);
2472 /* Check some other permission type things. */
2475 if (CC->usersupp.axlevel < 6) {
2476 cprintf("%d You don't have permission to masquerade.\n",
2477 ERROR + HIGHER_ACCESS_REQUIRED);
2480 extract(newusername, entargs, 4);
2481 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2482 safestrncpy(CC->fake_postname, newusername,
2483 sizeof(CC->fake_postname) );
2484 cprintf("%d ok\n", CIT_OK);
2487 CC->cs_flags |= CS_POSTING;
2489 /* In the Mail> room we have to behave a little differently --
2490 * make sure the user has specified at least one recipient. Then
2491 * validate the recipient(s).
2493 if ( (CC->quickroom.QRflags & QR_MAILBOX)
2494 && (!strcasecmp(&CC->quickroom.QRname[11], MAILROOM)) ) {
2496 if (CC->usersupp.axlevel < 2) {
2497 strcpy(recp, "sysop");
2500 valid = validate_recipients(recp);
2501 if (valid->num_error > 0) {
2503 ERROR + NO_SUCH_USER, valid->errormsg);
2508 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2509 && (CC->usersupp.axlevel < 4) ) {
2510 cprintf("%d Higher access required for network mail.\n",
2511 ERROR + HIGHER_ACCESS_REQUIRED);
2516 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2517 && ((CC->usersupp.flags & US_INTERNET) == 0)
2518 && (!CC->internal_pgm)) {
2519 cprintf("%d You don't have access to Internet mail.\n",
2520 ERROR + HIGHER_ACCESS_REQUIRED);
2527 /* Is this a room which has anonymous-only or anonymous-option? */
2528 anonymous = MES_NORMAL;
2529 if (CC->quickroom.QRflags & QR_ANONONLY) {
2530 anonymous = MES_ANONONLY;
2532 if (CC->quickroom.QRflags & QR_ANONOPT) {
2533 if (anon_flag == 1) { /* only if the user requested it */
2534 anonymous = MES_ANONOPT;
2538 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2542 /* If we're only checking the validity of the request, return
2543 * success without creating the message.
2546 cprintf("%d %s\n", CIT_OK,
2547 ((valid != NULL) ? valid->display_recp : "") );
2552 /* Handle author masquerading */
2553 if (CC->fake_postname[0]) {
2554 strcpy(masquerade_as, CC->fake_postname);
2556 else if (CC->fake_username[0]) {
2557 strcpy(masquerade_as, CC->fake_username);
2560 strcpy(masquerade_as, "");
2563 /* Read in the message from the client. */
2564 cprintf("%d send message\n", SEND_LISTING);
2565 msg = make_message(&CC->usersupp, recp,
2566 CC->quickroom.QRname, anonymous, format_type,
2567 masquerade_as, subject);
2570 CtdlSubmitMsg(msg, valid, "");
2571 CtdlFreeMessage(msg);
2573 CC->fake_postname[0] = '\0';
2581 * API function to delete messages which match a set of criteria
2582 * (returns the actual number of messages deleted)
2584 int CtdlDeleteMessages(char *room_name, /* which room */
2585 long dmsgnum, /* or "0" for any */
2586 char *content_type /* or "" for any */
2590 struct quickroom qrbuf;
2591 struct cdbdata *cdbfr;
2592 long *msglist = NULL;
2593 long *dellist = NULL;
2596 int num_deleted = 0;
2598 struct MetaData smi;
2600 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2601 room_name, dmsgnum, content_type);
2603 /* get room record, obtaining a lock... */
2604 if (lgetroom(&qrbuf, room_name) != 0) {
2605 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2607 return (0); /* room not found */
2609 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2611 if (cdbfr != NULL) {
2612 msglist = mallok(cdbfr->len);
2613 dellist = mallok(cdbfr->len);
2614 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2615 num_msgs = cdbfr->len / sizeof(long);
2619 for (i = 0; i < num_msgs; ++i) {
2622 /* Set/clear a bit for each criterion */
2624 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2625 delete_this |= 0x01;
2627 if (strlen(content_type) == 0) {
2628 delete_this |= 0x02;
2630 GetMetaData(&smi, msglist[i]);
2631 if (!strcasecmp(smi.meta_content_type,
2633 delete_this |= 0x02;
2637 /* Delete message only if all bits are set */
2638 if (delete_this == 0x03) {
2639 dellist[num_deleted++] = msglist[i];
2644 num_msgs = sort_msglist(msglist, num_msgs);
2645 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2646 msglist, (num_msgs * sizeof(long)));
2648 qrbuf.QRhighest = msglist[num_msgs - 1];
2652 /* Go through the messages we pulled out of the index, and decrement
2653 * their reference counts by 1. If this is the only room the message
2654 * was in, the reference count will reach zero and the message will
2655 * automatically be deleted from the database. We do this in a
2656 * separate pass because there might be plug-in hooks getting called,
2657 * and we don't want that happening during an S_QUICKROOM critical
2660 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2661 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2662 AdjRefCount(dellist[i], -1);
2665 /* Now free the memory we used, and go away. */
2666 if (msglist != NULL) phree(msglist);
2667 if (dellist != NULL) phree(dellist);
2668 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2669 return (num_deleted);
2675 * Check whether the current user has permission to delete messages from
2676 * the current room (returns 1 for yes, 0 for no)
2678 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2679 getuser(&CC->usersupp, CC->curr_user);
2680 if ((CC->usersupp.axlevel < 6)
2681 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2682 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2683 && (!(CC->internal_pgm))) {
2692 * Delete message from current room
2694 void cmd_dele(char *delstr)
2699 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2700 cprintf("%d Higher access required.\n",
2701 ERROR + HIGHER_ACCESS_REQUIRED);
2704 delnum = extract_long(delstr, 0);
2706 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2709 cprintf("%d %d message%s deleted.\n", CIT_OK,
2710 num_deleted, ((num_deleted != 1) ? "s" : ""));
2712 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2718 * Back end API function for moves and deletes
2720 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2723 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2724 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2725 if (err != 0) return(err);
2733 * move or copy a message to another room
2735 void cmd_move(char *args)
2739 struct quickroom qtemp;
2743 num = extract_long(args, 0);
2744 extract(targ, args, 1);
2745 targ[ROOMNAMELEN - 1] = 0;
2746 is_copy = extract_int(args, 2);
2748 if (getroom(&qtemp, targ) != 0) {
2749 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2753 getuser(&CC->usersupp, CC->curr_user);
2754 /* Aides can move/copy */
2755 if ((CC->usersupp.axlevel < 6)
2756 /* Roomaides can move/copy */
2757 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2758 /* Permit move/copy to/from personal rooms */
2759 && (!((CC->quickroom.QRflags & QR_MAILBOX)
2760 && (qtemp.QRflags & QR_MAILBOX)))
2761 /* Permit only copy from public to personal room */
2762 && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2763 && (qtemp.QRflags & QR_MAILBOX)))) {
2764 cprintf("%d Higher access required.\n",
2765 ERROR + HIGHER_ACCESS_REQUIRED);
2769 err = CtdlCopyMsgToRoom(num, targ);
2771 cprintf("%d Cannot store message in %s: error %d\n",
2776 /* Now delete the message from the source room,
2777 * if this is a 'move' rather than a 'copy' operation.
2780 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2783 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2789 * GetMetaData() - Get the supplementary record for a message
2791 void GetMetaData(struct MetaData *smibuf, long msgnum)
2794 struct cdbdata *cdbsmi;
2797 memset(smibuf, 0, sizeof(struct MetaData));
2798 smibuf->meta_msgnum = msgnum;
2799 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2801 /* Use the negative of the message number for its supp record index */
2802 TheIndex = (0L - msgnum);
2804 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2805 if (cdbsmi == NULL) {
2806 return; /* record not found; go with defaults */
2808 memcpy(smibuf, cdbsmi->ptr,
2809 ((cdbsmi->len > sizeof(struct MetaData)) ?
2810 sizeof(struct MetaData) : cdbsmi->len));
2817 * PutMetaData() - (re)write supplementary record for a message
2819 void PutMetaData(struct MetaData *smibuf)
2823 /* Use the negative of the message number for the metadata db index */
2824 TheIndex = (0L - smibuf->meta_msgnum);
2826 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2827 smibuf->meta_msgnum, smibuf->meta_refcount);
2829 cdb_store(CDB_MSGMAIN,
2830 &TheIndex, sizeof(long),
2831 smibuf, sizeof(struct MetaData));
2836 * AdjRefCount - change the reference count for a message;
2837 * delete the message if it reaches zero
2839 void AdjRefCount(long msgnum, int incr)
2842 struct MetaData smi;
2845 /* This is a *tight* critical section; please keep it that way, as
2846 * it may get called while nested in other critical sections.
2847 * Complicating this any further will surely cause deadlock!
2849 begin_critical_section(S_SUPPMSGMAIN);
2850 GetMetaData(&smi, msgnum);
2851 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2852 msgnum, smi.meta_refcount);
2853 smi.meta_refcount += incr;
2855 end_critical_section(S_SUPPMSGMAIN);
2856 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2857 msgnum, smi.meta_refcount);
2859 /* If the reference count is now zero, delete the message
2860 * (and its supplementary record as well).
2862 if (smi.meta_refcount == 0) {
2863 lprintf(9, "Deleting message <%ld>\n", msgnum);
2865 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2867 /* We have to delete the metadata record too! */
2868 delnum = (0L - msgnum);
2869 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2874 * Write a generic object to this room
2876 * Note: this could be much more efficient. Right now we use two temporary
2877 * files, and still pull the message into memory as with all others.
2879 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2880 char *content_type, /* MIME type of this object */
2881 char *tempfilename, /* Where to fetch it from */
2882 struct usersupp *is_mailbox, /* Mailbox room? */
2883 int is_binary, /* Is encoding necessary? */
2884 int is_unique, /* Del others of this type? */
2885 unsigned int flags /* Internal save flags */
2890 char filename[PATH_MAX];
2893 struct quickroom qrbuf;
2894 char roomname[ROOMNAMELEN];
2895 struct CtdlMessage *msg;
2898 if (is_mailbox != NULL)
2899 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
2901 safestrncpy(roomname, req_room, sizeof(roomname));
2902 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2904 strcpy(filename, tmpnam(NULL));
2905 fp = fopen(filename, "w");
2909 tempfp = fopen(tempfilename, "r");
2910 if (tempfp == NULL) {
2916 fprintf(fp, "Content-type: %s\n", content_type);
2917 lprintf(9, "Content-type: %s\n", content_type);
2919 if (is_binary == 0) {
2920 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2921 while (ch = getc(tempfp), ch > 0)
2927 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2930 snprintf(cmdbuf, sizeof cmdbuf, "./base64 -e <%s >>%s",
2931 tempfilename, filename);
2935 lprintf(9, "Allocating\n");
2936 msg = mallok(sizeof(struct CtdlMessage));
2937 memset(msg, 0, sizeof(struct CtdlMessage));
2938 msg->cm_magic = CTDLMESSAGE_MAGIC;
2939 msg->cm_anon_type = MES_NORMAL;
2940 msg->cm_format_type = 4;
2941 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2942 msg->cm_fields['O'] = strdoop(req_room);
2943 msg->cm_fields['N'] = strdoop(config.c_nodename);
2944 msg->cm_fields['H'] = strdoop(config.c_humannode);
2945 msg->cm_flags = flags;
2947 lprintf(9, "Loading\n");
2948 fp = fopen(filename, "rb");
2949 fseek(fp, 0L, SEEK_END);
2952 msg->cm_fields['M'] = mallok(len);
2953 fread(msg->cm_fields['M'], len, 1, fp);
2957 /* Create the requested room if we have to. */
2958 if (getroom(&qrbuf, roomname) != 0) {
2959 create_room(roomname,
2960 ( (is_mailbox != NULL) ? 5 : 3 ),
2963 /* If the caller specified this object as unique, delete all
2964 * other objects of this type that are currently in the room.
2967 lprintf(9, "Deleted %d other msgs of this type\n",
2968 CtdlDeleteMessages(roomname, 0L, content_type));
2970 /* Now write the data */
2971 CtdlSubmitMsg(msg, NULL, roomname);
2972 CtdlFreeMessage(msg);
2980 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2981 config_msgnum = msgnum;
2985 char *CtdlGetSysConfig(char *sysconfname) {
2986 char hold_rm[ROOMNAMELEN];
2989 struct CtdlMessage *msg;
2992 strcpy(hold_rm, CC->quickroom.QRname);
2993 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2994 getroom(&CC->quickroom, hold_rm);
2999 /* We want the last (and probably only) config in this room */
3000 begin_critical_section(S_CONFIG);
3001 config_msgnum = (-1L);
3002 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3003 CtdlGetSysConfigBackend, NULL);
3004 msgnum = config_msgnum;
3005 end_critical_section(S_CONFIG);
3011 msg = CtdlFetchMessage(msgnum);
3013 conf = strdoop(msg->cm_fields['M']);
3014 CtdlFreeMessage(msg);
3021 getroom(&CC->quickroom, hold_rm);
3023 if (conf != NULL) do {
3024 extract_token(buf, conf, 0, '\n');
3025 strcpy(conf, &conf[strlen(buf)+1]);
3026 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3031 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3032 char temp[PATH_MAX];
3035 strcpy(temp, tmpnam(NULL));
3037 fp = fopen(temp, "w");
3038 if (fp == NULL) return;
3039 fprintf(fp, "%s", sysconfdata);
3042 /* this handy API function does all the work for us */
3043 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);