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
994 TheMessage = CtdlFetchMessage(msg_num);
995 if (TheMessage == NULL) {
996 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
998 return(om_no_such_msg);
1001 retcode = CtdlOutputPreLoadedMsg(
1002 TheMessage, msg_num, mode,
1003 headers_only, do_proto, crlf);
1005 CtdlFreeMessage(TheMessage);
1011 * Get a message off disk. (returns om_* values found in msgbase.h)
1014 int CtdlOutputPreLoadedMsg(struct CtdlMessage *TheMessage,
1016 int mode, /* how would you like that message? */
1017 int headers_only, /* eschew the message body? */
1018 int do_proto, /* do Citadel protocol responses? */
1019 int crlf /* Use CRLF newlines instead of LF? */
1025 char display_name[SIZ];
1027 char *nl; /* newline string */
1030 /* buffers needed for RFC822 translation */
1037 char datestamp[SIZ];
1040 snprintf(mid, sizeof mid, "%ld", msg_num);
1041 nl = (crlf ? "\r\n" : "\n");
1043 if (!is_valid_message(TheMessage)) {
1044 lprintf(1, "ERROR: invalid preloaded message for output\n");
1045 return(om_no_such_msg);
1048 /* Are we downloading a MIME component? */
1049 if (mode == MT_DOWNLOAD) {
1050 if (TheMessage->cm_format_type != FMT_RFC822) {
1052 cprintf("%d This is not a MIME message.\n",
1054 } else if (CC->download_fp != NULL) {
1055 if (do_proto) cprintf(
1056 "%d You already have a download open.\n",
1059 /* Parse the message text component */
1060 mptr = TheMessage->cm_fields['M'];
1061 mime_parser(mptr, NULL,
1062 *mime_download, NULL, NULL,
1064 /* If there's no file open by this time, the requested
1065 * section wasn't found, so print an error
1067 if (CC->download_fp == NULL) {
1068 if (do_proto) cprintf(
1069 "%d Section %s not found.\n",
1070 ERROR + FILE_NOT_FOUND,
1074 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1077 /* now for the user-mode message reading loops */
1078 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1080 /* Tell the client which format type we're using. If this is a
1081 * MIME message, *lie* about it and tell the user it's fixed-format.
1083 if (mode == MT_CITADEL) {
1084 if (TheMessage->cm_format_type == FMT_RFC822) {
1085 if (do_proto) cprintf("type=1\n");
1088 if (do_proto) cprintf("type=%d\n",
1089 TheMessage->cm_format_type);
1093 /* nhdr=yes means that we're only displaying headers, no body */
1094 if ((TheMessage->cm_anon_type == MES_ANONONLY) && (mode == MT_CITADEL)) {
1095 if (do_proto) cprintf("nhdr=yes\n");
1098 /* begin header processing loop for Citadel message format */
1100 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1102 strcpy(display_name, "<unknown>");
1103 if (TheMessage->cm_fields['A']) {
1104 strcpy(buf, TheMessage->cm_fields['A']);
1105 PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
1106 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1107 strcpy(display_name, "****");
1109 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1110 strcpy(display_name, "anonymous");
1113 strcpy(display_name, buf);
1115 if ((is_room_aide())
1116 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1117 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1118 size_t tmp = strlen(display_name);
1119 snprintf(&display_name[tmp],
1120 sizeof display_name - tmp,
1125 /* Don't show Internet address for users on the
1126 * local Citadel network.
1129 if (TheMessage->cm_fields['N'] != NULL)
1130 if (strlen(TheMessage->cm_fields['N']) > 0)
1131 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1135 /* Now spew the header fields in the order we like them. */
1136 strcpy(allkeys, FORDER);
1137 for (i=0; i<strlen(allkeys); ++i) {
1138 k = (int) allkeys[i];
1140 if ( (TheMessage->cm_fields[k] != NULL)
1141 && (msgkeys[k] != NULL) ) {
1143 if (do_proto) cprintf("%s=%s\n",
1147 else if ((k == 'F') && (suppress_f)) {
1150 /* Masquerade display name if needed */
1152 if (do_proto) cprintf("%s=%s\n",
1154 TheMessage->cm_fields[k]
1163 /* begin header processing loop for RFC822 transfer format */
1168 strcpy(snode, NODENAME);
1169 strcpy(lnode, HUMANNODE);
1170 if (mode == MT_RFC822) {
1171 cprintf("X-UIDL: %ld%s", msg_num, nl);
1172 for (i = 0; i < 256; ++i) {
1173 if (TheMessage->cm_fields[i]) {
1174 mptr = TheMessage->cm_fields[i];
1177 strcpy(luser, mptr);
1178 strcpy(suser, mptr);
1181 "Path:" removed for now because it confuses brain-dead Microsoft shitware
1182 into thinking that mail messages are newsgroup messages instead. When we
1183 add NNTP support back into Citadel we'll have to add code to only output
1184 this field when appropriate.
1185 else if (i == 'P') {
1186 cprintf("Path: %s%s", mptr, nl);
1190 cprintf("Subject: %s%s", mptr, nl);
1192 safestrncpy(mid, mptr, sizeof mid);
1194 safestrncpy(lnode, mptr, sizeof lnode);
1196 safestrncpy(fuser, mptr, sizeof fuser);
1198 cprintf("X-Citadel-Room: %s%s",
1201 safestrncpy(snode, mptr, sizeof snode);
1203 cprintf("To: %s%s", mptr, nl);
1204 else if (i == 'T') {
1205 datestring(datestamp, sizeof datestamp, atol(mptr),
1206 DATESTRING_RFC822 );
1207 cprintf("Date: %s%s", datestamp, nl);
1213 for (i=0; i<strlen(suser); ++i) {
1214 suser[i] = tolower(suser[i]);
1215 if (!isalnum(suser[i])) suser[i]='_';
1218 if (mode == MT_RFC822) {
1219 if (!strcasecmp(snode, NODENAME)) {
1220 strcpy(snode, FQDN);
1223 /* Construct a fun message id */
1224 cprintf("Message-ID: <%s", mid);
1225 if (strchr(mid, '@')==NULL) {
1226 cprintf("@%s", snode);
1230 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
1232 if (strlen(fuser) > 0) {
1233 cprintf("From: %s (%s)%s", fuser, luser, nl);
1236 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1239 cprintf("Organization: %s%s", lnode, nl);
1242 /* end header processing loop ... at this point, we're in the text */
1244 mptr = TheMessage->cm_fields['M'];
1246 /* Tell the client about the MIME parts in this message */
1247 if (TheMessage->cm_format_type == FMT_RFC822) {
1248 if (mode == MT_CITADEL) {
1249 mime_parser(mptr, NULL,
1255 else if (mode == MT_MIME) { /* list parts only */
1256 mime_parser(mptr, NULL,
1261 if (do_proto) cprintf("000\n");
1264 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1265 /* FIXME ... we have to put some code in here to avoid
1266 * printing duplicate header information when both
1267 * Citadel and RFC822 headers exist. Preference should
1268 * probably be given to the RFC822 headers.
1270 while (ch=*(mptr++), ch!=0) {
1272 else if (ch==10) cprintf("%s", nl);
1273 else cprintf("%c", ch);
1275 if (do_proto) cprintf("000\n");
1281 if (do_proto) cprintf("000\n");
1285 /* signify start of msg text */
1286 if (mode == MT_CITADEL)
1287 if (do_proto) cprintf("text\n");
1288 if (mode == MT_RFC822) {
1289 if (TheMessage->cm_fields['U'] == NULL) {
1290 cprintf("Subject: (no subject)%s", nl);
1295 /* If the format type on disk is 1 (fixed-format), then we want
1296 * everything to be output completely literally ... regardless of
1297 * what message transfer format is in use.
1299 if (TheMessage->cm_format_type == FMT_FIXED) {
1301 while (ch = *mptr++, ch > 0) {
1304 if ((ch == 10) || (strlen(buf) > 250)) {
1305 cprintf("%s%s", buf, nl);
1308 buf[strlen(buf) + 1] = 0;
1309 buf[strlen(buf)] = ch;
1312 if (strlen(buf) > 0)
1313 cprintf("%s%s", buf, nl);
1316 /* If the message on disk is format 0 (Citadel vari-format), we
1317 * output using the formatter at 80 columns. This is the final output
1318 * form if the transfer format is RFC822, but if the transfer format
1319 * is Citadel proprietary, it'll still work, because the indentation
1320 * for new paragraphs is correct and the client will reformat the
1321 * message to the reader's screen width.
1323 if (TheMessage->cm_format_type == FMT_CITADEL) {
1324 memfmout(80, mptr, 0, nl);
1327 /* If the message on disk is format 4 (MIME), we've gotta hand it
1328 * off to the MIME parser. The client has already been told that
1329 * this message is format 1 (fixed format), so the callback function
1330 * we use will display those parts as-is.
1332 if (TheMessage->cm_format_type == FMT_RFC822) {
1333 CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info));
1334 memset(ma, 0, sizeof(struct ma_info));
1335 mime_parser(mptr, NULL,
1336 *fixed_output, *fixed_output_pre, *fixed_output_post,
1340 /* now we're done */
1341 if (do_proto) cprintf("000\n");
1348 * display a message (mode 0 - Citadel proprietary)
1350 void cmd_msg0(char *cmdbuf)
1353 int headers_only = 0;
1355 msgid = extract_long(cmdbuf, 0);
1356 headers_only = extract_int(cmdbuf, 1);
1358 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1364 * display a message (mode 2 - RFC822)
1366 void cmd_msg2(char *cmdbuf)
1369 int headers_only = 0;
1371 msgid = extract_long(cmdbuf, 0);
1372 headers_only = extract_int(cmdbuf, 1);
1374 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1380 * display a message (mode 3 - IGnet raw format - internal programs only)
1382 void cmd_msg3(char *cmdbuf)
1385 struct CtdlMessage *msg;
1388 if (CC->internal_pgm == 0) {
1389 cprintf("%d This command is for internal programs only.\n",
1394 msgnum = extract_long(cmdbuf, 0);
1395 msg = CtdlFetchMessage(msgnum);
1397 cprintf("%d Message %ld not found.\n",
1402 serialize_message(&smr, msg);
1403 CtdlFreeMessage(msg);
1406 cprintf("%d Unable to serialize message\n",
1407 ERROR+INTERNAL_ERROR);
1411 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1412 client_write(smr.ser, smr.len);
1419 * display a message (mode 4 - MIME) (FIXME ... still evolving, not complete)
1421 void cmd_msg4(char *cmdbuf)
1425 msgid = extract_long(cmdbuf, 0);
1426 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1430 * Open a component of a MIME message as a download file
1432 void cmd_opna(char *cmdbuf)
1436 CtdlAllocUserData(SYM_DESIRED_SECTION, SIZ);
1438 msgid = extract_long(cmdbuf, 0);
1439 extract(desired_section, cmdbuf, 1);
1441 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1446 * Save a message pointer into a specified room
1447 * (Returns 0 for success, nonzero for failure)
1448 * roomname may be NULL to use the current room
1450 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1452 char hold_rm[ROOMNAMELEN];
1453 struct cdbdata *cdbfr;
1456 long highest_msg = 0L;
1457 struct CtdlMessage *msg = NULL;
1459 lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1460 roomname, msgid, flags);
1462 strcpy(hold_rm, CC->quickroom.QRname);
1464 /* We may need to check to see if this message is real */
1465 if ( (flags & SM_VERIFY_GOODNESS)
1466 || (flags & SM_DO_REPL_CHECK)
1468 msg = CtdlFetchMessage(msgid);
1469 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1472 /* Perform replication checks if necessary */
1473 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1475 if (getroom(&CC->quickroom,
1476 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1478 lprintf(9, "No such room <%s>\n", roomname);
1479 if (msg != NULL) CtdlFreeMessage(msg);
1480 return(ERROR + ROOM_NOT_FOUND);
1483 if (ReplicationChecks(msg) != 0) {
1484 getroom(&CC->quickroom, hold_rm);
1485 if (msg != NULL) CtdlFreeMessage(msg);
1486 lprintf(9, "Did replication, and newer exists\n");
1491 /* Now the regular stuff */
1492 if (lgetroom(&CC->quickroom,
1493 ((roomname != NULL) ? roomname : CC->quickroom.QRname) )
1495 lprintf(9, "No such room <%s>\n", roomname);
1496 if (msg != NULL) CtdlFreeMessage(msg);
1497 return(ERROR + ROOM_NOT_FOUND);
1500 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long));
1501 if (cdbfr == NULL) {
1505 msglist = mallok(cdbfr->len);
1506 if (msglist == NULL)
1507 lprintf(3, "ERROR malloc msglist!\n");
1508 num_msgs = cdbfr->len / sizeof(long);
1509 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1514 /* Make sure the message doesn't already exist in this room. It
1515 * is absolutely taboo to have more than one reference to the same
1516 * message in a room.
1518 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1519 if (msglist[i] == msgid) {
1520 lputroom(&CC->quickroom); /* unlock the room */
1521 getroom(&CC->quickroom, hold_rm);
1522 if (msg != NULL) CtdlFreeMessage(msg);
1523 return(ERROR + ALREADY_EXISTS);
1527 /* Now add the new message */
1529 msglist = reallok(msglist,
1530 (num_msgs * sizeof(long)));
1532 if (msglist == NULL) {
1533 lprintf(3, "ERROR: can't realloc message list!\n");
1535 msglist[num_msgs - 1] = msgid;
1537 /* Sort the message list, so all the msgid's are in order */
1538 num_msgs = sort_msglist(msglist, num_msgs);
1540 /* Determine the highest message number */
1541 highest_msg = msglist[num_msgs - 1];
1543 /* Write it back to disk. */
1544 cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long),
1545 msglist, num_msgs * sizeof(long));
1547 /* Free up the memory we used. */
1550 /* Update the highest-message pointer and unlock the room. */
1551 CC->quickroom.QRhighest = highest_msg;
1552 lputroom(&CC->quickroom);
1553 getroom(&CC->quickroom, hold_rm);
1555 /* Bump the reference count for this message. */
1556 if ((flags & SM_DONT_BUMP_REF)==0) {
1557 AdjRefCount(msgid, +1);
1560 /* Return success. */
1561 if (msg != NULL) CtdlFreeMessage(msg);
1568 * Message base operation to send a message to the master file
1569 * (returns new message number)
1571 * This is the back end for CtdlSubmitMsg() and should not be directly
1572 * called by server-side modules.
1575 long send_message(struct CtdlMessage *msg, /* pointer to buffer */
1576 FILE *save_a_copy) /* save a copy to disk? */
1583 /* Get a new message number */
1584 newmsgid = get_new_message_number();
1585 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1587 /* Generate an ID if we don't have one already */
1588 if (msg->cm_fields['I']==NULL) {
1589 msg->cm_fields['I'] = strdoop(msgidbuf);
1592 serialize_message(&smr, msg);
1595 cprintf("%d Unable to serialize message\n",
1596 ERROR+INTERNAL_ERROR);
1600 /* Write our little bundle of joy into the message base */
1601 if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
1602 smr.ser, smr.len) < 0) {
1603 lprintf(2, "Can't store message\n");
1609 /* If the caller specified that a copy should be saved to a particular
1610 * file handle, do that now too.
1612 if (save_a_copy != NULL) {
1613 fwrite(smr.ser, smr.len, 1, save_a_copy);
1616 /* Free the memory we used for the serialized message */
1619 /* Return the *local* message ID to the caller
1620 * (even if we're storing an incoming network message)
1628 * Serialize a struct CtdlMessage into the format used on disk and network.
1630 * This function loads up a "struct ser_ret" (defined in server.h) which
1631 * contains the length of the serialized message and a pointer to the
1632 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1634 void serialize_message(struct ser_ret *ret, /* return values */
1635 struct CtdlMessage *msg) /* unserialized msg */
1639 static char *forder = FORDER;
1641 if (is_valid_message(msg) == 0) return; /* self check */
1644 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1645 ret->len = ret->len +
1646 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1648 lprintf(9, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1649 ret->ser = mallok(ret->len);
1650 if (ret->ser == NULL) {
1656 ret->ser[1] = msg->cm_anon_type;
1657 ret->ser[2] = msg->cm_format_type;
1660 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1661 ret->ser[wlen++] = (char)forder[i];
1662 strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1663 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1665 if (ret->len != wlen) lprintf(3, "ERROR: len=%ld wlen=%ld\n",
1666 (long)ret->len, (long)wlen);
1674 * Back end for the ReplicationChecks() function
1676 void check_repl(long msgnum, void *userdata) {
1677 struct CtdlMessage *msg;
1678 time_t timestamp = (-1L);
1680 lprintf(9, "check_repl() found message %ld\n", msgnum);
1681 msg = CtdlFetchMessage(msgnum);
1682 if (msg == NULL) return;
1683 if (msg->cm_fields['T'] != NULL) {
1684 timestamp = atol(msg->cm_fields['T']);
1686 CtdlFreeMessage(msg);
1688 if (timestamp > msg_repl->highest) {
1689 msg_repl->highest = timestamp; /* newer! */
1690 lprintf(9, "newer!\n");
1693 lprintf(9, "older!\n");
1695 /* Existing isn't newer? Then delete the old one(s). */
1696 CtdlDeleteMessages(CC->quickroom.QRname, msgnum, "");
1701 * Check to see if any messages already exist which carry the same Extended ID
1705 * -> With older timestamps: delete them and return 0. Message will be saved.
1706 * -> With newer timestamps: return 1. Message save will be aborted.
1708 int ReplicationChecks(struct CtdlMessage *msg) {
1709 struct CtdlMessage *template;
1712 lprintf(9, "ReplicationChecks() started\n");
1713 /* No extended id? Don't do anything. */
1714 if (msg->cm_fields['E'] == NULL) return 0;
1715 if (strlen(msg->cm_fields['E']) == 0) return 0;
1716 lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']);
1718 CtdlAllocUserData(SYM_REPL, sizeof(struct repl));
1719 strcpy(msg_repl->extended_id, msg->cm_fields['E']);
1720 msg_repl->highest = atol(msg->cm_fields['T']);
1722 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1723 memset(template, 0, sizeof(struct CtdlMessage));
1724 template->cm_fields['E'] = strdoop(msg->cm_fields['E']);
1726 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1728 /* If a newer message exists with the same Extended ID, abort
1731 if (msg_repl->highest > atol(msg->cm_fields['T']) ) {
1735 CtdlFreeMessage(template);
1736 lprintf(9, "ReplicationChecks() returning %d\n", abort_this);
1744 * Save a message to disk and submit it into the delivery system.
1746 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1747 struct recptypes *recps, /* recipients (if mail) */
1748 char *force /* force a particular room? */
1751 char hold_rm[ROOMNAMELEN];
1752 char actual_rm[ROOMNAMELEN];
1753 char force_room[ROOMNAMELEN];
1754 char content_type[SIZ]; /* We have to learn this */
1755 char recipient[SIZ];
1758 struct usersupp userbuf;
1760 struct MetaData smi;
1761 FILE *network_fp = NULL;
1762 static int seqnum = 1;
1763 struct CtdlMessage *imsg = NULL;
1766 char *hold_R, *hold_D;
1768 lprintf(9, "CtdlSubmitMsg() called\n");
1769 if (is_valid_message(msg) == 0) return(-1); /* self check */
1771 /* If this message has no timestamp, we take the liberty of
1772 * giving it one, right now.
1774 if (msg->cm_fields['T'] == NULL) {
1775 lprintf(9, "Generating timestamp\n");
1776 snprintf(aaa, sizeof aaa, "%ld", (long)time(NULL));
1777 msg->cm_fields['T'] = strdoop(aaa);
1780 /* If this message has no path, we generate one.
1782 if (msg->cm_fields['P'] == NULL) {
1783 lprintf(9, "Generating path\n");
1784 if (msg->cm_fields['A'] != NULL) {
1785 msg->cm_fields['P'] = strdoop(msg->cm_fields['A']);
1786 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1787 if (isspace(msg->cm_fields['P'][a])) {
1788 msg->cm_fields['P'][a] = ' ';
1793 msg->cm_fields['P'] = strdoop("unknown");
1797 strcpy(force_room, force);
1799 /* Learn about what's inside, because it's what's inside that counts */
1800 lprintf(9, "Learning what's inside\n");
1801 if (msg->cm_fields['M'] == NULL) {
1802 lprintf(1, "ERROR: attempt to save message with NULL body\n");
1805 switch (msg->cm_format_type) {
1807 strcpy(content_type, "text/x-citadel-variformat");
1810 strcpy(content_type, "text/plain");
1813 strcpy(content_type, "text/plain");
1814 /* advance past header fields */
1815 mptr = msg->cm_fields['M'];
1818 if (!strncasecmp(mptr, "Content-type: ", 14)) {
1819 safestrncpy(content_type, mptr,
1820 sizeof(content_type));
1821 strcpy(content_type, &content_type[14]);
1822 for (a = 0; a < strlen(content_type); ++a)
1823 if ((content_type[a] == ';')
1824 || (content_type[a] == ' ')
1825 || (content_type[a] == 13)
1826 || (content_type[a] == 10))
1827 content_type[a] = 0;
1834 /* Goto the correct room */
1835 lprintf(9, "Switching rooms\n");
1836 strcpy(hold_rm, CC->quickroom.QRname);
1837 strcpy(actual_rm, CC->quickroom.QRname);
1838 if (recps != NULL) {
1839 strcpy(actual_rm, SENTITEMS);
1842 /* If the user is a twit, move to the twit room for posting */
1843 lprintf(9, "Handling twit stuff\n");
1845 if (CC->usersupp.axlevel == 2) {
1846 strcpy(hold_rm, actual_rm);
1847 strcpy(actual_rm, config.c_twitroom);
1851 /* ...or if this message is destined for Aide> then go there. */
1852 if (strlen(force_room) > 0) {
1853 strcpy(actual_rm, force_room);
1856 lprintf(9, "Possibly relocating\n");
1857 if (strcasecmp(actual_rm, CC->quickroom.QRname)) {
1858 getroom(&CC->quickroom, actual_rm);
1862 * If this message has no O (room) field, generate one.
1864 if (msg->cm_fields['O'] == NULL) {
1865 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
1868 /* Perform "before save" hooks (aborting if any return nonzero) */
1869 lprintf(9, "Performing before-save hooks\n");
1870 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-1);
1872 /* If this message has an Extended ID, perform replication checks */
1873 lprintf(9, "Performing replication checks\n");
1874 if (ReplicationChecks(msg) > 0) return(-1);
1876 /* Save it to disk */
1877 lprintf(9, "Saving to disk\n");
1878 newmsgid = send_message(msg, NULL);
1879 if (newmsgid <= 0L) return(-1);
1881 /* Write a supplemental message info record. This doesn't have to
1882 * be a critical section because nobody else knows about this message
1885 lprintf(9, "Creating MetaData record\n");
1886 memset(&smi, 0, sizeof(struct MetaData));
1887 smi.meta_msgnum = newmsgid;
1888 smi.meta_refcount = 0;
1889 safestrncpy(smi.meta_content_type, content_type, 64);
1892 /* Now figure out where to store the pointers */
1893 lprintf(9, "Storing pointers\n");
1895 /* If this is being done by the networker delivering a private
1896 * message, we want to BYPASS saving the sender's copy (because there
1897 * is no local sender; it would otherwise go to the Trashcan).
1899 if ((!CC->internal_pgm) || (recps == NULL)) {
1900 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
1901 lprintf(3, "ERROR saving message pointer!\n");
1902 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
1906 /* For internet mail, drop a copy in the outbound queue room */
1908 if (recps->num_internet > 0) {
1909 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
1912 /* If other rooms are specified, drop them there too. */
1914 if (recps->num_room > 0)
1915 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
1916 extract(recipient, recps->recp_room, i);
1917 lprintf(9, "Delivering to local room <%s>\n", recipient);
1918 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
1921 /* Bump this user's messages posted counter. */
1922 lprintf(9, "Updating user\n");
1923 lgetuser(&CC->usersupp, CC->curr_user);
1924 CC->usersupp.posted = CC->usersupp.posted + 1;
1925 lputuser(&CC->usersupp);
1927 /* If this is private, local mail, make a copy in the
1928 * recipient's mailbox and bump the reference count.
1931 if (recps->num_local > 0)
1932 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
1933 extract(recipient, recps->recp_local, i);
1934 lprintf(9, "Delivering private local mail to <%s>\n",
1936 if (getuser(&userbuf, recipient) == 0) {
1937 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
1938 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
1939 BumpNewMailCounter(userbuf.usernum);
1942 lprintf(9, "No user <%s>\n", recipient);
1943 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
1947 /* Perform "after save" hooks */
1948 lprintf(9, "Performing after-save hooks\n");
1949 PerformMessageHooks(msg, EVT_AFTERSAVE);
1951 /* For IGnet mail, we have to save a new copy into the spooler for
1952 * each recipient, with the R and D fields set to the recipient and
1953 * destination-node. This has two ugly side effects: all other
1954 * recipients end up being unlisted in this recipient's copy of the
1955 * message, and it has to deliver multiple messages to the same
1956 * node. We'll revisit this again in a year or so when everyone has
1957 * a network spool receiver that can handle the new style messages.
1960 if (recps->num_ignet > 0)
1961 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
1962 extract(recipient, recps->recp_ignet, i);
1964 hold_R = msg->cm_fields['R'];
1965 hold_D = msg->cm_fields['D'];
1966 msg->cm_fields['R'] = mallok(SIZ);
1967 msg->cm_fields['D'] = mallok(SIZ);
1968 extract_token(msg->cm_fields['R'], recipient, 0, '@');
1969 extract_token(msg->cm_fields['D'], recipient, 1, '@');
1971 serialize_message(&smr, msg);
1973 snprintf(aaa, sizeof aaa,
1974 "./network/spoolin/netmail.%04lx.%04x.%04x",
1975 (long) getpid(), CC->cs_pid, ++seqnum);
1976 network_fp = fopen(aaa, "wb+");
1977 if (network_fp != NULL) {
1978 fwrite(smr.ser, smr.len, 1, network_fp);
1984 phree(msg->cm_fields['R']);
1985 phree(msg->cm_fields['D']);
1986 msg->cm_fields['R'] = hold_R;
1987 msg->cm_fields['D'] = hold_D;
1990 /* Go back to the room we started from */
1991 lprintf(9, "Returning to original room\n");
1992 if (strcasecmp(hold_rm, CC->quickroom.QRname))
1993 getroom(&CC->quickroom, hold_rm);
1995 /* For internet mail, generate delivery instructions.
1996 * Yes, this is recursive. Deal with it. Infinite recursion does
1997 * not happen because the delivery instructions message does not
1998 * contain a recipient.
2001 if (recps->num_internet > 0) {
2002 lprintf(9, "Generating delivery instructions\n");
2003 instr = mallok(SIZ * 2);
2004 snprintf(instr, SIZ * 2,
2005 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2007 SPOOLMIME, newmsgid, (long)time(NULL),
2008 msg->cm_fields['A'], msg->cm_fields['N']
2011 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2012 size_t tmp = strlen(instr);
2013 extract(recipient, recps->recp_internet, i);
2014 snprintf(&instr[tmp], SIZ * 2 - tmp,
2015 "remote|%s|0||\n", recipient);
2018 imsg = mallok(sizeof(struct CtdlMessage));
2019 memset(imsg, 0, sizeof(struct CtdlMessage));
2020 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2021 imsg->cm_anon_type = MES_NORMAL;
2022 imsg->cm_format_type = FMT_RFC822;
2023 imsg->cm_fields['A'] = strdoop("Citadel");
2024 imsg->cm_fields['M'] = instr;
2025 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2026 CtdlFreeMessage(imsg);
2035 * Convenience function for generating small administrative messages.
2037 void quickie_message(char *from, char *to, char *room, char *text)
2039 struct CtdlMessage *msg;
2041 msg = mallok(sizeof(struct CtdlMessage));
2042 memset(msg, 0, sizeof(struct CtdlMessage));
2043 msg->cm_magic = CTDLMESSAGE_MAGIC;
2044 msg->cm_anon_type = MES_NORMAL;
2045 msg->cm_format_type = 0;
2046 msg->cm_fields['A'] = strdoop(from);
2047 msg->cm_fields['O'] = strdoop(room);
2048 msg->cm_fields['N'] = strdoop(NODENAME);
2050 msg->cm_fields['R'] = strdoop(to);
2051 msg->cm_fields['M'] = strdoop(text);
2053 CtdlSubmitMsg(msg, NULL, room);
2054 CtdlFreeMessage(msg);
2055 syslog(LOG_NOTICE, text);
2061 * Back end function used by make_message() and similar functions
2063 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2064 size_t maxlen, /* maximum message length */
2065 char *exist /* if non-null, append to it;
2066 exist is ALWAYS freed */
2070 size_t message_len = 0;
2071 size_t buffer_len = 0;
2075 if (exist == NULL) {
2082 message_len = strlen(exist);
2083 buffer_len = message_len + 4096;
2084 m = reallok(exist, buffer_len);
2091 /* flush the input if we have nowhere to store it */
2093 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;;
2097 /* read in the lines of message text one by one */
2098 while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) {
2100 /* strip trailing newline type stuff */
2101 if (buf[strlen(buf)-1]==10) buf[strlen(buf)-1]=0;
2102 if (buf[strlen(buf)-1]==13) buf[strlen(buf)-1]=0;
2104 linelen = strlen(buf);
2106 /* augment the buffer if we have to */
2107 if ((message_len + linelen + 2) > buffer_len) {
2108 lprintf(9, "realloking\n");
2109 ptr = reallok(m, (buffer_len * 2) );
2110 if (ptr == NULL) { /* flush if can't allocate */
2111 while ( (client_gets(buf)>0) &&
2112 strcmp(buf, terminator)) ;;
2115 buffer_len = (buffer_len * 2);
2117 lprintf(9, "buffer_len is %ld\n", (long)buffer_len);
2121 /* Add the new line to the buffer. NOTE: this loop must avoid
2122 * using functions like strcat() and strlen() because they
2123 * traverse the entire buffer upon every call, and doing that
2124 * for a multi-megabyte message slows it down beyond usability.
2126 strcpy(&m[message_len], buf);
2127 m[message_len + linelen] = '\n';
2128 m[message_len + linelen + 1] = 0;
2129 message_len = message_len + linelen + 1;
2131 /* if we've hit the max msg length, flush the rest */
2132 if (message_len >= maxlen) {
2133 while ( (client_gets(buf)>0)
2134 && strcmp(buf, terminator)) ;;
2145 * Build a binary message to be saved on disk.
2148 static struct CtdlMessage *make_message(
2149 struct usersupp *author, /* author's usersupp structure */
2150 char *recipient, /* NULL if it's not mail */
2151 char *room, /* room where it's going */
2152 int type, /* see MES_ types in header file */
2153 int format_type, /* variformat, plain text, MIME... */
2154 char *fake_name, /* who we're masquerading as */
2155 char *subject /* Subject (optional) */
2157 char dest_node[SIZ];
2159 struct CtdlMessage *msg;
2161 msg = mallok(sizeof(struct CtdlMessage));
2162 memset(msg, 0, sizeof(struct CtdlMessage));
2163 msg->cm_magic = CTDLMESSAGE_MAGIC;
2164 msg->cm_anon_type = type;
2165 msg->cm_format_type = format_type;
2167 /* Don't confuse the poor folks if it's not routed mail. */
2168 strcpy(dest_node, "");
2172 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2173 msg->cm_fields['P'] = strdoop(buf);
2175 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2176 msg->cm_fields['T'] = strdoop(buf);
2178 if (fake_name[0]) /* author */
2179 msg->cm_fields['A'] = strdoop(fake_name);
2181 msg->cm_fields['A'] = strdoop(author->fullname);
2183 if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */
2184 msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]);
2187 msg->cm_fields['O'] = strdoop(CC->quickroom.QRname);
2190 msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */
2191 msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */
2193 if (recipient[0] != 0) {
2194 msg->cm_fields['R'] = strdoop(recipient);
2196 if (dest_node[0] != 0) {
2197 msg->cm_fields['D'] = strdoop(dest_node);
2200 if ( (author == &CC->usersupp) && (CC->cs_inet_email != NULL) ) {
2201 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
2204 if (subject != NULL) {
2206 if (strlen(subject) > 0) {
2207 msg->cm_fields['U'] = strdoop(subject);
2211 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2212 config.c_maxmsglen, NULL);
2219 * Check to see whether we have permission to post a message in the current
2220 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2221 * returns 0 on success.
2223 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2225 if (!(CC->logged_in)) {
2226 snprintf(errmsgbuf, n, "Not logged in.");
2227 return (ERROR + NOT_LOGGED_IN);
2230 if ((CC->usersupp.axlevel < 2)
2231 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
2232 snprintf(errmsgbuf, n, "Need to be validated to enter "
2233 "(except in %s> to sysop)", MAILROOM);
2234 return (ERROR + HIGHER_ACCESS_REQUIRED);
2237 if ((CC->usersupp.axlevel < 4)
2238 && (CC->quickroom.QRflags & QR_NETWORK)) {
2239 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2240 return (ERROR + HIGHER_ACCESS_REQUIRED);
2243 if ((CC->usersupp.axlevel < 6)
2244 && (CC->quickroom.QRflags & QR_READONLY)) {
2245 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2246 return (ERROR + HIGHER_ACCESS_REQUIRED);
2249 strcpy(errmsgbuf, "Ok");
2255 * Validate recipients, count delivery types and errors, and handle aliasing
2256 * FIXME check for dupes!!!!!
2258 struct recptypes *validate_recipients(char *recipients) {
2259 struct recptypes *ret;
2260 char this_recp[SIZ];
2261 char this_recp_cooked[SIZ];
2267 struct usersupp tempUS;
2268 struct quickroom tempQR;
2271 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2272 if (ret == NULL) return(NULL);
2273 memset(ret, 0, sizeof(struct recptypes));
2276 ret->num_internet = 0;
2281 if (recipients == NULL) {
2284 else if (strlen(recipients) == 0) {
2288 /* Change all valid separator characters to commas */
2289 for (i=0; i<strlen(recipients); ++i) {
2290 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2291 recipients[i] = ',';
2296 num_recps = num_tokens(recipients, ',');
2299 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2300 extract_token(this_recp, recipients, i, ',');
2302 lprintf(9, "Evaluating recipient #%d <%s>\n", i, this_recp);
2303 mailtype = alias(this_recp);
2304 mailtype = alias(this_recp);
2305 mailtype = alias(this_recp);
2306 for (j=0; j<=strlen(this_recp); ++j) {
2307 if (this_recp[j]=='_') {
2308 this_recp_cooked[j] = ' ';
2311 this_recp_cooked[j] = this_recp[j];
2317 if (!strcasecmp(this_recp, "sysop")) {
2319 strcpy(this_recp, config.c_aideroom);
2320 if (strlen(ret->recp_room) > 0) {
2321 strcat(ret->recp_room, "|");
2323 strcat(ret->recp_room, this_recp);
2325 else if (getuser(&tempUS, this_recp) == 0) {
2327 strcpy(this_recp, tempUS.fullname);
2328 if (strlen(ret->recp_local) > 0) {
2329 strcat(ret->recp_local, "|");
2331 strcat(ret->recp_local, this_recp);
2333 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2335 strcpy(this_recp, tempUS.fullname);
2336 if (strlen(ret->recp_local) > 0) {
2337 strcat(ret->recp_local, "|");
2339 strcat(ret->recp_local, this_recp);
2341 else if ( (!strncasecmp(this_recp, "room_", 5))
2342 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2344 if (strlen(ret->recp_room) > 0) {
2345 strcat(ret->recp_room, "|");
2347 strcat(ret->recp_room, &this_recp_cooked[5]);
2355 ++ret->num_internet;
2356 if (strlen(ret->recp_internet) > 0) {
2357 strcat(ret->recp_internet, "|");
2359 strcat(ret->recp_internet, this_recp);
2363 if (strlen(ret->recp_ignet) > 0) {
2364 strcat(ret->recp_ignet, "|");
2366 strcat(ret->recp_ignet, this_recp);
2374 if (strlen(ret->errormsg) == 0) {
2375 snprintf(append, sizeof append,
2376 "Invalid recipient: %s",
2380 snprintf(append, sizeof append,
2383 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2384 strcat(ret->errormsg, append);
2388 if (strlen(ret->display_recp) == 0) {
2389 strcpy(append, this_recp);
2392 snprintf(append, sizeof append, ", %s",
2395 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2396 strcat(ret->display_recp, append);
2401 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2402 ret->num_room + ret->num_error) == 0) {
2404 strcpy(ret->errormsg, "No recipients specified.");
2407 lprintf(9, "validate_recipients()\n");
2408 lprintf(9, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2409 lprintf(9, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2410 lprintf(9, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2411 lprintf(9, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2412 lprintf(9, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2420 * message entry - mode 0 (normal)
2422 void cmd_ent0(char *entargs)
2426 char masquerade_as[SIZ];
2428 int format_type = 0;
2429 char newusername[SIZ];
2430 struct CtdlMessage *msg;
2434 struct recptypes *valid = NULL;
2437 post = extract_int(entargs, 0);
2438 extract(recp, entargs, 1);
2439 anon_flag = extract_int(entargs, 2);
2440 format_type = extract_int(entargs, 3);
2441 extract(subject, entargs, 4);
2443 /* first check to make sure the request is valid. */
2445 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2447 cprintf("%d %s\n", err, errmsg);
2451 /* Check some other permission type things. */
2454 if (CC->usersupp.axlevel < 6) {
2455 cprintf("%d You don't have permission to masquerade.\n",
2456 ERROR + HIGHER_ACCESS_REQUIRED);
2459 extract(newusername, entargs, 4);
2460 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2461 safestrncpy(CC->fake_postname, newusername,
2462 sizeof(CC->fake_postname) );
2463 cprintf("%d ok\n", CIT_OK);
2466 CC->cs_flags |= CS_POSTING;
2468 /* In the Mail> room we have to behave a little differently --
2469 * make sure the user has specified at least one recipient. Then
2470 * validate the recipient(s).
2472 if ( (CC->quickroom.QRflags & QR_MAILBOX)
2473 && (!strcasecmp(&CC->quickroom.QRname[11], MAILROOM)) ) {
2475 if (CC->usersupp.axlevel < 2) {
2476 strcpy(recp, "sysop");
2479 valid = validate_recipients(recp);
2480 if (valid->num_error > 0) {
2482 ERROR + NO_SUCH_USER, valid->errormsg);
2487 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2488 && (CC->usersupp.axlevel < 4) ) {
2489 cprintf("%d Higher access required for network mail.\n",
2490 ERROR + HIGHER_ACCESS_REQUIRED);
2495 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2496 && ((CC->usersupp.flags & US_INTERNET) == 0)
2497 && (!CC->internal_pgm)) {
2498 cprintf("%d You don't have access to Internet mail.\n",
2499 ERROR + HIGHER_ACCESS_REQUIRED);
2506 /* Is this a room which has anonymous-only or anonymous-option? */
2507 anonymous = MES_NORMAL;
2508 if (CC->quickroom.QRflags & QR_ANONONLY) {
2509 anonymous = MES_ANONONLY;
2511 if (CC->quickroom.QRflags & QR_ANONOPT) {
2512 if (anon_flag == 1) { /* only if the user requested it */
2513 anonymous = MES_ANONOPT;
2517 if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) {
2521 /* If we're only checking the validity of the request, return
2522 * success without creating the message.
2525 cprintf("%d %s\n", CIT_OK,
2526 ((valid != NULL) ? valid->display_recp : "") );
2531 /* Handle author masquerading */
2532 if (CC->fake_postname[0]) {
2533 strcpy(masquerade_as, CC->fake_postname);
2535 else if (CC->fake_username[0]) {
2536 strcpy(masquerade_as, CC->fake_username);
2539 strcpy(masquerade_as, "");
2542 /* Read in the message from the client. */
2543 cprintf("%d send message\n", SEND_LISTING);
2544 msg = make_message(&CC->usersupp, recp,
2545 CC->quickroom.QRname, anonymous, format_type,
2546 masquerade_as, subject);
2549 CtdlSubmitMsg(msg, valid, "");
2550 CtdlFreeMessage(msg);
2552 CC->fake_postname[0] = '\0';
2560 * API function to delete messages which match a set of criteria
2561 * (returns the actual number of messages deleted)
2563 int CtdlDeleteMessages(char *room_name, /* which room */
2564 long dmsgnum, /* or "0" for any */
2565 char *content_type /* or "" for any */
2569 struct quickroom qrbuf;
2570 struct cdbdata *cdbfr;
2571 long *msglist = NULL;
2572 long *dellist = NULL;
2575 int num_deleted = 0;
2577 struct MetaData smi;
2579 lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n",
2580 room_name, dmsgnum, content_type);
2582 /* get room record, obtaining a lock... */
2583 if (lgetroom(&qrbuf, room_name) != 0) {
2584 lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n",
2586 return (0); /* room not found */
2588 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2590 if (cdbfr != NULL) {
2591 msglist = mallok(cdbfr->len);
2592 dellist = mallok(cdbfr->len);
2593 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2594 num_msgs = cdbfr->len / sizeof(long);
2598 for (i = 0; i < num_msgs; ++i) {
2601 /* Set/clear a bit for each criterion */
2603 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2604 delete_this |= 0x01;
2606 if (strlen(content_type) == 0) {
2607 delete_this |= 0x02;
2609 GetMetaData(&smi, msglist[i]);
2610 if (!strcasecmp(smi.meta_content_type,
2612 delete_this |= 0x02;
2616 /* Delete message only if all bits are set */
2617 if (delete_this == 0x03) {
2618 dellist[num_deleted++] = msglist[i];
2623 num_msgs = sort_msglist(msglist, num_msgs);
2624 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long),
2625 msglist, (num_msgs * sizeof(long)));
2627 qrbuf.QRhighest = msglist[num_msgs - 1];
2631 /* Go through the messages we pulled out of the index, and decrement
2632 * their reference counts by 1. If this is the only room the message
2633 * was in, the reference count will reach zero and the message will
2634 * automatically be deleted from the database. We do this in a
2635 * separate pass because there might be plug-in hooks getting called,
2636 * and we don't want that happening during an S_QUICKROOM critical
2639 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2640 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2641 AdjRefCount(dellist[i], -1);
2644 /* Now free the memory we used, and go away. */
2645 if (msglist != NULL) phree(msglist);
2646 if (dellist != NULL) phree(dellist);
2647 lprintf(9, "%d message(s) deleted.\n", num_deleted);
2648 return (num_deleted);
2654 * Check whether the current user has permission to delete messages from
2655 * the current room (returns 1 for yes, 0 for no)
2657 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2658 getuser(&CC->usersupp, CC->curr_user);
2659 if ((CC->usersupp.axlevel < 6)
2660 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2661 && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)
2662 && (!(CC->internal_pgm))) {
2671 * Delete message from current room
2673 void cmd_dele(char *delstr)
2678 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2679 cprintf("%d Higher access required.\n",
2680 ERROR + HIGHER_ACCESS_REQUIRED);
2683 delnum = extract_long(delstr, 0);
2685 num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, "");
2688 cprintf("%d %d message%s deleted.\n", CIT_OK,
2689 num_deleted, ((num_deleted != 1) ? "s" : ""));
2691 cprintf("%d Message %ld not found.\n", ERROR, delnum);
2697 * Back end API function for moves and deletes
2699 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2702 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2703 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2704 if (err != 0) return(err);
2712 * move or copy a message to another room
2714 void cmd_move(char *args)
2718 struct quickroom qtemp;
2722 num = extract_long(args, 0);
2723 extract(targ, args, 1);
2724 targ[ROOMNAMELEN - 1] = 0;
2725 is_copy = extract_int(args, 2);
2727 if (getroom(&qtemp, targ) != 0) {
2728 cprintf("%d '%s' does not exist.\n", ERROR, targ);
2732 getuser(&CC->usersupp, CC->curr_user);
2733 /* Aides can move/copy */
2734 if ((CC->usersupp.axlevel < 6)
2735 /* Roomaides can move/copy */
2736 && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
2737 /* Permit move/copy to/from personal rooms */
2738 && (!((CC->quickroom.QRflags & QR_MAILBOX)
2739 && (qtemp.QRflags & QR_MAILBOX)))
2740 /* Permit only copy from public to personal room */
2741 && (!(is_copy && !(CC->quickroom.QRflags & QR_MAILBOX)
2742 && (qtemp.QRflags & QR_MAILBOX)))) {
2743 cprintf("%d Higher access required.\n",
2744 ERROR + HIGHER_ACCESS_REQUIRED);
2748 err = CtdlCopyMsgToRoom(num, targ);
2750 cprintf("%d Cannot store message in %s: error %d\n",
2755 /* Now delete the message from the source room,
2756 * if this is a 'move' rather than a 'copy' operation.
2759 CtdlDeleteMessages(CC->quickroom.QRname, num, "");
2762 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
2768 * GetMetaData() - Get the supplementary record for a message
2770 void GetMetaData(struct MetaData *smibuf, long msgnum)
2773 struct cdbdata *cdbsmi;
2776 memset(smibuf, 0, sizeof(struct MetaData));
2777 smibuf->meta_msgnum = msgnum;
2778 smibuf->meta_refcount = 1; /* Default reference count is 1 */
2780 /* Use the negative of the message number for its supp record index */
2781 TheIndex = (0L - msgnum);
2783 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
2784 if (cdbsmi == NULL) {
2785 return; /* record not found; go with defaults */
2787 memcpy(smibuf, cdbsmi->ptr,
2788 ((cdbsmi->len > sizeof(struct MetaData)) ?
2789 sizeof(struct MetaData) : cdbsmi->len));
2796 * PutMetaData() - (re)write supplementary record for a message
2798 void PutMetaData(struct MetaData *smibuf)
2802 /* Use the negative of the message number for the metadata db index */
2803 TheIndex = (0L - smibuf->meta_msgnum);
2805 lprintf(9, "PutMetaData(%ld) - ref count is %d\n",
2806 smibuf->meta_msgnum, smibuf->meta_refcount);
2808 cdb_store(CDB_MSGMAIN,
2809 &TheIndex, sizeof(long),
2810 smibuf, sizeof(struct MetaData));
2815 * AdjRefCount - change the reference count for a message;
2816 * delete the message if it reaches zero
2818 void AdjRefCount(long msgnum, int incr)
2821 struct MetaData smi;
2824 /* This is a *tight* critical section; please keep it that way, as
2825 * it may get called while nested in other critical sections.
2826 * Complicating this any further will surely cause deadlock!
2828 begin_critical_section(S_SUPPMSGMAIN);
2829 GetMetaData(&smi, msgnum);
2830 lprintf(9, "Ref count for message <%ld> before write is <%d>\n",
2831 msgnum, smi.meta_refcount);
2832 smi.meta_refcount += incr;
2834 end_critical_section(S_SUPPMSGMAIN);
2835 lprintf(9, "Ref count for message <%ld> after write is <%d>\n",
2836 msgnum, smi.meta_refcount);
2838 /* If the reference count is now zero, delete the message
2839 * (and its supplementary record as well).
2841 if (smi.meta_refcount == 0) {
2842 lprintf(9, "Deleting message <%ld>\n", msgnum);
2844 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2846 /* We have to delete the metadata record too! */
2847 delnum = (0L - msgnum);
2848 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
2853 * Write a generic object to this room
2855 * Note: this could be much more efficient. Right now we use two temporary
2856 * files, and still pull the message into memory as with all others.
2858 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
2859 char *content_type, /* MIME type of this object */
2860 char *tempfilename, /* Where to fetch it from */
2861 struct usersupp *is_mailbox, /* Mailbox room? */
2862 int is_binary, /* Is encoding necessary? */
2863 int is_unique, /* Del others of this type? */
2864 unsigned int flags /* Internal save flags */
2869 char filename[PATH_MAX];
2872 struct quickroom qrbuf;
2873 char roomname[ROOMNAMELEN];
2874 struct CtdlMessage *msg;
2877 if (is_mailbox != NULL)
2878 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
2880 safestrncpy(roomname, req_room, sizeof(roomname));
2881 lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
2883 strcpy(filename, tmpnam(NULL));
2884 fp = fopen(filename, "w");
2888 tempfp = fopen(tempfilename, "r");
2889 if (tempfp == NULL) {
2895 fprintf(fp, "Content-type: %s\n", content_type);
2896 lprintf(9, "Content-type: %s\n", content_type);
2898 if (is_binary == 0) {
2899 fprintf(fp, "Content-transfer-encoding: 7bit\n\n");
2900 while (ch = getc(tempfp), ch > 0)
2906 fprintf(fp, "Content-transfer-encoding: base64\n\n");
2909 snprintf(cmdbuf, sizeof cmdbuf, "./base64 -e <%s >>%s",
2910 tempfilename, filename);
2914 lprintf(9, "Allocating\n");
2915 msg = mallok(sizeof(struct CtdlMessage));
2916 memset(msg, 0, sizeof(struct CtdlMessage));
2917 msg->cm_magic = CTDLMESSAGE_MAGIC;
2918 msg->cm_anon_type = MES_NORMAL;
2919 msg->cm_format_type = 4;
2920 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
2921 msg->cm_fields['O'] = strdoop(req_room);
2922 msg->cm_fields['N'] = strdoop(config.c_nodename);
2923 msg->cm_fields['H'] = strdoop(config.c_humannode);
2924 msg->cm_flags = flags;
2926 lprintf(9, "Loading\n");
2927 fp = fopen(filename, "rb");
2928 fseek(fp, 0L, SEEK_END);
2931 msg->cm_fields['M'] = mallok(len);
2932 fread(msg->cm_fields['M'], len, 1, fp);
2936 /* Create the requested room if we have to. */
2937 if (getroom(&qrbuf, roomname) != 0) {
2938 create_room(roomname,
2939 ( (is_mailbox != NULL) ? 5 : 3 ),
2942 /* If the caller specified this object as unique, delete all
2943 * other objects of this type that are currently in the room.
2946 lprintf(9, "Deleted %d other msgs of this type\n",
2947 CtdlDeleteMessages(roomname, 0L, content_type));
2949 /* Now write the data */
2950 CtdlSubmitMsg(msg, NULL, roomname);
2951 CtdlFreeMessage(msg);
2959 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
2960 config_msgnum = msgnum;
2964 char *CtdlGetSysConfig(char *sysconfname) {
2965 char hold_rm[ROOMNAMELEN];
2968 struct CtdlMessage *msg;
2971 strcpy(hold_rm, CC->quickroom.QRname);
2972 if (getroom(&CC->quickroom, SYSCONFIGROOM) != 0) {
2973 getroom(&CC->quickroom, hold_rm);
2978 /* We want the last (and probably only) config in this room */
2979 begin_critical_section(S_CONFIG);
2980 config_msgnum = (-1L);
2981 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
2982 CtdlGetSysConfigBackend, NULL);
2983 msgnum = config_msgnum;
2984 end_critical_section(S_CONFIG);
2990 msg = CtdlFetchMessage(msgnum);
2992 conf = strdoop(msg->cm_fields['M']);
2993 CtdlFreeMessage(msg);
3000 getroom(&CC->quickroom, hold_rm);
3002 if (conf != NULL) do {
3003 extract_token(buf, conf, 0, '\n');
3004 strcpy(conf, &conf[strlen(buf)+1]);
3005 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3010 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3011 char temp[PATH_MAX];
3014 strcpy(temp, tmpnam(NULL));
3016 fp = fopen(temp, "w");
3017 if (fp == NULL) return;
3018 fprintf(fp, "%s", sysconfdata);
3021 /* this handy API function does all the work for us */
3022 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);