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 /* Tell the client which format type we're using. If this is a
1096 * 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);
1108 /* nhdr=yes means that we're only displaying headers, no body */
1109 if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1110 if (do_proto) cprintf("nhdr=yes\n");
1113 /* begin header processing loop for Citadel message format */
1115 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1117 strcpy(display_name, "<unknown>");
1118 if (TheMessage->cm_fields['A']) {
1119 strcpy(buf, TheMessage->cm_fields['A']);
1120 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1121 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1122 strcpy(display_name, "****");
1124 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1125 strcpy(display_name, "anonymous");
1128 strcpy(display_name, buf);
1130 if ((is_room_aide())
1131 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1132 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1133 size_t tmp = strlen(display_name);
1134 snprintf(&display_name[tmp],
1135 sizeof display_name - tmp,
1140 /* Don't show Internet address for users on the
1141 * local Citadel network.
1144 if (TheMessage->cm_fields['N'] != NULL)
1145 if (strlen(TheMessage->cm_fields['N']) > 0)
1146 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1150 /* Now spew the header fields in the order we like them. */
1151 strcpy(allkeys, FORDER);
1152 for (i=0; i<strlen(allkeys); ++i) {
1153 k = (int) allkeys[i];
1155 if ( (TheMessage->cm_fields[k] != NULL)
1156 && (msgkeys[k] != NULL) ) {
1158 if (do_proto) cprintf("%s=%s\n",
1162 else if ((k == 'F') && (suppress_f)) {
1165 /* Masquerade display name if needed */
1167 if (do_proto) cprintf("%s=%s\n",
1169 TheMessage->cm_fields[k]
1178 /* begin header processing loop for RFC822 transfer format */
1183 strcpy(snode, NODENAME);
1184 strcpy(lnode, HUMANNODE);
1185 if (mode == MT_RFC822) {
1186 cprintf("X-UIDL: %ld%s", msg_num, nl);
1187 for (i = 0; i < 256; ++i) {
1188 if (TheMessage->cm_fields[i]) {
1189 mptr = TheMessage->cm_fields[i];
1192 strcpy(luser, mptr);
1193 strcpy(suser, mptr);
1196 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1197 into thinking that mail messages are newsgroup messages instead. When we
1198 add NNTP support back into Citadel we'll have to add code to only output
1199 this field when appropriate.
1200 else if (i == 'P') {
1201 cprintf("Path: %s%s", mptr, nl);
1205 cprintf("Subject: %s%s", mptr, nl);
1207 safestrncpy(mid, mptr, sizeof mid);
1209 safestrncpy(lnode, mptr, sizeof lnode);
1211 safestrncpy(fuser, mptr, sizeof fuser);
1213 cprintf("X-Citadel-Room: %s%s",
1216 safestrncpy(snode, mptr, sizeof snode);
1218 cprintf("To: %s%s", mptr, nl);
1219 else if (i == 'T') {
1220 datestring(datestamp, sizeof datestamp, atol(mptr),
1221 DATESTRING_RFC822 );
1222 cprintf("Date: %s%s", datestamp, nl);
1228 for (i=0; i<strlen(suser); ++i) {
1229 suser[i] = tolower(suser[i]);
1230 if (!isalnum(suser[i])) suser[i]='_';
1233 if (mode == MT_RFC822) {
1234 if (!strcasecmp(snode, NODENAME)) {
1235 strcpy(snode, FQDN);
1238 /* Construct a fun message id */
1239 cprintf("Message-ID: <%s", mid);
1240 if (strchr(mid, '@')==NULL) {
1241 cprintf("@%s", snode);
1245 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1247 if (strlen(fuser) > 0) {
1248 cprintf("From: %s (%s)%s", fuser, luser, nl);
1251 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1254 cprintf("Organization: %s%s", lnode, nl);
1257 /* end header processing loop ... at this point, we're in the text */
1259 mptr = TheMessage->cm_fields['M'];
1261 /* Tell the client about the MIME parts in this message */
1262 if (TheMessage->cm_format_type == FMT_RFC822) {
1263 if (mode == MT_CITADEL) {
1264 mime_parser(mptr, NULL,
1270 else if (mode == MT_MIME) { /* list parts only */
1271 mime_parser(mptr, NULL,
1276 if (do_proto) cprintf("000\n");
1279 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1280 /* FIXME ... we have to put some code in here to avoid
1281 * printing duplicate header information when both
1282 * Citadel and RFC822 headers exist. Preference should
1283 * probably be given to the RFC822 headers.
1285 while (ch=*(mptr++), ch!=0) {
1287 else if (ch==10) cprintf("%s", nl);
1288 else cprintf("%c", ch);
1290 if (do_proto) cprintf("000\n");
1296 if (do_proto) cprintf("000\n");
1300 /* signify start of msg text */
1301 if (mode == MT_CITADEL)
1302 if (do_proto) cprintf("text\n");
1303 if (mode == MT_RFC822) {
1304 if (TheMessage->cm_fields['U'] == NULL) {
1305 cprintf("Subject: (no subject)%s", nl);
1310 /* If the format type on disk is 1 (fixed-format), then we want
1311 * everything to be output completely literally ... regardless of
1312 * what message transfer format is in use.
1314 if (TheMessage->cm_format_type == FMT_FIXED) {
1316 while (ch = *mptr++, ch > 0) {
1319 if ((ch == 10) || (strlen(buf) > 250)) {
1320 cprintf("%s%s", buf, nl);
1323 buf[strlen(buf) + 1] = 0;
1324 buf[strlen(buf)] = ch;
1327 if (strlen(buf) > 0)
1328 cprintf("%s%s", buf, nl);
1331 /* If the message on disk is format 0 (Citadel vari-format), we
1332 * output using the formatter at 80 columns. This is the final output
1333 * form if the transfer format is RFC822, but if the transfer format
1334 * is Citadel proprietary, it'll still work, because the indentation
1335 * for new paragraphs is correct and the client will reformat the
1336 * message to the reader's screen width.
1338 if (TheMessage->cm_format_type == FMT_CITADEL) {
1339 memfmout(80, mptr, 0, nl);
1342 /* If the message on disk is format 4 (MIME), we've gotta hand it
1343 * off to the MIME parser. The client has already been told that
1344 * this message is format 1 (fixed format), so the callback function
1345 * we use will display those parts as-is.
1347 if (TheMessage->cm_format_type == FMT_RFC822) {
1348 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1349 memset(ma, 0, sizeof(struct ma_info));
1350 mime_parser(mptr, NULL,
1351 *fixed_output, *fixed_output_pre, *fixed_output_post,
1355 /* now we're done */
1356 if (do_proto) cprintf("000\n");
1363 * display a message (mode 0 - Citadel proprietary)
1365 void cmd_msg0(char *cmdbuf)
1368 int headers_only = 0;
1370 msgid = extract_long(cmdbuf, 0);
1371 headers_only = extract_int(cmdbuf, 1);
1373 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1379 * display a message (mode 2 - RFC822)
1381 void cmd_msg2(char *cmdbuf)
1384 int headers_only = 0;
1386 msgid = extract_long(cmdbuf, 0);
1387 headers_only = extract_int(cmdbuf, 1);
1389 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1395 * display a message (mode 3 - IGnet raw format - internal programs only)
1397 void cmd_msg3(char *cmdbuf)
1400 struct CtdlMessage *msg;
1403 if (CC->internal_pgm == 0) {
1404 cprintf("%d This command is for internal programs only.\n",
1409 msgnum = extract_long(cmdbuf, 0);
1410 msg = CtdlFetchMessage(msgnum);
1412 cprintf("%d Message %ld not found.\n",
1417 serialize_message(&smr, msg);
1418 CtdlFreeMessage(msg);
1421 cprintf("%d Unable to serialize message\n",
1422 ERROR+INTERNAL_ERROR);
1426 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1427 client_write(smr.ser, smr.len);
1434 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1436 void cmd_msg4(char *cmdbuf)
1440 msgid = extract_long(cmdbuf, 0);
1441 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1445 * Open a component of a MIME message as a download file
1447 void cmd_opna(char *cmdbuf)
1451 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1453 msgid = extract_long(cmdbuf, 0);
1454 extract(desired_section, cmdbuf, 1);
1456 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1461 * Save a message pointer into a specified room
1462 * (Returns 0 for success, nonzero for failure)
1463 * roomname may be NULL to use the current room
1465 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1467 char hold_rm[ROOMNAMELEN];
1468 struct cdbdata *cdbfr;
1471 long highest_msg = 0L;
1472 struct CtdlMessage *msg = NULL;
1474 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1475 roomname, msgid, flags);
1477 strcpy(hold_rm, CC->quickroom.QRname);
1479 /* We may need to check to see if this message is real */
1480 if ( (flags & SM_VERIFY_GOODNESS)
1481 || (flags & SM_DO_REPL_CHECK)
1483 msg = CtdlFetchMessage(msgid);
1484 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1487 /* Perform replication checks if necessary */
1488 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1490 if (getroom(&CC->quickroom,
1491 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1493 lprintf(9, "No such room <%s>\n", roomname);
1494 if (msg != NULL) CtdlFreeMessage(msg);
1495 return(ERROR + ROOM_NOT_FOUND);
1498 if (ReplicationChecks(msg) != 0) {
1499 getroom(&CC->quickroom, hold_rm);
1500 if (msg != NULL) CtdlFreeMessage(msg);
1501 lprintf(9, "Did replication, and newer exists\n");
1506 /* Now the regular stuff */
1507 if (lgetroom(&CC->quickroom,
1508 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1510 lprintf(9, "No such room <%s>\n", roomname);
1511 if (msg != NULL) CtdlFreeMessage(msg);
1512 return(ERROR + ROOM_NOT_FOUND);
1515 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1516 if (cdbfr == NULL) {
1520 msglist = mallok(cdbfr->len);
1521 if (msglist == NULL)
1522 lprintf(3, "ERROR malloc msglist!\n");
1523 num_msgs = cdbfr->len / sizeof(long);
1524 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1529 /* Make sure the message doesn't already exist in this room. It
1530 * is absolutely taboo to have more than one reference to the same
1531 * message in a room.
1533 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1534 if (msglist[i] == msgid) {
1535 lputroom(&CC->quickroom); /* unlock the room */
1536 getroom(&CC->quickroom, hold_rm);
1537 if (msg != NULL) CtdlFreeMessage(msg);
1538 return(ERROR + ALREADY_EXISTS);
1542 /* Now add the new message */
1544 msglist = reallok(msglist,
1545 (num_msgs * sizeof(long)));
1547 if (msglist == NULL) {
1548 lprintf(3, "ERROR: can't realloc message list!\n");
1550 msglist[num_msgs - 1] = msgid;
1552 /* Sort the message list, so all the msgid's are in order */
1553 num_msgs = sort_msglist(msglist, num_msgs);
1555 /* Determine the highest message number */
1556 highest_msg = msglist[num_msgs - 1];
1558 /* Write it back to disk. */
1559 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1560 msglist, num_msgs * sizeof(long));
1562 /* Free up the memory we used. */
1565 /* Update the highest-message pointer and unlock the room. */
1566 CC->quickroom.QRhighest = highest_msg;
1567 lputroom(&CC->quickroom);
1568 getroom(&CC->quickroom, hold_rm);
1570 /* Bump the reference count for this message. */
1571 if ((flags & SM_DONT_BUMP_REF)==0) {
1572 AdjRefCount(msgid, +1);
1575 /* Return success. */
1576 if (msg != NULL) CtdlFreeMessage(msg);
1583 * Message base operation to send a message to the master file
1584 * (returns new message number)
1586 * This is the back end for CtdlSubmitMsg() and should not be directly
1587 * called by server-side modules.
1590 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1591 FILE *save_a_copy) /* save a copy to disk? */
1598 /* Get a new message number */
1599 newmsgid = get_new_message_number();
1600 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1602 /* Generate an ID if we don't have one already */
1603 if (msg->cm_fields['I']==NULL) {
1604 msg->cm_fields['I'] = strdoop(msgidbuf);
1607 serialize_message(&smr, msg);
1610 cprintf("%d Unable to serialize message\n",
1611 ERROR+INTERNAL_ERROR);
1615 /* Write our little bundle of joy into the message base */
1616 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1617 smr.ser, smr.len) < 0) {
1618 lprintf(2, "Can't store message\n");
1624 /* If the caller specified that a copy should be saved to a particular
1625 * file handle, do that now too.
1627 if (save_a_copy != NULL) {
1628 fwrite(smr.ser, smr.len, 1, save_a_copy);
1631 /* Free the memory we used for the serialized message */
1634 /* Return the *local* message ID to the caller
1635 * (even if we're storing an incoming network message)
1643 * Serialize a struct CtdlMessage into the format used on disk and network.
1645 * This function loads up a "struct ser_ret" (defined in server.h) which
1646 * contains the length of the serialized message and a pointer to the
1647 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1649 void serialize_message(struct ser_ret *ret, /* return values */
1650 struct CtdlMessage *msg) /* unserialized msg */
1654 static char *forder = FORDER;
1656 if (is_valid_message(msg) == 0) return; /* self check */
1659 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1660 ret->len = ret->len +
1661 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1663 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1664 ret->ser = mallok(ret->len);
1665 if (ret->ser == NULL) {
1671 ret->ser[1] = msg->cm_anon_type;
1672 ret->ser[2] = msg->cm_format_type;
1675 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1676 ret->ser[wlen++] = (char)forder[i];
1677 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1678 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1680 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1681 (long)ret->len, (long)wlen);
1689 * Back end for the ReplicationChecks() function
1691 void check_repl(long msgnum, void *userdata) {
1692 struct CtdlMessage *msg;
1693 time_t timestamp = (-1L);
1695 lprintf(9, "check_repl() found message %ld\n", msgnum);
1696 msg = CtdlFetchMessage(msgnum);
1697 if (msg == NULL) return;
1698 if (msg->cm_fields['T'] != NULL) {
1699 timestamp = atol(msg->cm_fields['T']);
1701 CtdlFreeMessage(msg);
1703 if (timestamp > msg_repl->highest) {
1704 msg_repl->highest = timestamp; /* newer! */
1705 lprintf(9, "newer!\n");
1708 lprintf(9, "older!\n");
1710 /* Existing isn't newer? Then delete the old one(s). */
1711 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1716 * Check to see if any messages already exist which carry the same Extended ID
1720 * -> With older timestamps: delete them and return 0. Message will be saved.
1721 * -> With newer timestamps: return 1. Message save will be aborted.
1723 int ReplicationChecks(struct CtdlMessage *msg) {
1724 struct CtdlMessage *template;
1727 lprintf(9, "ReplicationChecks() started\n");
1728 /* No extended id? Don't do anything. */
1729 if (msg->cm_fields['E'] == NULL) return 0;
1730 if (strlen(msg->cm_fields['E']) == 0) return 0;
1731 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1733 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1734 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1735 msg_repl->highest = atol(msg->cm_fields['T']);
1737 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1738 memset(template, 0, sizeof(struct CtdlMessage));
1739 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1741 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1743 /* If a newer message exists with the same Extended ID, abort
1746 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1750 CtdlFreeMessage(template);
1751 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1759 * Save a message to disk and submit it into the delivery system.
1761 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1762 struct recptypes *recps, /* recipients (if mail) */
1763 char *force /* force a particular room? */
1766 char hold_rm[ROOMNAMELEN];
1767 char actual_rm[ROOMNAMELEN];
1768 char force_room[ROOMNAMELEN];
1769 char content_type[SIZ]; /* We have to learn this */
1770 char recipient[SIZ];
1773 struct usersupp userbuf;
1775 struct MetaData smi;
1776 FILE *network_fp = NULL;
1777 static int seqnum = 1;
1778 struct CtdlMessage *imsg = NULL;
1781 char *hold_R, *hold_D;
1783 lprintf(9, "CtdlSubmitMsg() called\n");
1784 if (is_valid_message(msg) == 0) return(-1); /* self check */
1786 /* If this message has no timestamp, we take the liberty of
1787 * giving it one, right now.
1789 if (msg->cm_fields['T'] == NULL) {
1790 lprintf(9, "Generating timestamp\n");
1791 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1792 msg->cm_fields['T'] = strdoop(aaa);
1795 /* If this message has no path, we generate one.
1797 if (msg->cm_fields['P'] == NULL) {
1798 lprintf(9, "Generating path\n");
1799 if (msg->cm_fields['A'] != NULL) {
1800 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1801 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1802 if (isspace(msg->cm_fields['P'][a])) {
1803 msg->cm_fields['P'][a] = ' ';
1808 msg->cm_fields['P'] = strdoop("unknown");
1812 strcpy(force_room, force);
1814 /* Learn about what's inside, because it's what's inside that counts */
1815 lprintf(9, "Learning what's inside\n");
1816 if (msg->cm_fields['M'] == NULL) {
1817 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1820 switch (msg->cm_format_type) {
1822 strcpy(content_type, "text/x-citadel-variformat");
1825 strcpy(content_type, "text/plain");
1828 strcpy(content_type, "text/plain");
1829 /* advance past header fields */
1830 mptr = msg->cm_fields['M'];
1833 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1834 safestrncpy(content_type, mptr,
1835 sizeof(content_type));
1836 strcpy(content_type, &content_type[14]);
1837 for (a = 0; a < strlen(content_type); ++a)
1838 if ((content_type[a] == ';')
1839 || (content_type[a] == ' ')
1840 || (content_type[a] == 13)
1841 || (content_type[a] == 10))
1842 content_type[a] = 0;
1849 /* Goto the correct room */
1850 lprintf(9, "Switching rooms\n");
1851 strcpy(hold_rm, CC->quickroom.QRname);
1852 strcpy(actual_rm, CC->quickroom.QRname);
1853 if (recps != NULL) {
1854 strcpy(actual_rm, SENTITEMS);
1857 /* If the user is a twit, move to the twit room for posting */
1858 lprintf(9, "Handling twit stuff\n");
1860 if (CC->usersupp.axlevel == 2) {
1861 strcpy(hold_rm, actual_rm);
1862 strcpy(actual_rm, config.c_twitroom);
1866 /* ...or if this message is destined for Aide> then go there. */
1867 if (strlen(force_room) > 0) {
1868 strcpy(actual_rm, force_room);
1871 lprintf(9, "Possibly relocating\n");
1872 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1873 getroom(&CC->quickroom, actual_rm);
1877 * If this message has no O (room) field, generate one.
1879 if (msg->cm_fields['O'] == NULL) {
1880 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1883 /* Perform "before save" hooks (aborting if any return nonzero) */
1884 lprintf(9, "Performing before-save hooks\n");
1885 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1887 /* If this message has an Extended ID, perform replication checks */
1888 lprintf(9, "Performing replication checks\n");
1889 if (ReplicationChecks(msg) > 0) return(-1);
1891 /* Save it to disk */
1892 lprintf(9, "Saving to disk\n");
1893 newmsgid = send_message(msg, NULL);
1894 if (newmsgid <= 0L) return(-1);
1896 /* Write a supplemental message info record. This doesn't have to
1897 * be a critical section because nobody else knows about this message
1900 lprintf(9, "Creating MetaData record\n");
1901 memset(&smi, 0, sizeof(struct MetaData));
1902 smi.meta_msgnum = newmsgid;
1903 smi.meta_refcount = 0;
1904 safestrncpy(smi.meta_content_type, content_type, 64);
1907 /* Now figure out where to store the pointers */
1908 lprintf(9, "Storing pointers\n");
1910 /* If this is being done by the networker delivering a private
1911 * message, we want to BYPASS saving the sender's copy (because there
1912 * is no local sender; it would otherwise go to the Trashcan).
1914 if ((!CC->internal_pgm) || (recps == NULL)) {
1915 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1916 lprintf(3, "ERROR saving message pointer!\n");
1917 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
1921 /* For internet mail, drop a copy in the outbound queue room */
1923 if (recps->num_internet > 0) {
1924 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1927 /* If other rooms are specified, drop them there too. */
1929 if (recps->num_room > 0)
1930 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1931 extract(recipient, recps->recp_room, i);
1932 lprintf(9, "Delivering to local room <%s>\n", recipient);
1933 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1936 /* Bump this user's messages posted counter. */
1937 lprintf(9, "Updating user\n");
1938 lgetuser(&CC->usersupp, CC->curr_user);
1939 CC->usersupp.posted = CC->usersupp.posted + 1;
1940 lputuser(&CC->usersupp);
1942 /* If this is private, local mail, make a copy in the
1943 * recipient's mailbox and bump the reference count.
1946 if (recps->num_local > 0)
1947 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1948 extract(recipient, recps->recp_local, i);
1949 lprintf(9, "Delivering private local mail to <%s>\n",
1951 if (getuser(&userbuf, recipient) == 0) {
1952 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
1953 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1954 BumpNewMailCounter(userbuf.usernum);
1957 lprintf(9, "No user <%s>\n", recipient);
1958 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
1962 /* Perform "after save" hooks */
1963 lprintf(9, "Performing after-save hooks\n");
1964 PerformMessageHooks(msg, EVT_AFTERSAVE);
1966 /* For IGnet mail, we have to save a new copy into the spooler for
1967 * each recipient, with the R and D fields set to the recipient and
1968 * destination-node. This has two ugly side effects: all other
1969 * recipients end up being unlisted in this recipient's copy of the
1970 * message, and it has to deliver multiple messages to the same
1971 * node. We'll revisit this again in a year or so when everyone has
1972 * a network spool receiver that can handle the new style messages.
1975 if (recps->num_ignet > 0)
1976 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1977 extract(recipient, recps->recp_ignet, i);
1979 hold_R = msg->cm_fields['R'];
1980 hold_D = msg->cm_fields['D'];
1981 msg->cm_fields['R'] = mallok(SIZ);
1982 msg->cm_fields['D'] = mallok(SIZ);
1983 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1984 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1986 serialize_message(&smr, msg);
1988 snprintf(aaa, sizeof aaa,
1989 "./network/spoolin/netmail.%04lx.%04x.%04x",
1990 (long) getpid(), CC->cs_pid, ++seqnum);
1991 network_fp = fopen(aaa, "wb+");
1992 if (network_fp != NULL) {
1993 fwrite(smr.ser, smr.len, 1, network_fp);
1999 phree(msg->cm_fields['R']);
2000 phree(msg->cm_fields['D']);
2001 msg->cm_fields['R'] = hold_R;
2002 msg->cm_fields['D'] = hold_D;
2005 /* Go back to the room we started from */
2006 lprintf(9, "Returning to original room\n");
2007 if (strcasecmp(hold_rm, CC->quickroom.QRname))
2008 getroom(&CC->quickroom, hold_rm);
2010 /* For internet mail, generate delivery instructions.
2011 * Yes, this is recursive. Deal with it. Infinite recursion does
2012 * not happen because the delivery instructions message does not
2013 * contain a recipient.
2016 if (recps->num_internet > 0) {
2017 lprintf(9, "Generating delivery instructions\n");
2018 instr = mallok(SIZ * 2);
2019 snprintf(instr, SIZ * 2,
2020 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2022 SPOOLMIME, newmsgid, (long)time(NULL),
2023 msg->cm_fields['A'], msg->cm_fields['N']
2026 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2027 size_t tmp = strlen(instr);
2028 extract(recipient, recps->recp_internet, i);
2029 snprintf(&instr[tmp], SIZ * 2 - tmp,
2030 "remote|%s|0||\n", recipient);
2033 imsg = mallok(sizeof(struct CtdlMessage));
2034 memset(imsg, 0, sizeof(struct CtdlMessage));
2035 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2036 imsg->cm_anon_type = MES_NORMAL;
2037 imsg->cm_format_type = FMT_RFC822;
2038 imsg->cm_fields['A'] = strdoop("Citadel");
2039 imsg->cm_fields['M'] = instr;
2040 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2041 CtdlFreeMessage(imsg);
2050 * Convenience function for generating small administrative messages.
2052 void quickie_message(char *from, char *to, char *room, char *text)
2054 struct CtdlMessage *msg;
2056 msg = mallok(sizeof(struct CtdlMessage));
2057 memset(msg, 0, sizeof(struct CtdlMessage));
2058 msg->cm_magic = CTDLMESSAGE_MAGIC;
2059 msg->cm_anon_type = MES_NORMAL;
2060 msg->cm_format_type = 0;
2061 msg->cm_fields['A'] = strdoop(from);
2062 msg->cm_fields['O'] = strdoop(room);
2063 msg->cm_fields['N'] = strdoop(NODENAME);
2065 msg->cm_fields['R'] = strdoop(to);
2066 msg->cm_fields['M'] = strdoop(text);
2068 CtdlSubmitMsg(msg, NULL, room);
2069 CtdlFreeMessage(msg);
2070 syslog(LOG_NOTICE, text);
2076 * Back end function used by make_message() and similar functions
2078 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2079 size_t maxlen, /* maximum message length */
2080 char *exist /* if non-null, append to it;
2081 exist is ALWAYS freed */
2085 size_t message_len = 0;
2086 size_t buffer_len = 0;
2090 if (exist == NULL) {
2097 message_len = strlen(exist);
2098 buffer_len = message_len + 4096;
2099 m = reallok(exist, buffer_len);
2106 /* flush the input if we have nowhere to store it */
2108 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2112 /* read in the lines of message text one by one */
2113 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2115 /* strip trailing newline type stuff */
2116 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2117 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2119 linelen = strlen(buf);
2121 /* augment the buffer if we have to */
2122 if ((message_len + linelen + 2) > buffer_len) {
2123 lprintf(9, "realloking\n");
2124 ptr = reallok(m, (buffer_len * 2) );
2125 if (ptr == NULL) { /* flush if can't allocate */
2126 while ( (client_gets(buf)>0) &&
2127 strcmp(buf, terminator)) ;;
2130 buffer_len = (buffer_len * 2);
2132 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2136 /* Add the new line to the buffer. NOTE: this loop must avoid
2137 * using functions like strcat() and strlen() because they
2138 * traverse the entire buffer upon every call, and doing that
2139 * for a multi-megabyte message slows it down beyond usability.
2141 strcpy(&m[message_len], buf);
2142 m[message_len + linelen] = '\n';
2143 m[message_len + linelen + 1] = 0;
2144 message_len = message_len + linelen + 1;
2146 /* if we've hit the max msg length, flush the rest */
2147 if (message_len >= maxlen) {
2148 while ( (client_gets(buf)>0)
2149 && strcmp(buf, terminator)) ;;
2160 * Build a binary message to be saved on disk.
2163 static struct CtdlMessage *make_message(
2164 struct usersupp *author, /* author's usersupp structure */
2165 char *recipient, /* NULL if it's not mail */
2166 char *room, /* room where it's going */
2167 int type, /* see MES_ types in header file */
2168 int format_type, /* variformat, plain text, MIME... */
2169 char *fake_name, /* who we're masquerading as */
2170 char *subject /* Subject (optional) */
2172 char dest_node[SIZ];
2174 struct CtdlMessage *msg;
2176 msg = mallok(sizeof(struct CtdlMessage));
2177 memset(msg, 0, sizeof(struct CtdlMessage));
2178 msg->cm_magic = CTDLMESSAGE_MAGIC;
2179 msg->cm_anon_type = type;
2180 msg->cm_format_type = format_type;
2182 /* Don't confuse the poor folks if it's not routed mail. */
2183 strcpy(dest_node, "");
2187 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2188 msg->cm_fields['P'] = strdoop(buf);
2190 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2191 msg->cm_fields['T'] = strdoop(buf);
2193 if (fake_name[0]) /* author */
2194 msg->cm_fields['A'] = strdoop(fake_name);
2196 msg->cm_fields['A'] = strdoop(author->fullname);
2198 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2199 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2202 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2205 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2206 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2208 if (recipient[0] != 0) {
2209 msg->cm_fields['R'] = strdoop(recipient);
2211 if (dest_node[0] != 0) {
2212 msg->cm_fields['D'] = strdoop(dest_node);
2215 if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
2216 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2219 if (subject != NULL) {
2221 if (strlen(subject) > 0) {
2222 msg->cm_fields['U'] = strdoop(subject);
2226 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2227 config.c_maxmsglen, NULL);
2234 * Check to see whether we have permission to post a message in the current
2235 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2236 * returns 0 on success.
2238 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2240 if (!(CC->logged_in)) {
2241 snprintf(errmsgbuf, n, "Not logged in.");
2242 return (ERROR + NOT_LOGGED_IN);
2245 if ((CC->usersupp.axlevel < 2)
2246 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2247 snprintf(errmsgbuf, n, "Need to be validated to enter "
2248 "(except in %s> to sysop)", MAILROOM);
2249 return (ERROR + HIGHER_ACCESS_REQUIRED);
2252 if ((CC->usersupp.axlevel < 4)
2253 && (CC->quickroom.QRflags & QR_NETWORK)) {
2254 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2255 return (ERROR + HIGHER_ACCESS_REQUIRED);
2258 if ((CC->usersupp.axlevel < 6)
2259 && (CC->quickroom.QRflags & QR_READONLY)) {
2260 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2261 return (ERROR + HIGHER_ACCESS_REQUIRED);
2264 strcpy(errmsgbuf, "Ok");
2270 * Validate recipients, count delivery types and errors, and handle aliasing
2271 * FIXME check for dupes!!!!!
2273 struct recptypes *validate_recipients(char *recipients) {
2274 struct recptypes *ret;
2275 char this_recp[SIZ];
2276 char this_recp_cooked[SIZ];
2282 struct usersupp tempUS;
2283 struct quickroom tempQR;
2286 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2287 if (ret == NULL) return(NULL);
2288 memset(ret, 0, sizeof(struct recptypes));
2291 ret->num_internet = 0;
2296 if (recipients == NULL) {
2299 else if (strlen(recipients) == 0) {
2303 /* Change all valid separator characters to commas */
2304 for (i=0; i<strlen(recipients); ++i) {
2305 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2306 recipients[i] = ',';
2311 num_recps = num_tokens(recipients, ',');
2314 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2315 extract_token(this_recp, recipients, i, ',');
2317 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2318 mailtype = alias(this_recp);
2319 mailtype = alias(this_recp);
2320 mailtype = alias(this_recp);
2321 for (j=0; j<=strlen(this_recp); ++j) {
2322 if (this_recp[j]=='_') {
2323 this_recp_cooked[j] = ' ';
2326 this_recp_cooked[j] = this_recp[j];
2332 if (!strcasecmp(this_recp, "sysop")) {
2334 strcpy(this_recp, config.c_aideroom);
2335 if (strlen(ret->recp_room) > 0) {
2336 strcat(ret->recp_room, "|");
2338 strcat(ret->recp_room, this_recp);
2340 else if (getuser(&tempUS, this_recp) == 0) {
2342 strcpy(this_recp, tempUS.fullname);
2343 if (strlen(ret->recp_local) > 0) {
2344 strcat(ret->recp_local, "|");
2346 strcat(ret->recp_local, this_recp);
2348 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2350 strcpy(this_recp, tempUS.fullname);
2351 if (strlen(ret->recp_local) > 0) {
2352 strcat(ret->recp_local, "|");
2354 strcat(ret->recp_local, this_recp);
2356 else if ( (!strncasecmp(this_recp, "room_", 5))
2357 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2359 if (strlen(ret->recp_room) > 0) {
2360 strcat(ret->recp_room, "|");
2362 strcat(ret->recp_room, &this_recp_cooked[5]);
2370 ++ret->num_internet;
2371 if (strlen(ret->recp_internet) > 0) {
2372 strcat(ret->recp_internet, "|");
2374 strcat(ret->recp_internet, this_recp);
2378 if (strlen(ret->recp_ignet) > 0) {
2379 strcat(ret->recp_ignet, "|");
2381 strcat(ret->recp_ignet, this_recp);
2389 if (strlen(ret->errormsg) == 0) {
2390 snprintf(append, sizeof append,
2391 "Invalid recipient: %s",
2395 snprintf(append, sizeof append,
2398 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2399 strcat(ret->errormsg, append);
2403 if (strlen(ret->display_recp) == 0) {
2404 strcpy(append, this_recp);
2407 snprintf(append, sizeof append, ", %s",
2410 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2411 strcat(ret->display_recp, append);
2416 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2417 ret->num_room + ret->num_error) == 0) {
2419 strcpy(ret->errormsg, "No recipients specified.");
2422 lprintf(9, "validate_recipients()\n");
2423 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2424 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2425 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2426 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2427 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2435 * message entry - mode 0 (normal)
2437 void cmd_ent0(char *entargs)
2441 char masquerade_as[SIZ];
2443 int format_type = 0;
2444 char newusername[SIZ];
2445 struct CtdlMessage *msg;
2449 struct recptypes *valid = NULL;
2452 post = extract_int(entargs, 0);
2453 extract(recp, entargs, 1);
2454 anon_flag = extract_int(entargs, 2);
2455 format_type = extract_int(entargs, 3);
2456 extract(subject, entargs, 4);
2458 /* first check to make sure the request is valid. */
2460 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2462 cprintf("%d %s\n", err, errmsg);
2466 /* Check some other permission type things. */
2469 if (CC->usersupp.axlevel < 6) {
2470 cprintf("%d You don't have permission to masquerade.\n",
2471 ERROR + HIGHER_ACCESS_REQUIRED);
2474 extract(newusername, entargs, 4);
2475 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2476 safestrncpy(CC->fake_postname, newusername,
2477 sizeof(CC->fake_postname) );
2478 cprintf("%d ok\n", CIT_OK);
2481 CC->cs_flags |= CS_POSTING;
2483 /* In the Mail> room we have to behave a little differently --
2484 * make sure the user has specified at least one recipient. Then
2485 * validate the recipient(s).
2487 if ( (CC->quickroom.QRflags & QR_MAILBOX)
2488 && (!strcasecmp(&CC->quickroom.QRname[11], MAILROOM)) ) {
2490 if (CC->usersupp.axlevel < 2) {
2491 strcpy(recp, "sysop");
2494 valid = validate_recipients(recp);
2495 if (valid->num_error > 0) {
2497 ERROR + NO_SUCH_USER, valid->errormsg);
2502 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2503 && (CC->usersupp.axlevel < 4) ) {
2504 cprintf("%d Higher access required for network mail.\n",
2505 ERROR + HIGHER_ACCESS_REQUIRED);
2510 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2511 && ((CC->usersupp.flags & US_INTERNET) == 0)
2512 && (!CC->internal_pgm)) {
2513 cprintf("%d You don't have access to Internet mail.\n",
2514 ERROR + HIGHER_ACCESS_REQUIRED);
2521 /* Is this a room which has anonymous-only or anonymous-option? */
2522 anonymous = MES_NORMAL;
2523 if (CC->quickroom.QRflags & QR_ANONONLY) {
2524 anonymous = MES_ANONONLY;
2526 if (CC->quickroom.QRflags & QR_ANONOPT) {
2527 if (anon_flag == 1) { /* only if the user requested it */
2528 anonymous = MES_ANONOPT;
2532 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2536 /* If we're only checking the validity of the request, return
2537 * success without creating the message.
2540 cprintf("%d %s\n", CIT_OK,
2541 ((valid != NULL) ? valid->display_recp : "") );
2546 /* Handle author masquerading */
2547 if (CC->fake_postname[0]) {
2548 strcpy(masquerade_as, CC->fake_postname);
2550 else if (CC->fake_username[0]) {
2551 strcpy(masquerade_as, CC->fake_username);
2554 strcpy(masquerade_as, "");
2557 /* Read in the message from the client. */
2558 cprintf("%d send message\n", SEND_LISTING);
2559 msg = make_message(&CC->usersupp, recp,
2560 CC->quickroom.QRname, anonymous, format_type,
2561 masquerade_as, subject);
2564 CtdlSubmitMsg(msg, valid, "");
2565 CtdlFreeMessage(msg);
2567 CC->fake_postname[0] = '\0';
2575 * API function to delete messages which match a set of criteria
2576 * (returns the actual number of messages deleted)
2578 int CtdlDeleteMessages(char *room_name, /* which room */
2579 long dmsgnum, /* or "0" for any */
2580 char *content_type /* or "" for any */
2584 struct quickroom qrbuf;
2585 struct cdbdata *cdbfr;
2586 long *msglist = NULL;
2587 long *dellist = NULL;
2590 int num_deleted = 0;
2592 struct MetaData smi;
2594 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2595 room_name, dmsgnum, content_type);
2597 /* get room record, obtaining a lock... */
2598 if (lgetroom(&qrbuf, room_name) != 0) {
2599 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2601 return (0); /* room not found */
2603 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2605 if (cdbfr != NULL) {
2606 msglist = mallok(cdbfr->len);
2607 dellist = mallok(cdbfr->len);
2608 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2609 num_msgs = cdbfr->len / sizeof(long);
2613 for (i = 0; i < num_msgs; ++i) {
2616 /* Set/clear a bit for each criterion */
2618 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2619 delete_this |= 0x01;
2621 if (strlen(content_type) == 0) {
2622 delete_this |= 0x02;
2624 GetMetaData(&smi, msglist[i]);
2625 if (!strcasecmp(smi.meta_content_type,
2627 delete_this |= 0x02;
2631 /* Delete message only if all bits are set */
2632 if (delete_this == 0x03) {
2633 dellist[num_deleted++] = msglist[i];
2638 num_msgs = sort_msglist(msglist, num_msgs);
2639 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2640 msglist, (num_msgs * sizeof(long)));
2642 qrbuf.QRhighest = msglist[num_msgs - 1];
2646 /* Go through the messages we pulled out of the index, and decrement
2647 * their reference counts by 1. If this is the only room the message
2648 * was in, the reference count will reach zero and the message will
2649 * automatically be deleted from the database. We do this in a
2650 * separate pass because there might be plug-in hooks getting called,
2651 * and we don't want that happening during an S_QUICKROOM critical
2654 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2655 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2656 AdjRefCount(dellist[i], -1);
2659 /* Now free the memory we used, and go away. */
2660 if (msglist != NULL) phree(msglist);
2661 if (dellist != NULL) phree(dellist);
2662 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2663 return (num_deleted);
2669 * Check whether the current user has permission to delete messages from
2670 * the current room (returns 1 for yes, 0 for no)
2672 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2673 getuser(&CC->usersupp, CC->curr_user);
2674 if ((CC->usersupp.axlevel < 6)
2675 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2676 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2677 && (!(CC->internal_pgm))) {
2686 * Delete message from current room
2688 void cmd_dele(char *delstr)
2693 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2694 cprintf("%d Higher access required.\n",
2695 ERROR + HIGHER_ACCESS_REQUIRED);
2698 delnum = extract_long(delstr, 0);
2700 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2703 cprintf("%d %d message%s deleted.\n", CIT_OK,
2704 num_deleted, ((num_deleted != 1) ? "s" : ""));
2706 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2712 * Back end API function for moves and deletes
2714 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2717 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2718 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2719 if (err != 0) return(err);
2727 * move or copy a message to another room
2729 void cmd_move(char *args)
2733 struct quickroom qtemp;
2737 num = extract_long(args, 0);
2738 extract(targ, args, 1);
2739 targ[ROOMNAMELEN - 1] = 0;
2740 is_copy = extract_int(args, 2);
2742 if (getroom(&qtemp, targ) != 0) {
2743 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2747 getuser(&CC->usersupp, CC->curr_user);
2748 /* Aides can move/copy */
2749 if ((CC->usersupp.axlevel < 6)
2750 /* Roomaides can move/copy */
2751 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2752 /* Permit move/copy to/from personal rooms */
2753 && (!((CC->quickroom.QRflags & QR_MAILBOX)
2754 && (qtemp.QRflags & QR_MAILBOX)))
2755 /* Permit only copy from public to personal room */
2756 && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2757 && (qtemp.QRflags & QR_MAILBOX)))) {
2758 cprintf("%d Higher access required.\n",
2759 ERROR + HIGHER_ACCESS_REQUIRED);
2763 err = CtdlCopyMsgToRoom(num, targ);
2765 cprintf("%d Cannot store message in %s: error %d\n",
2770 /* Now delete the message from the source room,
2771 * if this is a 'move' rather than a 'copy' operation.
2774 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2777 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2783 * GetMetaData() - Get the supplementary record for a message
2785 void GetMetaData(struct MetaData *smibuf, long msgnum)
2788 struct cdbdata *cdbsmi;
2791 memset(smibuf, 0, sizeof(struct MetaData));
2792 smibuf->meta_msgnum = msgnum;
2793 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2795 /* Use the negative of the message number for its supp record index */
2796 TheIndex = (0L - msgnum);
2798 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2799 if (cdbsmi == NULL) {
2800 return; /* record not found; go with defaults */
2802 memcpy(smibuf, cdbsmi->ptr,
2803 ((cdbsmi->len > sizeof(struct MetaData)) ?
2804 sizeof(struct MetaData) : cdbsmi->len));
2811 * PutMetaData() - (re)write supplementary record for a message
2813 void PutMetaData(struct MetaData *smibuf)
2817 /* Use the negative of the message number for the metadata db index */
2818 TheIndex = (0L - smibuf->meta_msgnum);
2820 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2821 smibuf->meta_msgnum, smibuf->meta_refcount);
2823 cdb_store(CDB_MSGMAIN,
2824 &TheIndex, sizeof(long),
2825 smibuf, sizeof(struct MetaData));
2830 * AdjRefCount - change the reference count for a message;
2831 * delete the message if it reaches zero
2833 void AdjRefCount(long msgnum, int incr)
2836 struct MetaData smi;
2839 /* This is a *tight* critical section; please keep it that way, as
2840 * it may get called while nested in other critical sections.
2841 * Complicating this any further will surely cause deadlock!
2843 begin_critical_section(S_SUPPMSGMAIN);
2844 GetMetaData(&smi, msgnum);
2845 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2846 msgnum, smi.meta_refcount);
2847 smi.meta_refcount += incr;
2849 end_critical_section(S_SUPPMSGMAIN);
2850 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2851 msgnum, smi.meta_refcount);
2853 /* If the reference count is now zero, delete the message
2854 * (and its supplementary record as well).
2856 if (smi.meta_refcount == 0) {
2857 lprintf(9, "Deleting message <%ld>\n", msgnum);
2859 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2861 /* We have to delete the metadata record too! */
2862 delnum = (0L - msgnum);
2863 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2868 * Write a generic object to this room
2870 * Note: this could be much more efficient. Right now we use two temporary
2871 * files, and still pull the message into memory as with all others.
2873 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2874 char *content_type, /* MIME type of this object */
2875 char *tempfilename, /* Where to fetch it from */
2876 struct usersupp *is_mailbox, /* Mailbox room? */
2877 int is_binary, /* Is encoding necessary? */
2878 int is_unique, /* Del others of this type? */
2879 unsigned int flags /* Internal save flags */
2884 char filename[PATH_MAX];
2887 struct quickroom qrbuf;
2888 char roomname[ROOMNAMELEN];
2889 struct CtdlMessage *msg;
2892 if (is_mailbox != NULL)
2893 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
2895 safestrncpy(roomname, req_room, sizeof(roomname));
2896 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2898 strcpy(filename, tmpnam(NULL));
2899 fp = fopen(filename, "w");
2903 tempfp = fopen(tempfilename, "r");
2904 if (tempfp == NULL) {
2910 fprintf(fp, "Content-type: %s\n", content_type);
2911 lprintf(9, "Content-type: %s\n", content_type);
2913 if (is_binary == 0) {
2914 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2915 while (ch = getc(tempfp), ch > 0)
2921 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2924 snprintf(cmdbuf, sizeof cmdbuf, "./base64 -e <%s >>%s",
2925 tempfilename, filename);
2929 lprintf(9, "Allocating\n");
2930 msg = mallok(sizeof(struct CtdlMessage));
2931 memset(msg, 0, sizeof(struct CtdlMessage));
2932 msg->cm_magic = CTDLMESSAGE_MAGIC;
2933 msg->cm_anon_type = MES_NORMAL;
2934 msg->cm_format_type = 4;
2935 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2936 msg->cm_fields['O'] = strdoop(req_room);
2937 msg->cm_fields['N'] = strdoop(config.c_nodename);
2938 msg->cm_fields['H'] = strdoop(config.c_humannode);
2939 msg->cm_flags = flags;
2941 lprintf(9, "Loading\n");
2942 fp = fopen(filename, "rb");
2943 fseek(fp, 0L, SEEK_END);
2946 msg->cm_fields['M'] = mallok(len);
2947 fread(msg->cm_fields['M'], len, 1, fp);
2951 /* Create the requested room if we have to. */
2952 if (getroom(&qrbuf, roomname) != 0) {
2953 create_room(roomname,
2954 ( (is_mailbox != NULL) ? 5 : 3 ),
2957 /* If the caller specified this object as unique, delete all
2958 * other objects of this type that are currently in the room.
2961 lprintf(9, "Deleted %d other msgs of this type\n",
2962 CtdlDeleteMessages(roomname, 0L, content_type));
2964 /* Now write the data */
2965 CtdlSubmitMsg(msg, NULL, roomname);
2966 CtdlFreeMessage(msg);
2974 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2975 config_msgnum = msgnum;
2979 char *CtdlGetSysConfig(char *sysconfname) {
2980 char hold_rm[ROOMNAMELEN];
2983 struct CtdlMessage *msg;
2986 strcpy(hold_rm, CC->quickroom.QRname);
2987 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2988 getroom(&CC->quickroom, hold_rm);
2993 /* We want the last (and probably only) config in this room */
2994 begin_critical_section(S_CONFIG);
2995 config_msgnum = (-1L);
2996 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2997 CtdlGetSysConfigBackend, NULL);
2998 msgnum = config_msgnum;
2999 end_critical_section(S_CONFIG);
3005 msg = CtdlFetchMessage(msgnum);
3007 conf = strdoop(msg->cm_fields['M']);
3008 CtdlFreeMessage(msg);
3015 getroom(&CC->quickroom, hold_rm);
3017 if (conf != NULL) do {
3018 extract_token(buf, conf, 0, '\n');
3019 strcpy(conf, &conf[strlen(buf)+1]);
3020 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3025 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3026 char temp[PATH_MAX];
3029 strcpy(temp, tmpnam(NULL));
3031 fp = fopen(temp, "w");
3032 if (fp == NULL) return;
3033 fprintf(fp, "%s", sysconfdata);
3036 /* this handy API function does all the work for us */
3037 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);