4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
57 #include "ctdl_module.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
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,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
113 * This function is self explanatory.
114 * (What can I say, I'm in a weird mood today...)
116 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
120 for (i = 0; i < strlen(name); ++i) {
121 if (name[i] == '@') {
122 while (isspace(name[i - 1]) && i > 0) {
123 strcpy(&name[i - 1], &name[i]);
126 while (isspace(name[i + 1])) {
127 strcpy(&name[i + 1], &name[i + 2]);
135 * Aliasing for network mail.
136 * (Error messages have been commented out, because this is a server.)
138 int alias(char *name)
139 { /* process alias and routing info for mail */
142 char aaa[SIZ], bbb[SIZ];
143 char *ignetcfg = NULL;
144 char *ignetmap = NULL;
150 char original_name[256];
151 safestrncpy(original_name, name, sizeof original_name);
154 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
155 stripallbut(name, '<', '>');
157 fp = fopen(file_mail_aliases, "r");
159 fp = fopen("/dev/null", "r");
166 while (fgets(aaa, sizeof aaa, fp) != NULL) {
167 while (isspace(name[0]))
168 strcpy(name, &name[1]);
169 aaa[strlen(aaa) - 1] = 0;
171 for (a = 0; a < strlen(aaa); ++a) {
173 strcpy(bbb, &aaa[a + 1]);
177 if (!strcasecmp(name, aaa))
182 /* Hit the Global Address Book */
183 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
187 if (strcasecmp(original_name, name)) {
188 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
191 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
192 for (a=0; a<strlen(name); ++a) {
193 if (name[a] == '@') {
194 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
196 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
201 /* determine local or remote type, see citadel.h */
202 at = haschar(name, '@');
203 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
204 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
205 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
207 /* figure out the delivery mode */
208 extract_token(node, name, 1, '@', sizeof node);
210 /* If there are one or more dots in the nodename, we assume that it
211 * is an FQDN and will attempt SMTP delivery to the Internet.
213 if (haschar(node, '.') > 0) {
214 return(MES_INTERNET);
217 /* Otherwise we look in the IGnet maps for a valid Citadel node.
218 * Try directly-connected nodes first...
220 ignetcfg = CtdlGetSysConfig(IGNETCFG);
221 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
222 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
232 * Then try nodes that are two or more hops away.
234 ignetmap = CtdlGetSysConfig(IGNETMAP);
235 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
236 extract_token(buf, ignetmap, i, '\n', sizeof buf);
237 extract_token(testnode, buf, 0, '|', sizeof testnode);
238 if (!strcasecmp(node, testnode)) {
245 /* If we get to this point it's an invalid node name */
251 * Back end for the MSGS command: output message number only.
253 void simple_listing(long msgnum, void *userdata)
255 cprintf("%ld\n", msgnum);
261 * Back end for the MSGS command: output header summary.
263 void headers_listing(long msgnum, void *userdata)
265 struct CtdlMessage *msg;
267 msg = CtdlFetchMessage(msgnum, 0);
269 cprintf("%ld|0|||||\n", msgnum);
273 cprintf("%ld|%s|%s|%s|%s|%s|\n",
275 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
276 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
277 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
278 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
279 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
281 CtdlFreeMessage(msg);
286 /* Determine if a given message matches the fields in a message template.
287 * Return 0 for a successful match.
289 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
292 /* If there aren't any fields in the template, all messages will
295 if (template == NULL) return(0);
297 /* Null messages are bogus. */
298 if (msg == NULL) return(1);
300 for (i='A'; i<='Z'; ++i) {
301 if (template->cm_fields[i] != NULL) {
302 if (msg->cm_fields[i] == NULL) {
303 /* Considered equal if temmplate is empty string */
304 if (IsEmptyStr(template->cm_fields[i])) continue;
307 if (strcasecmp(msg->cm_fields[i],
308 template->cm_fields[i])) return 1;
312 /* All compares succeeded: we have a match! */
319 * Retrieve the "seen" message list for the current room.
321 void CtdlGetSeen(char *buf, int which_set) {
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 if (which_set == ctdlsetseen_seen)
328 safestrncpy(buf, vbuf.v_seen, SIZ);
329 if (which_set == ctdlsetseen_answered)
330 safestrncpy(buf, vbuf.v_answered, SIZ);
336 * Manipulate the "seen msgs" string (or other message set strings)
338 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
339 int target_setting, int which_set,
340 struct ctdluser *which_user, struct ctdlroom *which_room) {
341 struct cdbdata *cdbfr;
355 char *is_set; /* actually an array of booleans */
358 /* Don't bother doing *anything* if we were passed a list of zero messages */
359 if (num_target_msgnums < 1) {
363 /* If no room was specified, we go with the current room. */
365 which_room = &CC->room;
368 /* If no user was specified, we go with the current user. */
370 which_user = &CC->user;
373 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
374 num_target_msgnums, target_msgnums[0],
375 (target_setting ? "SET" : "CLEAR"),
379 /* Learn about the user and room in question */
380 CtdlGetRelationship(&vbuf, which_user, which_room);
382 /* Load the message list */
383 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
385 msglist = (long *) cdbfr->ptr;
386 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
387 num_msgs = cdbfr->len / sizeof(long);
390 return; /* No messages at all? No further action. */
393 is_set = malloc(num_msgs * sizeof(char));
394 memset(is_set, 0, (num_msgs * sizeof(char)) );
396 /* Decide which message set we're manipulating */
398 case ctdlsetseen_seen:
399 vset = NewStrBufPlain(vbuf.v_seen, -1);
401 case ctdlsetseen_answered:
402 vset = NewStrBufPlain(vbuf.v_answered, -1);
409 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
410 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
411 for (i=0; i<num_msgs; ++i) {
412 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
414 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
415 for (k=0; k<num_target_msgnums; ++k) {
416 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
420 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
422 /* Translate the existing sequence set into an array of booleans */
423 setstr = NewStrBuf();
427 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
429 StrBufExtract_token(lostr, setstr, 0, ':');
430 if (StrBufNum_tokens(setstr, ':') >= 2) {
431 StrBufExtract_token(histr, setstr, 1, ':');
435 StrBufAppendBuf(histr, lostr, 0);
438 if (!strcmp(ChrPtr(histr), "*")) {
445 for (i = 0; i < num_msgs; ++i) {
446 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
456 /* Now translate the array of booleans back into a sequence set */
462 for (i=0; i<num_msgs; ++i) {
466 for (k=0; k<num_target_msgnums; ++k) {
467 if (msglist[i] == target_msgnums[k]) {
468 is_seen = target_setting;
472 w = 0; /* set to 1 if we write something to the string */
474 if ((was_seen == 0) && (is_seen == 1)) {
477 else if ((was_seen == 1) && (is_seen == 0)) {
481 if (StrLength(vset) > 0) {
482 StrBufAppendBufPlain(vset, HKEY(","), 0);
485 StrBufAppendPrintf(vset, "%ld", hi);
488 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
491 else if ((is_seen) && (i == num_msgs - 1)) {
493 if (StrLength(vset) > 0) {
494 StrBufAppendBufPlain(vset, HKEY(","), 0);
496 if ((i==0) || (was_seen == 0)) {
497 StrBufAppendPrintf(vset, "%ld", msglist[i]);
500 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
507 while (StrLength(vset) > SIZ) {
509 * If we're truncating the sequence set of messages marked with the 'seen' flag,
510 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
511 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
513 if (which_set == ctdlsetseen_seen) {
515 first_tok = NewStrBuf();
516 StrBufRemove_token(vset, 0, ',');
517 StrBufExtract_token(first_tok, vset, 0, ',');
518 StrBufRemove_token(vset, 0, ',');
520 if (StrBufNum_tokens(first_tok, ':') > 1) {
521 StrBufRemove_token(first_tok, 0, ':');
525 new_set = NewStrBuf();
526 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
527 StrBufAppendBuf(new_set, first_tok, 0);
528 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
529 StrBufAppendBuf(new_set, vset, 0);
532 FreeStrBuf(&first_tok);
537 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
539 /* Decide which message set we're manipulating */
541 case ctdlsetseen_seen:
542 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
544 case ctdlsetseen_answered:
545 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
551 CtdlSetRelationship(&vbuf, which_user, which_room);
557 * API function to perform an operation for each qualifying message in the
558 * current room. (Returns the number of messages processed.)
560 int CtdlForEachMessage(int mode, long ref, char *search_string,
562 struct CtdlMessage *compare,
563 void (*CallBack) (long, void *),
569 struct cdbdata *cdbfr;
570 long *msglist = NULL;
572 int num_processed = 0;
575 struct CtdlMessage *msg = NULL;
578 int printed_lastold = 0;
579 int num_search_msgs = 0;
580 long *search_msgs = NULL;
582 int need_to_free_re = 0;
585 if ((content_type) && (!IsEmptyStr(content_type))) {
586 regcomp(&re, content_type, 0);
590 /* Learn about the user and room in question */
591 getuser(&CC->user, CC->curr_user);
592 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
594 /* Load the message list */
595 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
597 msglist = (long *) cdbfr->ptr;
598 num_msgs = cdbfr->len / sizeof(long);
600 if (need_to_free_re) regfree(&re);
601 return 0; /* No messages at all? No further action. */
606 * Now begin the traversal.
608 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
610 /* If the caller is looking for a specific MIME type, filter
611 * out all messages which are not of the type requested.
613 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
615 /* This call to GetMetaData() sits inside this loop
616 * so that we only do the extra database read per msg
617 * if we need to. Doing the extra read all the time
618 * really kills the server. If we ever need to use
619 * metadata for another search criterion, we need to
620 * move the read somewhere else -- but still be smart
621 * enough to only do the read if the caller has
622 * specified something that will need it.
624 GetMetaData(&smi, msglist[a]);
626 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
627 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
633 num_msgs = sort_msglist(msglist, num_msgs);
635 /* If a template was supplied, filter out the messages which
636 * don't match. (This could induce some delays!)
639 if (compare != NULL) {
640 for (a = 0; a < num_msgs; ++a) {
641 msg = CtdlFetchMessage(msglist[a], 1);
643 if (CtdlMsgCmp(msg, compare)) {
646 CtdlFreeMessage(msg);
652 /* If a search string was specified, get a message list from
653 * the full text index and remove messages which aren't on both
657 * Since the lists are sorted and strictly ascending, and the
658 * output list is guaranteed to be shorter than or equal to the
659 * input list, we overwrite the bottom of the input list. This
660 * eliminates the need to memmove big chunks of the list over and
663 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
665 /* Call search module via hook mechanism.
666 * NULL means use any search function available.
667 * otherwise replace with a char * to name of search routine
669 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
671 if (num_search_msgs > 0) {
675 orig_num_msgs = num_msgs;
677 for (i=0; i<orig_num_msgs; ++i) {
678 for (j=0; j<num_search_msgs; ++j) {
679 if (msglist[i] == search_msgs[j]) {
680 msglist[num_msgs++] = msglist[i];
686 num_msgs = 0; /* No messages qualify */
688 if (search_msgs != NULL) free(search_msgs);
690 /* Now that we've purged messages which don't contain the search
691 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
698 * Now iterate through the message list, according to the
699 * criteria supplied by the caller.
702 for (a = 0; a < num_msgs; ++a) {
703 thismsg = msglist[a];
704 if (mode == MSGS_ALL) {
708 is_seen = is_msg_in_sequence_set(
709 vbuf.v_seen, thismsg);
710 if (is_seen) lastold = thismsg;
716 || ((mode == MSGS_OLD) && (is_seen))
717 || ((mode == MSGS_NEW) && (!is_seen))
718 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
719 || ((mode == MSGS_FIRST) && (a < ref))
720 || ((mode == MSGS_GT) && (thismsg > ref))
721 || ((mode == MSGS_EQ) && (thismsg == ref))
724 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
726 CallBack(lastold, userdata);
730 if (CallBack) CallBack(thismsg, userdata);
734 cdb_free(cdbfr); /* Clean up */
735 if (need_to_free_re) regfree(&re);
736 return num_processed;
742 * cmd_msgs() - get list of message #'s in this room
743 * implements the MSGS server command using CtdlForEachMessage()
745 void cmd_msgs(char *cmdbuf)
754 int with_template = 0;
755 struct CtdlMessage *template = NULL;
756 int with_headers = 0;
757 char search_string[1024];
759 extract_token(which, cmdbuf, 0, '|', sizeof which);
760 cm_ref = extract_int(cmdbuf, 1);
761 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
762 with_template = extract_int(cmdbuf, 2);
763 with_headers = extract_int(cmdbuf, 3);
766 if (!strncasecmp(which, "OLD", 3))
768 else if (!strncasecmp(which, "NEW", 3))
770 else if (!strncasecmp(which, "FIRST", 5))
772 else if (!strncasecmp(which, "LAST", 4))
774 else if (!strncasecmp(which, "GT", 2))
776 else if (!strncasecmp(which, "SEARCH", 6))
781 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
782 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
786 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
787 cprintf("%d Full text index is not enabled on this server.\n",
788 ERROR + CMD_NOT_SUPPORTED);
794 cprintf("%d Send template then receive message list\n",
796 template = (struct CtdlMessage *)
797 malloc(sizeof(struct CtdlMessage));
798 memset(template, 0, sizeof(struct CtdlMessage));
799 template->cm_magic = CTDLMESSAGE_MAGIC;
800 template->cm_anon_type = MES_NORMAL;
802 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
803 extract_token(tfield, buf, 0, '|', sizeof tfield);
804 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
805 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
806 if (!strcasecmp(tfield, msgkeys[i])) {
807 template->cm_fields[i] =
815 cprintf("%d \n", LISTING_FOLLOWS);
818 CtdlForEachMessage(mode,
819 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
820 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
823 (with_headers ? headers_listing : simple_listing),
826 if (template != NULL) CtdlFreeMessage(template);
834 * help_subst() - support routine for help file viewer
836 void help_subst(char *strbuf, char *source, char *dest)
841 while (p = pattern2(strbuf, source), (p >= 0)) {
842 strcpy(workbuf, &strbuf[p + strlen(source)]);
843 strcpy(&strbuf[p], dest);
844 strcat(strbuf, workbuf);
849 void do_help_subst(char *buffer)
853 help_subst(buffer, "^nodename", config.c_nodename);
854 help_subst(buffer, "^humannode", config.c_humannode);
855 help_subst(buffer, "^fqdn", config.c_fqdn);
856 help_subst(buffer, "^username", CC->user.fullname);
857 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
858 help_subst(buffer, "^usernum", buf2);
859 help_subst(buffer, "^sysadm", config.c_sysadm);
860 help_subst(buffer, "^variantname", CITADEL);
861 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
862 help_subst(buffer, "^maxsessions", buf2);
863 help_subst(buffer, "^bbsdir", ctdl_message_dir);
869 * memfmout() - Citadel text formatter and paginator.
870 * Although the original purpose of this routine was to format
871 * text to the reader's screen width, all we're really using it
872 * for here is to format text out to 80 columns before sending it
873 * to the client. The client software may reformat it again.
876 char *mptr, /* where are we going to get our text from? */
877 char subst, /* nonzero if we should do substitutions */
878 char *nl) /* string to terminate lines with */
886 static int width = 80;
891 c = 1; /* c is the current pos */
895 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
897 buffer[strlen(buffer) + 1] = 0;
898 buffer[strlen(buffer)] = ch;
901 if (buffer[0] == '^')
902 do_help_subst(buffer);
904 buffer[strlen(buffer) + 1] = 0;
906 strcpy(buffer, &buffer[1]);
914 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
917 if (((old == 13) || (old == 10)) && (isspace(real))) {
922 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
923 cprintf("%s%s", nl, aaa);
932 if ((strlen(aaa) + c) > (width - 5)) {
941 if ((ch == 13) || (ch == 10)) {
942 cprintf("%s%s", aaa, nl);
949 cprintf("%s%s", aaa, nl);
955 * Callback function for mime parser that simply lists the part
957 void list_this_part(char *name, char *filename, char *partnum, char *disp,
958 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
959 char *cbid, void *cbuserdata)
963 ma = (struct ma_info *)cbuserdata;
964 if (ma->is_ma == 0) {
965 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
966 name, filename, partnum, disp, cbtype, (long)length, cbid);
971 * Callback function for multipart prefix
973 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
974 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
975 char *cbid, void *cbuserdata)
979 ma = (struct ma_info *)cbuserdata;
980 if (!strcasecmp(cbtype, "multipart/alternative")) {
984 if (ma->is_ma == 0) {
985 cprintf("pref=%s|%s\n", partnum, cbtype);
990 * Callback function for multipart sufffix
992 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
993 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
994 char *cbid, void *cbuserdata)
998 ma = (struct ma_info *)cbuserdata;
999 if (ma->is_ma == 0) {
1000 cprintf("suff=%s|%s\n", partnum, cbtype);
1002 if (!strcasecmp(cbtype, "multipart/alternative")) {
1009 * Callback function for mime parser that opens a section for downloading
1011 void mime_download(char *name, char *filename, char *partnum, char *disp,
1012 void *content, char *cbtype, char *cbcharset, size_t length,
1013 char *encoding, char *cbid, void *cbuserdata)
1017 /* Silently go away if there's already a download open. */
1018 if (CC->download_fp != NULL)
1022 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1023 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1025 CC->download_fp = tmpfile();
1026 if (CC->download_fp == NULL)
1029 rv = fwrite(content, length, 1, CC->download_fp);
1030 fflush(CC->download_fp);
1031 rewind(CC->download_fp);
1033 OpenCmdResult(filename, cbtype);
1040 * Callback function for mime parser that outputs a section all at once.
1041 * We can specify the desired section by part number *or* content-id.
1043 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1044 void *content, char *cbtype, char *cbcharset, size_t length,
1045 char *encoding, char *cbid, void *cbuserdata)
1047 int *found_it = (int *)cbuserdata;
1050 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1051 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1054 cprintf("%d %d|-1|%s|%s\n",
1060 client_write(content, length);
1067 * Load a message from disk into memory.
1068 * This is used by CtdlOutputMsg() and other fetch functions.
1070 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1071 * using the CtdlMessageFree() function.
1073 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1075 struct cdbdata *dmsgtext;
1076 struct CtdlMessage *ret = NULL;
1080 cit_uint8_t field_header;
1082 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1084 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1085 if (dmsgtext == NULL) {
1088 mptr = dmsgtext->ptr;
1089 upper_bound = mptr + dmsgtext->len;
1091 /* Parse the three bytes that begin EVERY message on disk.
1092 * The first is always 0xFF, the on-disk magic number.
1093 * The second is the anonymous/public type byte.
1094 * The third is the format type byte (vari, fixed, or MIME).
1098 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1102 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1103 memset(ret, 0, sizeof(struct CtdlMessage));
1105 ret->cm_magic = CTDLMESSAGE_MAGIC;
1106 ret->cm_anon_type = *mptr++; /* Anon type byte */
1107 ret->cm_format_type = *mptr++; /* Format type byte */
1110 * The rest is zero or more arbitrary fields. Load them in.
1111 * We're done when we encounter either a zero-length field or
1112 * have just processed the 'M' (message text) field.
1115 if (mptr >= upper_bound) {
1118 field_header = *mptr++;
1119 ret->cm_fields[field_header] = strdup(mptr);
1121 while (*mptr++ != 0); /* advance to next field */
1123 } while ((mptr < upper_bound) && (field_header != 'M'));
1127 /* Always make sure there's something in the msg text field. If
1128 * it's NULL, the message text is most likely stored separately,
1129 * so go ahead and fetch that. Failing that, just set a dummy
1130 * body so other code doesn't barf.
1132 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1133 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1134 if (dmsgtext != NULL) {
1135 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1139 if (ret->cm_fields['M'] == NULL) {
1140 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1143 /* Perform "before read" hooks (aborting if any return nonzero) */
1144 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1145 CtdlFreeMessage(ret);
1154 * Returns 1 if the supplied pointer points to a valid Citadel message.
1155 * If the pointer is NULL or the magic number check fails, returns 0.
1157 int is_valid_message(struct CtdlMessage *msg) {
1160 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1161 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1169 * 'Destructor' for struct CtdlMessage
1171 void CtdlFreeMessage(struct CtdlMessage *msg)
1175 if (is_valid_message(msg) == 0)
1177 if (msg != NULL) free (msg);
1181 for (i = 0; i < 256; ++i)
1182 if (msg->cm_fields[i] != NULL) {
1183 free(msg->cm_fields[i]);
1186 msg->cm_magic = 0; /* just in case */
1192 * Pre callback function for multipart/alternative
1194 * NOTE: this differs from the standard behavior for a reason. Normally when
1195 * displaying multipart/alternative you want to show the _last_ usable
1196 * format in the message. Here we show the _first_ one, because it's
1197 * usually text/plain. Since this set of functions is designed for text
1198 * output to non-MIME-aware clients, this is the desired behavior.
1201 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1202 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1203 char *cbid, void *cbuserdata)
1207 ma = (struct ma_info *)cbuserdata;
1208 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1209 if (!strcasecmp(cbtype, "multipart/alternative")) {
1213 if (!strcasecmp(cbtype, "message/rfc822")) {
1219 * Post callback function for multipart/alternative
1221 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1222 void *content, char *cbtype, char *cbcharset, size_t length,
1223 char *encoding, char *cbid, void *cbuserdata)
1227 ma = (struct ma_info *)cbuserdata;
1228 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1229 if (!strcasecmp(cbtype, "multipart/alternative")) {
1233 if (!strcasecmp(cbtype, "message/rfc822")) {
1239 * Inline callback function for mime parser that wants to display text
1241 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1242 void *content, char *cbtype, char *cbcharset, size_t length,
1243 char *encoding, char *cbid, void *cbuserdata)
1250 ma = (struct ma_info *)cbuserdata;
1252 CtdlLogPrintf(CTDL_DEBUG,
1253 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1254 partnum, filename, cbtype, (long)length);
1257 * If we're in the middle of a multipart/alternative scope and
1258 * we've already printed another section, skip this one.
1260 if ( (ma->is_ma) && (ma->did_print) ) {
1261 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1266 if ( (!strcasecmp(cbtype, "text/plain"))
1267 || (IsEmptyStr(cbtype)) ) {
1270 client_write(wptr, length);
1271 if (wptr[length-1] != '\n') {
1278 if (!strcasecmp(cbtype, "text/html")) {
1279 ptr = html_to_ascii(content, length, 80, 0);
1281 client_write(ptr, wlen);
1282 if (ptr[wlen-1] != '\n') {
1289 if (ma->use_fo_hooks) {
1290 if (PerformFixedOutputHooks(cbtype, content, length)) {
1291 /* above function returns nonzero if it handled the part */
1296 if (strncasecmp(cbtype, "multipart/", 10)) {
1297 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1298 partnum, filename, cbtype, (long)length);
1304 * The client is elegant and sophisticated and wants to be choosy about
1305 * MIME content types, so figure out which multipart/alternative part
1306 * we're going to send.
1308 * We use a system of weights. When we find a part that matches one of the
1309 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1310 * and then set ma->chosen_pref to that MIME type's position in our preference
1311 * list. If we then hit another match, we only replace the first match if
1312 * the preference value is lower.
1314 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1315 void *content, char *cbtype, char *cbcharset, size_t length,
1316 char *encoding, char *cbid, void *cbuserdata)
1322 ma = (struct ma_info *)cbuserdata;
1324 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1325 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1326 // I don't know if there are any side effects! Please TEST TEST TEST
1327 //if (ma->is_ma > 0) {
1329 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1330 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1331 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1332 if (i < ma->chosen_pref) {
1333 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1334 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1335 ma->chosen_pref = i;
1342 * Now that we've chosen our preferred part, output it.
1344 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1345 void *content, char *cbtype, char *cbcharset, size_t length,
1346 char *encoding, char *cbid, void *cbuserdata)
1350 int add_newline = 0;
1354 ma = (struct ma_info *)cbuserdata;
1356 /* This is not the MIME part you're looking for... */
1357 if (strcasecmp(partnum, ma->chosen_part)) return;
1359 /* If the content-type of this part is in our preferred formats
1360 * list, we can simply output it verbatim.
1362 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1363 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1364 if (!strcasecmp(buf, cbtype)) {
1365 /* Yeah! Go! W00t!! */
1367 text_content = (char *)content;
1368 if (text_content[length-1] != '\n') {
1371 cprintf("Content-type: %s", cbtype);
1372 if (!IsEmptyStr(cbcharset)) {
1373 cprintf("; charset=%s", cbcharset);
1375 cprintf("\nContent-length: %d\n",
1376 (int)(length + add_newline) );
1377 if (!IsEmptyStr(encoding)) {
1378 cprintf("Content-transfer-encoding: %s\n", encoding);
1381 cprintf("Content-transfer-encoding: 7bit\n");
1383 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1385 client_write(content, length);
1386 if (add_newline) cprintf("\n");
1391 /* No translations required or possible: output as text/plain */
1392 cprintf("Content-type: text/plain\n\n");
1393 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1394 length, encoding, cbid, cbuserdata);
1399 char desired_section[64];
1406 * Callback function for
1408 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1409 void *content, char *cbtype, char *cbcharset, size_t length,
1410 char *encoding, char *cbid, void *cbuserdata)
1412 struct encapmsg *encap;
1414 encap = (struct encapmsg *)cbuserdata;
1416 /* Only proceed if this is the desired section... */
1417 if (!strcasecmp(encap->desired_section, partnum)) {
1418 encap->msglen = length;
1419 encap->msg = malloc(length + 2);
1420 memcpy(encap->msg, content, length);
1430 * Get a message off disk. (returns om_* values found in msgbase.h)
1433 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1434 int mode, /* how would you like that message? */
1435 int headers_only, /* eschew the message body? */
1436 int do_proto, /* do Citadel protocol responses? */
1437 int crlf, /* Use CRLF newlines instead of LF? */
1438 char *section, /* NULL or a message/rfc822 section */
1439 int flags /* should the bessage be exported clean? */
1441 struct CtdlMessage *TheMessage = NULL;
1442 int retcode = om_no_such_msg;
1443 struct encapmsg encap;
1445 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1447 (section ? section : "<>")
1450 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1451 if (do_proto) cprintf("%d Not logged in.\n",
1452 ERROR + NOT_LOGGED_IN);
1453 return(om_not_logged_in);
1456 /* FIXME: check message id against msglist for this room */
1459 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1460 * request that we don't even bother loading the body into memory.
1462 if (headers_only == HEADERS_FAST) {
1463 TheMessage = CtdlFetchMessage(msg_num, 0);
1466 TheMessage = CtdlFetchMessage(msg_num, 1);
1469 if (TheMessage == NULL) {
1470 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1471 ERROR + MESSAGE_NOT_FOUND, msg_num);
1472 return(om_no_such_msg);
1475 /* Here is the weird form of this command, to process only an
1476 * encapsulated message/rfc822 section.
1478 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1479 memset(&encap, 0, sizeof encap);
1480 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1481 mime_parser(TheMessage->cm_fields['M'],
1483 *extract_encapsulated_message,
1484 NULL, NULL, (void *)&encap, 0
1486 CtdlFreeMessage(TheMessage);
1490 encap.msg[encap.msglen] = 0;
1491 TheMessage = convert_internet_message(encap.msg);
1492 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1494 /* Now we let it fall through to the bottom of this
1495 * function, because TheMessage now contains the
1496 * encapsulated message instead of the top-level
1497 * message. Isn't that neat?
1502 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1503 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1504 retcode = om_no_such_msg;
1509 /* Ok, output the message now */
1510 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1511 CtdlFreeMessage(TheMessage);
1517 char *qp_encode_email_addrs(char *source)
1519 char user[256], node[256], name[256];
1520 const char headerStr[] = "=?UTF-8?Q?";
1524 int need_to_encode = 0;
1530 long nAddrPtrMax = 50;
1535 if (source == NULL) return source;
1536 if (IsEmptyStr(source)) return source;
1538 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1539 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1540 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1543 while (!IsEmptyStr (&source[i])) {
1544 if (nColons >= nAddrPtrMax){
1547 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1548 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1549 free (AddrPtr), AddrPtr = ptr;
1551 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1552 memset(&ptr[nAddrPtrMax], 0,
1553 sizeof (long) * nAddrPtrMax);
1555 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1556 free (AddrUtf8), AddrUtf8 = ptr;
1559 if (((unsigned char) source[i] < 32) ||
1560 ((unsigned char) source[i] > 126)) {
1562 AddrUtf8[nColons] = 1;
1564 if (source[i] == '"')
1565 InQuotes = !InQuotes;
1566 if (!InQuotes && source[i] == ',') {
1567 AddrPtr[nColons] = i;
1572 if (need_to_encode == 0) {
1579 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1580 Encoded = (char*) malloc (EncodedMaxLen);
1582 for (i = 0; i < nColons; i++)
1583 source[AddrPtr[i]++] = '\0';
1587 for (i = 0; i < nColons && nPtr != NULL; i++) {
1588 nmax = EncodedMaxLen - (nPtr - Encoded);
1590 process_rfc822_addr(&source[AddrPtr[i]],
1594 /* TODO: libIDN here ! */
1595 if (IsEmptyStr(name)) {
1596 n = snprintf(nPtr, nmax,
1597 (i==0)?"%s@%s" : ",%s@%s",
1601 EncodedName = rfc2047encode(name, strlen(name));
1602 n = snprintf(nPtr, nmax,
1603 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1604 EncodedName, user, node);
1609 n = snprintf(nPtr, nmax,
1610 (i==0)?"%s" : ",%s",
1611 &source[AddrPtr[i]]);
1617 ptr = (char*) malloc(EncodedMaxLen * 2);
1618 memcpy(ptr, Encoded, EncodedMaxLen);
1619 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1620 free(Encoded), Encoded = ptr;
1622 i--; /* do it once more with properly lengthened buffer */
1625 for (i = 0; i < nColons; i++)
1626 source[--AddrPtr[i]] = ',';
1633 /* If the last item in a list of recipients was truncated to a partial address,
1634 * remove it completely in order to avoid choking libSieve
1636 void sanitize_truncated_recipient(char *str)
1639 if (num_tokens(str, ',') < 2) return;
1641 int len = strlen(str);
1642 if (len < 900) return;
1643 if (len > 998) str[998] = 0;
1645 char *cptr = strrchr(str, ',');
1648 char *lptr = strchr(cptr, '<');
1649 char *rptr = strchr(cptr, '>');
1651 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1659 * Get a message off disk. (returns om_* values found in msgbase.h)
1661 int CtdlOutputPreLoadedMsg(
1662 struct CtdlMessage *TheMessage,
1663 int mode, /* how would you like that message? */
1664 int headers_only, /* eschew the message body? */
1665 int do_proto, /* do Citadel protocol responses? */
1666 int crlf, /* Use CRLF newlines instead of LF? */
1667 int flags /* should the bessage be exported clean? */
1671 cit_uint8_t ch, prev_ch;
1673 char display_name[256];
1675 char *nl; /* newline string */
1677 int subject_found = 0;
1680 /* Buffers needed for RFC822 translation. These are all filled
1681 * using functions that are bounds-checked, and therefore we can
1682 * make them substantially smaller than SIZ.
1689 char datestamp[100];
1691 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1692 ((TheMessage == NULL) ? "NULL" : "not null"),
1693 mode, headers_only, do_proto, crlf);
1695 strcpy(mid, "unknown");
1696 nl = (crlf ? "\r\n" : "\n");
1698 if (!is_valid_message(TheMessage)) {
1699 CtdlLogPrintf(CTDL_ERR,
1700 "ERROR: invalid preloaded message for output\n");
1702 return(om_no_such_msg);
1705 /* Are we downloading a MIME component? */
1706 if (mode == MT_DOWNLOAD) {
1707 if (TheMessage->cm_format_type != FMT_RFC822) {
1709 cprintf("%d This is not a MIME message.\n",
1710 ERROR + ILLEGAL_VALUE);
1711 } else if (CC->download_fp != NULL) {
1712 if (do_proto) cprintf(
1713 "%d You already have a download open.\n",
1714 ERROR + RESOURCE_BUSY);
1716 /* Parse the message text component */
1717 mptr = TheMessage->cm_fields['M'];
1718 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1719 /* If there's no file open by this time, the requested
1720 * section wasn't found, so print an error
1722 if (CC->download_fp == NULL) {
1723 if (do_proto) cprintf(
1724 "%d Section %s not found.\n",
1725 ERROR + FILE_NOT_FOUND,
1726 CC->download_desired_section);
1729 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1732 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1733 * in a single server operation instead of opening a download file.
1735 if (mode == MT_SPEW_SECTION) {
1736 if (TheMessage->cm_format_type != FMT_RFC822) {
1738 cprintf("%d This is not a MIME message.\n",
1739 ERROR + ILLEGAL_VALUE);
1741 /* Parse the message text component */
1744 mptr = TheMessage->cm_fields['M'];
1745 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1746 /* If section wasn't found, print an error
1749 if (do_proto) cprintf(
1750 "%d Section %s not found.\n",
1751 ERROR + FILE_NOT_FOUND,
1752 CC->download_desired_section);
1755 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1758 /* now for the user-mode message reading loops */
1759 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1761 /* Does the caller want to skip the headers? */
1762 if (headers_only == HEADERS_NONE) goto START_TEXT;
1764 /* Tell the client which format type we're using. */
1765 if ( (mode == MT_CITADEL) && (do_proto) ) {
1766 cprintf("type=%d\n", TheMessage->cm_format_type);
1769 /* nhdr=yes means that we're only displaying headers, no body */
1770 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1771 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1774 cprintf("nhdr=yes\n");
1777 /* begin header processing loop for Citadel message format */
1779 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1781 safestrncpy(display_name, "<unknown>", sizeof display_name);
1782 if (TheMessage->cm_fields['A']) {
1783 strcpy(buf, TheMessage->cm_fields['A']);
1784 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1785 safestrncpy(display_name, "****", sizeof display_name);
1787 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1788 safestrncpy(display_name, "anonymous", sizeof display_name);
1791 safestrncpy(display_name, buf, sizeof display_name);
1793 if ((is_room_aide())
1794 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1795 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1796 size_t tmp = strlen(display_name);
1797 snprintf(&display_name[tmp],
1798 sizeof display_name - tmp,
1803 /* Don't show Internet address for users on the
1804 * local Citadel network.
1807 if (TheMessage->cm_fields['N'] != NULL)
1808 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1809 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1813 /* Now spew the header fields in the order we like them. */
1814 safestrncpy(allkeys, FORDER, sizeof allkeys);
1815 for (i=0; i<strlen(allkeys); ++i) {
1816 k = (int) allkeys[i];
1818 if ( (TheMessage->cm_fields[k] != NULL)
1819 && (msgkeys[k] != NULL) ) {
1820 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1821 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1824 if (do_proto) cprintf("%s=%s\n",
1828 else if ((k == 'F') && (suppress_f)) {
1831 /* Masquerade display name if needed */
1833 if (do_proto) cprintf("%s=%s\n",
1835 TheMessage->cm_fields[k]
1844 /* begin header processing loop for RFC822 transfer format */
1849 strcpy(snode, NODENAME);
1850 if (mode == MT_RFC822) {
1851 for (i = 0; i < 256; ++i) {
1852 if (TheMessage->cm_fields[i]) {
1853 mptr = mpptr = TheMessage->cm_fields[i];
1856 safestrncpy(luser, mptr, sizeof luser);
1857 safestrncpy(suser, mptr, sizeof suser);
1859 else if (i == 'Y') {
1860 if ((flags & QP_EADDR) != 0) {
1861 mptr = qp_encode_email_addrs(mptr);
1863 sanitize_truncated_recipient(mptr);
1864 cprintf("CC: %s%s", mptr, nl);
1866 else if (i == 'P') {
1867 cprintf("Return-Path: %s%s", mptr, nl);
1869 else if (i == 'L') {
1870 cprintf("List-ID: %s%s", mptr, nl);
1872 else if (i == 'V') {
1873 if ((flags & QP_EADDR) != 0)
1874 mptr = qp_encode_email_addrs(mptr);
1875 cprintf("Envelope-To: %s%s", mptr, nl);
1877 else if (i == 'U') {
1878 cprintf("Subject: %s%s", mptr, nl);
1882 safestrncpy(mid, mptr, sizeof mid);
1884 safestrncpy(fuser, mptr, sizeof fuser);
1885 /* else if (i == 'O')
1886 cprintf("X-Citadel-Room: %s%s",
1889 safestrncpy(snode, mptr, sizeof snode);
1892 if (haschar(mptr, '@') == 0)
1894 sanitize_truncated_recipient(mptr);
1895 cprintf("To: %s@%s", mptr, config.c_fqdn);
1900 if ((flags & QP_EADDR) != 0) {
1901 mptr = qp_encode_email_addrs(mptr);
1903 sanitize_truncated_recipient(mptr);
1904 cprintf("To: %s", mptr);
1908 else if (i == 'T') {
1909 datestring(datestamp, sizeof datestamp,
1910 atol(mptr), DATESTRING_RFC822);
1911 cprintf("Date: %s%s", datestamp, nl);
1913 else if (i == 'W') {
1914 cprintf("References: ");
1915 k = num_tokens(mptr, '|');
1916 for (j=0; j<k; ++j) {
1917 extract_token(buf, mptr, j, '|', sizeof buf);
1918 cprintf("<%s>", buf);
1931 if (subject_found == 0) {
1932 cprintf("Subject: (no subject)%s", nl);
1936 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1937 suser[i] = tolower(suser[i]);
1938 if (!isalnum(suser[i])) suser[i]='_';
1941 if (mode == MT_RFC822) {
1942 if (!strcasecmp(snode, NODENAME)) {
1943 safestrncpy(snode, FQDN, sizeof snode);
1946 /* Construct a fun message id */
1947 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1948 if (strchr(mid, '@')==NULL) {
1949 cprintf("@%s", snode);
1953 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1954 cprintf("From: \"----\" <x@x.org>%s", nl);
1956 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1957 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1959 else if (!IsEmptyStr(fuser)) {
1960 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1963 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1966 /* Blank line signifying RFC822 end-of-headers */
1967 if (TheMessage->cm_format_type != FMT_RFC822) {
1972 /* end header processing loop ... at this point, we're in the text */
1974 if (headers_only == HEADERS_FAST) goto DONE;
1975 mptr = TheMessage->cm_fields['M'];
1977 /* Tell the client about the MIME parts in this message */
1978 if (TheMessage->cm_format_type == FMT_RFC822) {
1979 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1980 memset(&ma, 0, sizeof(struct ma_info));
1981 mime_parser(mptr, NULL,
1982 (do_proto ? *list_this_part : NULL),
1983 (do_proto ? *list_this_pref : NULL),
1984 (do_proto ? *list_this_suff : NULL),
1987 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1988 char *start_of_text = NULL;
1989 start_of_text = strstr(mptr, "\n\r\n");
1990 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1991 if (start_of_text == NULL) start_of_text = mptr;
1993 start_of_text = strstr(start_of_text, "\n");
1998 int nllen = strlen(nl);
2000 while (ch=*mptr, ch!=0) {
2006 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2007 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2008 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2011 sprintf(&outbuf[outlen], "%s", nl);
2015 outbuf[outlen++] = ch;
2019 if (flags & ESC_DOT)
2021 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2023 outbuf[outlen++] = '.';
2028 if (outlen > 1000) {
2029 client_write(outbuf, outlen);
2034 client_write(outbuf, outlen);
2042 if (headers_only == HEADERS_ONLY) {
2046 /* signify start of msg text */
2047 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2048 if (do_proto) cprintf("text\n");
2051 /* If the format type on disk is 1 (fixed-format), then we want
2052 * everything to be output completely literally ... regardless of
2053 * what message transfer format is in use.
2055 if (TheMessage->cm_format_type == FMT_FIXED) {
2057 if (mode == MT_MIME) {
2058 cprintf("Content-type: text/plain\n\n");
2062 while (ch = *mptr++, ch > 0) {
2065 if ((ch == 10) || (buflen > 250)) {
2067 cprintf("%s%s", buf, nl);
2076 if (!IsEmptyStr(buf))
2077 cprintf("%s%s", buf, nl);
2080 /* If the message on disk is format 0 (Citadel vari-format), we
2081 * output using the formatter at 80 columns. This is the final output
2082 * form if the transfer format is RFC822, but if the transfer format
2083 * is Citadel proprietary, it'll still work, because the indentation
2084 * for new paragraphs is correct and the client will reformat the
2085 * message to the reader's screen width.
2087 if (TheMessage->cm_format_type == FMT_CITADEL) {
2088 if (mode == MT_MIME) {
2089 cprintf("Content-type: text/x-citadel-variformat\n\n");
2091 memfmout(mptr, 0, nl);
2094 /* If the message on disk is format 4 (MIME), we've gotta hand it
2095 * off to the MIME parser. The client has already been told that
2096 * this message is format 1 (fixed format), so the callback function
2097 * we use will display those parts as-is.
2099 if (TheMessage->cm_format_type == FMT_RFC822) {
2100 memset(&ma, 0, sizeof(struct ma_info));
2102 if (mode == MT_MIME) {
2103 ma.use_fo_hooks = 0;
2104 strcpy(ma.chosen_part, "1");
2105 ma.chosen_pref = 9999;
2106 mime_parser(mptr, NULL,
2107 *choose_preferred, *fixed_output_pre,
2108 *fixed_output_post, (void *)&ma, 0);
2109 mime_parser(mptr, NULL,
2110 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2113 ma.use_fo_hooks = 1;
2114 mime_parser(mptr, NULL,
2115 *fixed_output, *fixed_output_pre,
2116 *fixed_output_post, (void *)&ma, 0);
2121 DONE: /* now we're done */
2122 if (do_proto) cprintf("000\n");
2129 * display a message (mode 0 - Citadel proprietary)
2131 void cmd_msg0(char *cmdbuf)
2134 int headers_only = HEADERS_ALL;
2136 msgid = extract_long(cmdbuf, 0);
2137 headers_only = extract_int(cmdbuf, 1);
2139 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2145 * display a message (mode 2 - RFC822)
2147 void cmd_msg2(char *cmdbuf)
2150 int headers_only = HEADERS_ALL;
2152 msgid = extract_long(cmdbuf, 0);
2153 headers_only = extract_int(cmdbuf, 1);
2155 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2161 * display a message (mode 3 - IGnet raw format - internal programs only)
2163 void cmd_msg3(char *cmdbuf)
2166 struct CtdlMessage *msg = NULL;
2169 if (CC->internal_pgm == 0) {
2170 cprintf("%d This command is for internal programs only.\n",
2171 ERROR + HIGHER_ACCESS_REQUIRED);
2175 msgnum = extract_long(cmdbuf, 0);
2176 msg = CtdlFetchMessage(msgnum, 1);
2178 cprintf("%d Message %ld not found.\n",
2179 ERROR + MESSAGE_NOT_FOUND, msgnum);
2183 serialize_message(&smr, msg);
2184 CtdlFreeMessage(msg);
2187 cprintf("%d Unable to serialize message\n",
2188 ERROR + INTERNAL_ERROR);
2192 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2193 client_write((char *)smr.ser, (int)smr.len);
2200 * Display a message using MIME content types
2202 void cmd_msg4(char *cmdbuf)
2207 msgid = extract_long(cmdbuf, 0);
2208 extract_token(section, cmdbuf, 1, '|', sizeof section);
2209 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2215 * Client tells us its preferred message format(s)
2217 void cmd_msgp(char *cmdbuf)
2219 if (!strcasecmp(cmdbuf, "dont_decode")) {
2220 CC->msg4_dont_decode = 1;
2221 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2224 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2225 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2231 * Open a component of a MIME message as a download file
2233 void cmd_opna(char *cmdbuf)
2236 char desired_section[128];
2238 msgid = extract_long(cmdbuf, 0);
2239 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2240 safestrncpy(CC->download_desired_section, desired_section,
2241 sizeof CC->download_desired_section);
2242 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2247 * Open a component of a MIME message and transmit it all at once
2249 void cmd_dlat(char *cmdbuf)
2252 char desired_section[128];
2254 msgid = extract_long(cmdbuf, 0);
2255 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2256 safestrncpy(CC->download_desired_section, desired_section,
2257 sizeof CC->download_desired_section);
2258 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2263 * Save one or more message pointers into a specified room
2264 * (Returns 0 for success, nonzero for failure)
2265 * roomname may be NULL to use the current room
2267 * Note that the 'supplied_msg' field may be set to NULL, in which case
2268 * the message will be fetched from disk, by number, if we need to perform
2269 * replication checks. This adds an additional database read, so if the
2270 * caller already has the message in memory then it should be supplied. (Obviously
2271 * this mode of operation only works if we're saving a single message.)
2273 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2274 int do_repl_check, struct CtdlMessage *supplied_msg)
2277 char hold_rm[ROOMNAMELEN];
2278 struct cdbdata *cdbfr;
2281 long highest_msg = 0L;
2284 struct CtdlMessage *msg = NULL;
2286 long *msgs_to_be_merged = NULL;
2287 int num_msgs_to_be_merged = 0;
2289 CtdlLogPrintf(CTDL_DEBUG,
2290 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2291 roomname, num_newmsgs, do_repl_check);
2293 strcpy(hold_rm, CC->room.QRname);
2296 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2297 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2298 if (num_newmsgs > 1) supplied_msg = NULL;
2300 /* Now the regular stuff */
2301 if (lgetroom(&CC->room,
2302 ((roomname != NULL) ? roomname : CC->room.QRname) )
2304 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2305 return(ERROR + ROOM_NOT_FOUND);
2309 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2310 num_msgs_to_be_merged = 0;
2313 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2314 if (cdbfr == NULL) {
2318 msglist = (long *) cdbfr->ptr;
2319 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2320 num_msgs = cdbfr->len / sizeof(long);
2325 /* Create a list of msgid's which were supplied by the caller, but do
2326 * not already exist in the target room. It is absolutely taboo to
2327 * have more than one reference to the same message in a room.
2329 for (i=0; i<num_newmsgs; ++i) {
2331 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2332 if (msglist[j] == newmsgidlist[i]) {
2337 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2341 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2344 * Now merge the new messages
2346 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2347 if (msglist == NULL) {
2348 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2350 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2351 num_msgs += num_msgs_to_be_merged;
2353 /* Sort the message list, so all the msgid's are in order */
2354 num_msgs = sort_msglist(msglist, num_msgs);
2356 /* Determine the highest message number */
2357 highest_msg = msglist[num_msgs - 1];
2359 /* Write it back to disk. */
2360 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2361 msglist, (int)(num_msgs * sizeof(long)));
2363 /* Free up the memory we used. */
2366 /* Update the highest-message pointer and unlock the room. */
2367 CC->room.QRhighest = highest_msg;
2368 lputroom(&CC->room);
2370 /* Perform replication checks if necessary */
2371 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2372 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2374 for (i=0; i<num_msgs_to_be_merged; ++i) {
2375 msgid = msgs_to_be_merged[i];
2377 if (supplied_msg != NULL) {
2381 msg = CtdlFetchMessage(msgid, 0);
2385 ReplicationChecks(msg);
2387 /* If the message has an Exclusive ID, index that... */
2388 if (msg->cm_fields['E'] != NULL) {
2389 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2392 /* Free up the memory we may have allocated */
2393 if (msg != supplied_msg) {
2394 CtdlFreeMessage(msg);
2402 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2405 /* Submit this room for processing by hooks */
2406 PerformRoomHooks(&CC->room);
2408 /* Go back to the room we were in before we wandered here... */
2409 getroom(&CC->room, hold_rm);
2411 /* Bump the reference count for all messages which were merged */
2412 for (i=0; i<num_msgs_to_be_merged; ++i) {
2413 AdjRefCount(msgs_to_be_merged[i], +1);
2416 /* Free up memory... */
2417 if (msgs_to_be_merged != NULL) {
2418 free(msgs_to_be_merged);
2421 /* Return success. */
2427 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2430 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2431 int do_repl_check, struct CtdlMessage *supplied_msg)
2433 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2440 * Message base operation to save a new message to the message store
2441 * (returns new message number)
2443 * This is the back end for CtdlSubmitMsg() and should not be directly
2444 * called by server-side modules.
2447 long send_message(struct CtdlMessage *msg) {
2455 /* Get a new message number */
2456 newmsgid = get_new_message_number();
2457 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2459 /* Generate an ID if we don't have one already */
2460 if (msg->cm_fields['I']==NULL) {
2461 msg->cm_fields['I'] = strdup(msgidbuf);
2464 /* If the message is big, set its body aside for storage elsewhere */
2465 if (msg->cm_fields['M'] != NULL) {
2466 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2468 holdM = msg->cm_fields['M'];
2469 msg->cm_fields['M'] = NULL;
2473 /* Serialize our data structure for storage in the database */
2474 serialize_message(&smr, msg);
2477 msg->cm_fields['M'] = holdM;
2481 cprintf("%d Unable to serialize message\n",
2482 ERROR + INTERNAL_ERROR);
2486 /* Write our little bundle of joy into the message base */
2487 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2488 smr.ser, smr.len) < 0) {
2489 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2493 cdb_store(CDB_BIGMSGS,
2503 /* Free the memory we used for the serialized message */
2506 /* Return the *local* message ID to the caller
2507 * (even if we're storing an incoming network message)
2515 * Serialize a struct CtdlMessage into the format used on disk and network.
2517 * This function loads up a "struct ser_ret" (defined in server.h) which
2518 * contains the length of the serialized message and a pointer to the
2519 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2521 void serialize_message(struct ser_ret *ret, /* return values */
2522 struct CtdlMessage *msg) /* unserialized msg */
2524 size_t wlen, fieldlen;
2526 static char *forder = FORDER;
2529 * Check for valid message format
2531 if (is_valid_message(msg) == 0) {
2532 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2539 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2540 ret->len = ret->len +
2541 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2543 ret->ser = malloc(ret->len);
2544 if (ret->ser == NULL) {
2545 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2546 (long)ret->len, strerror(errno));
2553 ret->ser[1] = msg->cm_anon_type;
2554 ret->ser[2] = msg->cm_format_type;
2557 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2558 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2559 ret->ser[wlen++] = (char)forder[i];
2560 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2561 wlen = wlen + fieldlen + 1;
2563 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2564 (long)ret->len, (long)wlen);
2571 * Serialize a struct CtdlMessage into the format used on disk and network.
2573 * This function loads up a "struct ser_ret" (defined in server.h) which
2574 * contains the length of the serialized message and a pointer to the
2575 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2577 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2578 long Siz) /* how many chars ? */
2582 static char *forder = FORDER;
2586 * Check for valid message format
2588 if (is_valid_message(msg) == 0) {
2589 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2593 buf = (char*) malloc (Siz + 1);
2597 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2598 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2599 msg->cm_fields[(int)forder[i]]);
2600 client_write (buf, strlen(buf));
2609 * Check to see if any messages already exist in the current room which
2610 * carry the same Exclusive ID as this one. If any are found, delete them.
2612 void ReplicationChecks(struct CtdlMessage *msg) {
2613 long old_msgnum = (-1L);
2615 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2617 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2620 /* No exclusive id? Don't do anything. */
2621 if (msg == NULL) return;
2622 if (msg->cm_fields['E'] == NULL) return;
2623 if (IsEmptyStr(msg->cm_fields['E'])) return;
2624 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2625 msg->cm_fields['E'], CC->room.QRname);*/
2627 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2628 if (old_msgnum > 0L) {
2629 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2630 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2637 * Save a message to disk and submit it into the delivery system.
2639 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2640 struct recptypes *recps, /* recipients (if mail) */
2641 char *force, /* force a particular room? */
2642 int flags /* should the bessage be exported clean? */
2644 char submit_filename[128];
2645 char generated_timestamp[32];
2646 char hold_rm[ROOMNAMELEN];
2647 char actual_rm[ROOMNAMELEN];
2648 char force_room[ROOMNAMELEN];
2649 char content_type[SIZ]; /* We have to learn this */
2650 char recipient[SIZ];
2653 struct ctdluser userbuf;
2655 struct MetaData smi;
2656 FILE *network_fp = NULL;
2657 static int seqnum = 1;
2658 struct CtdlMessage *imsg = NULL;
2660 size_t instr_alloc = 0;
2662 char *hold_R, *hold_D;
2663 char *collected_addresses = NULL;
2664 struct addresses_to_be_filed *aptr = NULL;
2665 char *saved_rfc822_version = NULL;
2666 int qualified_for_journaling = 0;
2667 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2668 char bounce_to[1024] = "";
2672 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2673 if (is_valid_message(msg) == 0) return(-1); /* self check */
2675 /* If this message has no timestamp, we take the liberty of
2676 * giving it one, right now.
2678 if (msg->cm_fields['T'] == NULL) {
2679 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2680 msg->cm_fields['T'] = strdup(generated_timestamp);
2683 /* If this message has no path, we generate one.
2685 if (msg->cm_fields['P'] == NULL) {
2686 if (msg->cm_fields['A'] != NULL) {
2687 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2688 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2689 if (isspace(msg->cm_fields['P'][a])) {
2690 msg->cm_fields['P'][a] = ' ';
2695 msg->cm_fields['P'] = strdup("unknown");
2699 if (force == NULL) {
2700 strcpy(force_room, "");
2703 strcpy(force_room, force);
2706 /* Learn about what's inside, because it's what's inside that counts */
2707 if (msg->cm_fields['M'] == NULL) {
2708 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2712 switch (msg->cm_format_type) {
2714 strcpy(content_type, "text/x-citadel-variformat");
2717 strcpy(content_type, "text/plain");
2720 strcpy(content_type, "text/plain");
2721 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2724 safestrncpy(content_type, &mptr[13], sizeof content_type);
2725 striplt(content_type);
2726 aptr = content_type;
2727 while (!IsEmptyStr(aptr)) {
2739 /* Goto the correct room */
2740 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2741 strcpy(hold_rm, CCC->room.QRname);
2742 strcpy(actual_rm, CCC->room.QRname);
2743 if (recps != NULL) {
2744 strcpy(actual_rm, SENTITEMS);
2747 /* If the user is a twit, move to the twit room for posting */
2749 if (CCC->user.axlevel == 2) {
2750 strcpy(hold_rm, actual_rm);
2751 strcpy(actual_rm, config.c_twitroom);
2752 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2756 /* ...or if this message is destined for Aide> then go there. */
2757 if (!IsEmptyStr(force_room)) {
2758 strcpy(actual_rm, force_room);
2761 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2762 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2763 /* getroom(&CCC->room, actual_rm); */
2764 usergoto(actual_rm, 0, 1, NULL, NULL);
2768 * If this message has no O (room) field, generate one.
2770 if (msg->cm_fields['O'] == NULL) {
2771 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2774 /* Perform "before save" hooks (aborting if any return nonzero) */
2775 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2776 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2779 * If this message has an Exclusive ID, and the room is replication
2780 * checking enabled, then do replication checks.
2782 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2783 ReplicationChecks(msg);
2786 /* Save it to disk */
2787 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2788 newmsgid = send_message(msg);
2789 if (newmsgid <= 0L) return(-5);
2791 /* Write a supplemental message info record. This doesn't have to
2792 * be a critical section because nobody else knows about this message
2795 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2796 memset(&smi, 0, sizeof(struct MetaData));
2797 smi.meta_msgnum = newmsgid;
2798 smi.meta_refcount = 0;
2799 safestrncpy(smi.meta_content_type, content_type,
2800 sizeof smi.meta_content_type);
2803 * Measure how big this message will be when rendered as RFC822.
2804 * We do this for two reasons:
2805 * 1. We need the RFC822 length for the new metadata record, so the
2806 * POP and IMAP services don't have to calculate message lengths
2807 * while the user is waiting (multiplied by potentially hundreds
2808 * or thousands of messages).
2809 * 2. If journaling is enabled, we will need an RFC822 version of the
2810 * message to attach to the journalized copy.
2812 if (CCC->redirect_buffer != NULL) {
2813 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2816 CCC->redirect_buffer = malloc(SIZ);
2817 CCC->redirect_len = 0;
2818 CCC->redirect_alloc = SIZ;
2819 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2820 smi.meta_rfc822_length = CCC->redirect_len;
2821 saved_rfc822_version = CCC->redirect_buffer;
2822 CCC->redirect_buffer = NULL;
2823 CCC->redirect_len = 0;
2824 CCC->redirect_alloc = 0;
2828 /* Now figure out where to store the pointers */
2829 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2831 /* If this is being done by the networker delivering a private
2832 * message, we want to BYPASS saving the sender's copy (because there
2833 * is no local sender; it would otherwise go to the Trashcan).
2835 if ((!CCC->internal_pgm) || (recps == NULL)) {
2836 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2837 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2838 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2842 /* For internet mail, drop a copy in the outbound queue room */
2843 if ((recps != NULL) && (recps->num_internet > 0)) {
2844 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2847 /* If other rooms are specified, drop them there too. */
2848 if ((recps != NULL) && (recps->num_room > 0))
2849 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2850 extract_token(recipient, recps->recp_room, i,
2851 '|', sizeof recipient);
2852 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2853 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2856 /* Bump this user's messages posted counter. */
2857 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2858 lgetuser(&CCC->user, CCC->curr_user);
2859 CCC->user.posted = CCC->user.posted + 1;
2860 lputuser(&CCC->user);
2862 /* Decide where bounces need to be delivered */
2863 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2864 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2866 else if (CCC->logged_in) {
2867 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2870 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2873 /* If this is private, local mail, make a copy in the
2874 * recipient's mailbox and bump the reference count.
2876 if ((recps != NULL) && (recps->num_local > 0))
2877 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2878 extract_token(recipient, recps->recp_local, i,
2879 '|', sizeof recipient);
2880 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2882 if (getuser(&userbuf, recipient) == 0) {
2883 // Add a flag so the Funambol module knows its mail
2884 msg->cm_fields['W'] = strdup(recipient);
2885 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2886 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2887 BumpNewMailCounter(userbuf.usernum);
2888 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2889 /* Generate a instruction message for the Funambol notification
2890 * server, in the same style as the SMTP queue
2893 instr = malloc(instr_alloc);
2894 snprintf(instr, instr_alloc,
2895 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2897 SPOOLMIME, newmsgid, (long)time(NULL),
2901 imsg = malloc(sizeof(struct CtdlMessage));
2902 memset(imsg, 0, sizeof(struct CtdlMessage));
2903 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2904 imsg->cm_anon_type = MES_NORMAL;
2905 imsg->cm_format_type = FMT_RFC822;
2906 imsg->cm_fields['A'] = strdup("Citadel");
2907 imsg->cm_fields['J'] = strdup("do not journal");
2908 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2909 imsg->cm_fields['W'] = strdup(recipient);
2910 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2911 CtdlFreeMessage(imsg);
2915 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2916 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2921 /* Perform "after save" hooks */
2922 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2923 PerformMessageHooks(msg, EVT_AFTERSAVE);
2925 /* For IGnet mail, we have to save a new copy into the spooler for
2926 * each recipient, with the R and D fields set to the recipient and
2927 * destination-node. This has two ugly side effects: all other
2928 * recipients end up being unlisted in this recipient's copy of the
2929 * message, and it has to deliver multiple messages to the same
2930 * node. We'll revisit this again in a year or so when everyone has
2931 * a network spool receiver that can handle the new style messages.
2933 if ((recps != NULL) && (recps->num_ignet > 0))
2934 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2935 extract_token(recipient, recps->recp_ignet, i,
2936 '|', sizeof recipient);
2938 hold_R = msg->cm_fields['R'];
2939 hold_D = msg->cm_fields['D'];
2940 msg->cm_fields['R'] = malloc(SIZ);
2941 msg->cm_fields['D'] = malloc(128);
2942 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2943 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2945 serialize_message(&smr, msg);
2947 snprintf(submit_filename, sizeof submit_filename,
2948 "%s/netmail.%04lx.%04x.%04x",
2950 (long) getpid(), CCC->cs_pid, ++seqnum);
2951 network_fp = fopen(submit_filename, "wb+");
2952 if (network_fp != NULL) {
2953 rv = fwrite(smr.ser, smr.len, 1, network_fp);
2959 free(msg->cm_fields['R']);
2960 free(msg->cm_fields['D']);
2961 msg->cm_fields['R'] = hold_R;
2962 msg->cm_fields['D'] = hold_D;
2965 /* Go back to the room we started from */
2966 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2967 if (strcasecmp(hold_rm, CCC->room.QRname))
2968 usergoto(hold_rm, 0, 1, NULL, NULL);
2970 /* For internet mail, generate delivery instructions.
2971 * Yes, this is recursive. Deal with it. Infinite recursion does
2972 * not happen because the delivery instructions message does not
2973 * contain a recipient.
2975 if ((recps != NULL) && (recps->num_internet > 0)) {
2976 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2978 instr = malloc(instr_alloc);
2979 snprintf(instr, instr_alloc,
2980 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2982 SPOOLMIME, newmsgid, (long)time(NULL),
2986 if (recps->envelope_from != NULL) {
2987 tmp = strlen(instr);
2988 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
2991 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2992 tmp = strlen(instr);
2993 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2994 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2995 instr_alloc = instr_alloc * 2;
2996 instr = realloc(instr, instr_alloc);
2998 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3001 imsg = malloc(sizeof(struct CtdlMessage));
3002 memset(imsg, 0, sizeof(struct CtdlMessage));
3003 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3004 imsg->cm_anon_type = MES_NORMAL;
3005 imsg->cm_format_type = FMT_RFC822;
3006 imsg->cm_fields['A'] = strdup("Citadel");
3007 imsg->cm_fields['J'] = strdup("do not journal");
3008 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3009 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3010 CtdlFreeMessage(imsg);
3014 * Any addresses to harvest for someone's address book?
3016 if ( (CCC->logged_in) && (recps != NULL) ) {
3017 collected_addresses = harvest_collected_addresses(msg);
3020 if (collected_addresses != NULL) {
3021 aptr = (struct addresses_to_be_filed *)
3022 malloc(sizeof(struct addresses_to_be_filed));
3023 MailboxName(actual_rm, sizeof actual_rm,
3024 &CCC->user, USERCONTACTSROOM);
3025 aptr->roomname = strdup(actual_rm);
3026 aptr->collected_addresses = collected_addresses;
3027 begin_critical_section(S_ATBF);
3030 end_critical_section(S_ATBF);
3034 * Determine whether this message qualifies for journaling.
3036 if (msg->cm_fields['J'] != NULL) {
3037 qualified_for_journaling = 0;
3040 if (recps == NULL) {
3041 qualified_for_journaling = config.c_journal_pubmsgs;
3043 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3044 qualified_for_journaling = config.c_journal_email;
3047 qualified_for_journaling = config.c_journal_pubmsgs;
3052 * Do we have to perform journaling? If so, hand off the saved
3053 * RFC822 version will be handed off to the journaler for background
3054 * submit. Otherwise, we have to free the memory ourselves.
3056 if (saved_rfc822_version != NULL) {
3057 if (qualified_for_journaling) {
3058 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3061 free(saved_rfc822_version);
3074 * Convenience function for generating small administrative messages.
3076 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3077 int format_type, const char *subject)
3079 struct CtdlMessage *msg;
3080 struct recptypes *recp = NULL;
3082 msg = malloc(sizeof(struct CtdlMessage));
3083 memset(msg, 0, sizeof(struct CtdlMessage));
3084 msg->cm_magic = CTDLMESSAGE_MAGIC;
3085 msg->cm_anon_type = MES_NORMAL;
3086 msg->cm_format_type = format_type;
3089 msg->cm_fields['A'] = strdup(from);
3091 else if (fromaddr != NULL) {
3092 msg->cm_fields['A'] = strdup(fromaddr);
3093 if (strchr(msg->cm_fields['A'], '@')) {
3094 *strchr(msg->cm_fields['A'], '@') = 0;
3098 msg->cm_fields['A'] = strdup("Citadel");
3101 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3102 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3103 msg->cm_fields['N'] = strdup(NODENAME);
3105 msg->cm_fields['R'] = strdup(to);
3106 recp = validate_recipients(to, NULL, 0);
3108 if (subject != NULL) {
3109 msg->cm_fields['U'] = strdup(subject);
3111 msg->cm_fields['M'] = strdup(text);
3113 CtdlSubmitMsg(msg, recp, room, 0);
3114 CtdlFreeMessage(msg);
3115 if (recp != NULL) free_recipients(recp);
3121 * Back end function used by CtdlMakeMessage() and similar functions
3123 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3124 size_t maxlen, /* maximum message length */
3125 char *exist, /* if non-null, append to it;
3126 exist is ALWAYS freed */
3127 int crlf, /* CRLF newlines instead of LF */
3128 int sock /* socket handle or 0 for this session's client socket */
3132 size_t message_len = 0;
3133 size_t buffer_len = 0;
3140 if (exist == NULL) {
3147 message_len = strlen(exist);
3148 buffer_len = message_len + 4096;
3149 m = realloc(exist, buffer_len);
3156 /* Do we need to change leading ".." to "." for SMTP escaping? */
3157 if (!strcmp(terminator, ".")) {
3161 /* flush the input if we have nowhere to store it */
3166 /* read in the lines of message text one by one */
3169 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3172 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3174 if (!strcmp(buf, terminator)) finished = 1;
3176 strcat(buf, "\r\n");
3182 /* Unescape SMTP-style input of two dots at the beginning of the line */
3184 if (!strncmp(buf, "..", 2)) {
3185 strcpy(buf, &buf[1]);
3189 if ( (!flushing) && (!finished) ) {
3190 /* Measure the line */
3191 linelen = strlen(buf);
3193 /* augment the buffer if we have to */
3194 if ((message_len + linelen) >= buffer_len) {
3195 ptr = realloc(m, (buffer_len * 2) );
3196 if (ptr == NULL) { /* flush if can't allocate */
3199 buffer_len = (buffer_len * 2);
3201 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3205 /* Add the new line to the buffer. NOTE: this loop must avoid
3206 * using functions like strcat() and strlen() because they
3207 * traverse the entire buffer upon every call, and doing that
3208 * for a multi-megabyte message slows it down beyond usability.
3210 strcpy(&m[message_len], buf);
3211 message_len += linelen;
3214 /* if we've hit the max msg length, flush the rest */
3215 if (message_len >= maxlen) flushing = 1;
3217 } while (!finished);
3225 * Build a binary message to be saved on disk.
3226 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3227 * will become part of the message. This means you are no longer
3228 * responsible for managing that memory -- it will be freed along with
3229 * the rest of the fields when CtdlFreeMessage() is called.)
3232 struct CtdlMessage *CtdlMakeMessage(
3233 struct ctdluser *author, /* author's user structure */
3234 char *recipient, /* NULL if it's not mail */
3235 char *recp_cc, /* NULL if it's not mail */
3236 char *room, /* room where it's going */
3237 int type, /* see MES_ types in header file */
3238 int format_type, /* variformat, plain text, MIME... */
3239 char *fake_name, /* who we're masquerading as */
3240 char *my_email, /* which of my email addresses to use (empty is ok) */
3241 char *subject, /* Subject (optional) */
3242 char *supplied_euid, /* ...or NULL if this is irrelevant */
3243 char *preformatted_text, /* ...or NULL to read text from client */
3244 char *references /* Thread references */
3246 char dest_node[256];
3248 struct CtdlMessage *msg;
3250 msg = malloc(sizeof(struct CtdlMessage));
3251 memset(msg, 0, sizeof(struct CtdlMessage));
3252 msg->cm_magic = CTDLMESSAGE_MAGIC;
3253 msg->cm_anon_type = type;
3254 msg->cm_format_type = format_type;
3256 /* Don't confuse the poor folks if it's not routed mail. */
3257 strcpy(dest_node, "");
3259 if (recipient != NULL) striplt(recipient);
3260 if (recp_cc != NULL) striplt(recp_cc);
3262 /* Path or Return-Path */
3263 if (my_email == NULL) my_email = "";
3265 if (!IsEmptyStr(my_email)) {
3266 msg->cm_fields['P'] = strdup(my_email);
3269 snprintf(buf, sizeof buf, "%s", author->fullname);
3270 msg->cm_fields['P'] = strdup(buf);
3272 convert_spaces_to_underscores(msg->cm_fields['P']);
3274 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3275 msg->cm_fields['T'] = strdup(buf);
3277 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3278 msg->cm_fields['A'] = strdup(fake_name);
3281 msg->cm_fields['A'] = strdup(author->fullname);
3284 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3285 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3288 msg->cm_fields['O'] = strdup(CC->room.QRname);
3291 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3292 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3294 if ((recipient != NULL) && (recipient[0] != 0)) {
3295 msg->cm_fields['R'] = strdup(recipient);
3297 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3298 msg->cm_fields['Y'] = strdup(recp_cc);
3300 if (dest_node[0] != 0) {
3301 msg->cm_fields['D'] = strdup(dest_node);
3304 if (!IsEmptyStr(my_email)) {
3305 msg->cm_fields['F'] = strdup(my_email);
3307 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3308 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3311 if (subject != NULL) {
3314 length = strlen(subject);
3320 while ((subject[i] != '\0') &&
3321 (IsAscii = isascii(subject[i]) != 0 ))
3324 msg->cm_fields['U'] = strdup(subject);
3325 else /* ok, we've got utf8 in the string. */
3327 msg->cm_fields['U'] = rfc2047encode(subject, length);
3333 if (supplied_euid != NULL) {
3334 msg->cm_fields['E'] = strdup(supplied_euid);
3337 if (references != NULL) {
3338 if (!IsEmptyStr(references)) {
3339 msg->cm_fields['W'] = strdup(references);
3343 if (preformatted_text != NULL) {
3344 msg->cm_fields['M'] = preformatted_text;
3347 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3355 * Check to see whether we have permission to post a message in the current
3356 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3357 * returns 0 on success.
3359 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3361 const char* RemoteIdentifier,
3365 if (!(CC->logged_in) &&
3366 (PostPublic == POST_LOGGED_IN)) {
3367 snprintf(errmsgbuf, n, "Not logged in.");
3368 return (ERROR + NOT_LOGGED_IN);
3370 else if (PostPublic == CHECK_EXISTANCE) {
3371 return (0); // We're Evaling whether a recipient exists
3373 else if (!(CC->logged_in)) {
3375 if ((CC->room.QRflags & QR_READONLY)) {
3376 snprintf(errmsgbuf, n, "Not logged in.");
3377 return (ERROR + NOT_LOGGED_IN);
3379 if (CC->room.QRflags2 & QR2_MODERATED) {
3380 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3381 return (ERROR + NOT_LOGGED_IN);
3383 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3388 if (RemoteIdentifier == NULL)
3390 snprintf(errmsgbuf, n, "Need sender to permit access.");
3391 return (ERROR + USERNAME_REQUIRED);
3394 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3395 begin_critical_section(S_NETCONFIGS);
3396 if (!read_spoolcontrol_file(&sc, filename))
3398 end_critical_section(S_NETCONFIGS);
3399 snprintf(errmsgbuf, n,
3400 "This mailing list only accepts posts from subscribers.");
3401 return (ERROR + NO_SUCH_USER);
3403 end_critical_section(S_NETCONFIGS);
3404 found = is_recipient (sc, RemoteIdentifier);
3405 free_spoolcontrol_struct(&sc);
3410 snprintf(errmsgbuf, n,
3411 "This mailing list only accepts posts from subscribers.");
3412 return (ERROR + NO_SUCH_USER);
3419 if ((CC->user.axlevel < 2)
3420 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3421 snprintf(errmsgbuf, n, "Need to be validated to enter "
3422 "(except in %s> to sysop)", MAILROOM);
3423 return (ERROR + HIGHER_ACCESS_REQUIRED);
3426 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3427 if (!(ra & UA_POSTALLOWED)) {
3428 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3429 return (ERROR + HIGHER_ACCESS_REQUIRED);
3432 strcpy(errmsgbuf, "Ok");
3438 * Check to see if the specified user has Internet mail permission
3439 * (returns nonzero if permission is granted)
3441 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3443 /* Do not allow twits to send Internet mail */
3444 if (who->axlevel <= 2) return(0);
3446 /* Globally enabled? */
3447 if (config.c_restrict == 0) return(1);
3449 /* User flagged ok? */
3450 if (who->flags & US_INTERNET) return(2);
3452 /* Aide level access? */
3453 if (who->axlevel >= 6) return(3);
3455 /* No mail for you! */
3461 * Validate recipients, count delivery types and errors, and handle aliasing
3462 * FIXME check for dupes!!!!!
3464 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3465 * were specified, or the number of addresses found invalid.
3467 * Caller needs to free the result using free_recipients()
3469 struct recptypes *validate_recipients(char *supplied_recipients,
3470 const char *RemoteIdentifier,
3472 struct recptypes *ret;
3473 char *recipients = NULL;
3474 char this_recp[256];
3475 char this_recp_cooked[256];
3481 struct ctdluser tempUS;
3482 struct ctdlroom tempQR;
3483 struct ctdlroom tempQR2;
3489 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3490 if (ret == NULL) return(NULL);
3492 /* Set all strings to null and numeric values to zero */
3493 memset(ret, 0, sizeof(struct recptypes));
3495 if (supplied_recipients == NULL) {
3496 recipients = strdup("");
3499 recipients = strdup(supplied_recipients);
3502 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3503 * actually need, but it's healthier for the heap than doing lots of tiny
3504 * realloc() calls instead.
3507 ret->errormsg = malloc(strlen(recipients) + 1024);
3508 ret->recp_local = malloc(strlen(recipients) + 1024);
3509 ret->recp_internet = malloc(strlen(recipients) + 1024);
3510 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3511 ret->recp_room = malloc(strlen(recipients) + 1024);
3512 ret->display_recp = malloc(strlen(recipients) + 1024);
3514 ret->errormsg[0] = 0;
3515 ret->recp_local[0] = 0;
3516 ret->recp_internet[0] = 0;
3517 ret->recp_ignet[0] = 0;
3518 ret->recp_room[0] = 0;
3519 ret->display_recp[0] = 0;
3521 ret->recptypes_magic = RECPTYPES_MAGIC;
3523 /* Change all valid separator characters to commas */
3524 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3525 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3526 recipients[i] = ',';
3530 /* Now start extracting recipients... */
3532 while (!IsEmptyStr(recipients)) {
3534 for (i=0; i<=strlen(recipients); ++i) {
3535 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3536 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3537 safestrncpy(this_recp, recipients, i+1);
3539 if (recipients[i] == ',') {
3540 strcpy(recipients, &recipients[i+1]);
3543 strcpy(recipients, "");
3550 if (IsEmptyStr(this_recp))
3552 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3554 mailtype = alias(this_recp);
3555 mailtype = alias(this_recp);
3556 mailtype = alias(this_recp);
3558 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3559 if (this_recp[j]=='_') {
3560 this_recp_cooked[j] = ' ';
3563 this_recp_cooked[j] = this_recp[j];
3566 this_recp_cooked[j] = '\0';
3571 if (!strcasecmp(this_recp, "sysop")) {
3573 strcpy(this_recp, config.c_aideroom);
3574 if (!IsEmptyStr(ret->recp_room)) {
3575 strcat(ret->recp_room, "|");
3577 strcat(ret->recp_room, this_recp);
3579 else if ( (!strncasecmp(this_recp, "room_", 5))
3580 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3582 /* Save room so we can restore it later */
3586 /* Check permissions to send mail to this room */
3587 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3599 if (!IsEmptyStr(ret->recp_room)) {
3600 strcat(ret->recp_room, "|");
3602 strcat(ret->recp_room, &this_recp_cooked[5]);
3605 /* Restore room in case something needs it */
3609 else if (getuser(&tempUS, this_recp) == 0) {
3611 strcpy(this_recp, tempUS.fullname);
3612 if (!IsEmptyStr(ret->recp_local)) {
3613 strcat(ret->recp_local, "|");
3615 strcat(ret->recp_local, this_recp);
3617 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3619 strcpy(this_recp, tempUS.fullname);
3620 if (!IsEmptyStr(ret->recp_local)) {
3621 strcat(ret->recp_local, "|");
3623 strcat(ret->recp_local, this_recp);
3631 /* Yes, you're reading this correctly: if the target
3632 * domain points back to the local system or an attached
3633 * Citadel directory, the address is invalid. That's
3634 * because if the address were valid, we would have
3635 * already translated it to a local address by now.
3637 if (IsDirectory(this_recp, 0)) {
3642 ++ret->num_internet;
3643 if (!IsEmptyStr(ret->recp_internet)) {
3644 strcat(ret->recp_internet, "|");
3646 strcat(ret->recp_internet, this_recp);
3651 if (!IsEmptyStr(ret->recp_ignet)) {
3652 strcat(ret->recp_ignet, "|");
3654 strcat(ret->recp_ignet, this_recp);
3662 if (IsEmptyStr(errmsg)) {
3663 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3666 snprintf(append, sizeof append, "%s", errmsg);
3668 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3669 if (!IsEmptyStr(ret->errormsg)) {
3670 strcat(ret->errormsg, "; ");
3672 strcat(ret->errormsg, append);
3676 if (IsEmptyStr(ret->display_recp)) {
3677 strcpy(append, this_recp);
3680 snprintf(append, sizeof append, ", %s", this_recp);
3682 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3683 strcat(ret->display_recp, append);
3688 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3689 ret->num_room + ret->num_error) == 0) {
3690 ret->num_error = (-1);
3691 strcpy(ret->errormsg, "No recipients specified.");
3694 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3695 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3696 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3697 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3698 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3699 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3707 * Destructor for struct recptypes
3709 void free_recipients(struct recptypes *valid) {
3711 if (valid == NULL) {
3715 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3716 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3720 if (valid->errormsg != NULL) free(valid->errormsg);
3721 if (valid->recp_local != NULL) free(valid->recp_local);
3722 if (valid->recp_internet != NULL) free(valid->recp_internet);
3723 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3724 if (valid->recp_room != NULL) free(valid->recp_room);
3725 if (valid->display_recp != NULL) free(valid->display_recp);
3726 if (valid->bounce_to != NULL) free(valid->bounce_to);
3727 if (valid->envelope_from != NULL) free(valid->envelope_from);
3734 * message entry - mode 0 (normal)
3736 void cmd_ent0(char *entargs)
3742 char supplied_euid[128];
3744 int format_type = 0;
3745 char newusername[256];
3746 char newuseremail[256];
3747 struct CtdlMessage *msg;
3751 struct recptypes *valid = NULL;
3752 struct recptypes *valid_to = NULL;
3753 struct recptypes *valid_cc = NULL;
3754 struct recptypes *valid_bcc = NULL;
3756 int subject_required = 0;
3761 int newuseremail_ok = 0;
3762 char references[SIZ];
3767 post = extract_int(entargs, 0);
3768 extract_token(recp, entargs, 1, '|', sizeof recp);
3769 anon_flag = extract_int(entargs, 2);
3770 format_type = extract_int(entargs, 3);
3771 extract_token(subject, entargs, 4, '|', sizeof subject);
3772 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3773 do_confirm = extract_int(entargs, 6);
3774 extract_token(cc, entargs, 7, '|', sizeof cc);
3775 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3776 switch(CC->room.QRdefaultview) {
3779 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3782 supplied_euid[0] = 0;
3785 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3786 extract_token(references, entargs, 11, '|', sizeof references);
3787 for (ptr=references; *ptr != 0; ++ptr) {
3788 if (*ptr == '!') *ptr = '|';
3791 /* first check to make sure the request is valid. */
3793 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3796 cprintf("%d %s\n", err, errmsg);
3800 /* Check some other permission type things. */
3802 if (IsEmptyStr(newusername)) {
3803 strcpy(newusername, CC->user.fullname);
3805 if ( (CC->user.axlevel < 6)
3806 && (strcasecmp(newusername, CC->user.fullname))
3807 && (strcasecmp(newusername, CC->cs_inet_fn))
3809 cprintf("%d You don't have permission to author messages as '%s'.\n",
3810 ERROR + HIGHER_ACCESS_REQUIRED,
3817 if (IsEmptyStr(newuseremail)) {
3818 newuseremail_ok = 1;
3821 if (!IsEmptyStr(newuseremail)) {
3822 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3823 newuseremail_ok = 1;
3825 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3826 j = num_tokens(CC->cs_inet_other_emails, '|');
3827 for (i=0; i<j; ++i) {
3828 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3829 if (!strcasecmp(newuseremail, buf)) {
3830 newuseremail_ok = 1;
3836 if (!newuseremail_ok) {
3837 cprintf("%d You don't have permission to author messages as '%s'.\n",
3838 ERROR + HIGHER_ACCESS_REQUIRED,
3844 CC->cs_flags |= CS_POSTING;
3846 /* In mailbox rooms we have to behave a little differently --
3847 * make sure the user has specified at least one recipient. Then
3848 * validate the recipient(s). We do this for the Mail> room, as
3849 * well as any room which has the "Mailbox" view set.
3852 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3853 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3855 if (CC->user.axlevel < 2) {
3856 strcpy(recp, "sysop");
3861 valid_to = validate_recipients(recp, NULL, 0);
3862 if (valid_to->num_error > 0) {
3863 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3864 free_recipients(valid_to);
3868 valid_cc = validate_recipients(cc, NULL, 0);
3869 if (valid_cc->num_error > 0) {
3870 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3871 free_recipients(valid_to);
3872 free_recipients(valid_cc);
3876 valid_bcc = validate_recipients(bcc, NULL, 0);
3877 if (valid_bcc->num_error > 0) {
3878 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3879 free_recipients(valid_to);
3880 free_recipients(valid_cc);
3881 free_recipients(valid_bcc);
3885 /* Recipient required, but none were specified */
3886 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3887 free_recipients(valid_to);
3888 free_recipients(valid_cc);
3889 free_recipients(valid_bcc);
3890 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3894 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3895 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3896 cprintf("%d You do not have permission "
3897 "to send Internet mail.\n",
3898 ERROR + HIGHER_ACCESS_REQUIRED);
3899 free_recipients(valid_to);
3900 free_recipients(valid_cc);
3901 free_recipients(valid_bcc);
3906 if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0)
3907 && (CC->user.axlevel < 4) ) {
3908 cprintf("%d Higher access required for network mail.\n",
3909 ERROR + HIGHER_ACCESS_REQUIRED);
3910 free_recipients(valid_to);
3911 free_recipients(valid_cc);
3912 free_recipients(valid_bcc);
3916 if ((RESTRICT_INTERNET == 1)
3917 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3918 && ((CC->user.flags & US_INTERNET) == 0)
3919 && (!CC->internal_pgm)) {
3920 cprintf("%d You don't have access to Internet mail.\n",
3921 ERROR + HIGHER_ACCESS_REQUIRED);
3922 free_recipients(valid_to);
3923 free_recipients(valid_cc);
3924 free_recipients(valid_bcc);
3930 /* Is this a room which has anonymous-only or anonymous-option? */
3931 anonymous = MES_NORMAL;
3932 if (CC->room.QRflags & QR_ANONONLY) {
3933 anonymous = MES_ANONONLY;
3935 if (CC->room.QRflags & QR_ANONOPT) {
3936 if (anon_flag == 1) { /* only if the user requested it */
3937 anonymous = MES_ANONOPT;
3941 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3945 /* Recommend to the client that the use of a message subject is
3946 * strongly recommended in this room, if either the SUBJECTREQ flag
3947 * is set, or if there is one or more Internet email recipients.
3949 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3950 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3951 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3952 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3954 /* If we're only checking the validity of the request, return
3955 * success without creating the message.
3958 cprintf("%d %s|%d\n", CIT_OK,
3959 ((valid_to != NULL) ? valid_to->display_recp : ""),
3961 free_recipients(valid_to);
3962 free_recipients(valid_cc);
3963 free_recipients(valid_bcc);
3967 /* We don't need these anymore because we'll do it differently below */
3968 free_recipients(valid_to);
3969 free_recipients(valid_cc);
3970 free_recipients(valid_bcc);
3972 /* Read in the message from the client. */
3974 cprintf("%d send message\n", START_CHAT_MODE);
3976 cprintf("%d send message\n", SEND_LISTING);
3979 msg = CtdlMakeMessage(&CC->user, recp, cc,
3980 CC->room.QRname, anonymous, format_type,
3981 newusername, newuseremail, subject,
3982 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3985 /* Put together one big recipients struct containing to/cc/bcc all in
3986 * one. This is for the envelope.
3988 char *all_recps = malloc(SIZ * 3);
3989 strcpy(all_recps, recp);
3990 if (!IsEmptyStr(cc)) {
3991 if (!IsEmptyStr(all_recps)) {
3992 strcat(all_recps, ",");
3994 strcat(all_recps, cc);
3996 if (!IsEmptyStr(bcc)) {
3997 if (!IsEmptyStr(all_recps)) {
3998 strcat(all_recps, ",");
4000 strcat(all_recps, bcc);
4002 if (!IsEmptyStr(all_recps)) {
4003 valid = validate_recipients(all_recps, NULL, 0);
4011 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4014 cprintf("%ld\n", msgnum);
4016 cprintf("Message accepted.\n");
4019 cprintf("Internal error.\n");
4021 if (msg->cm_fields['E'] != NULL) {
4022 cprintf("%s\n", msg->cm_fields['E']);
4029 CtdlFreeMessage(msg);
4031 if (valid != NULL) {
4032 free_recipients(valid);
4040 * API function to delete messages which match a set of criteria
4041 * (returns the actual number of messages deleted)
4043 int CtdlDeleteMessages(char *room_name, /* which room */
4044 long *dmsgnums, /* array of msg numbers to be deleted */
4045 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4046 char *content_type /* or "" for any. regular expressions expected. */
4049 struct ctdlroom qrbuf;
4050 struct cdbdata *cdbfr;
4051 long *msglist = NULL;
4052 long *dellist = NULL;
4055 int num_deleted = 0;
4057 struct MetaData smi;
4060 int need_to_free_re = 0;
4062 if (content_type) if (!IsEmptyStr(content_type)) {
4063 regcomp(&re, content_type, 0);
4064 need_to_free_re = 1;
4066 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4067 room_name, num_dmsgnums, content_type);
4069 /* get room record, obtaining a lock... */
4070 if (lgetroom(&qrbuf, room_name) != 0) {
4071 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4073 if (need_to_free_re) regfree(&re);
4074 return (0); /* room not found */
4076 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4078 if (cdbfr != NULL) {
4079 dellist = malloc(cdbfr->len);
4080 msglist = (long *) cdbfr->ptr;
4081 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4082 num_msgs = cdbfr->len / sizeof(long);
4086 for (i = 0; i < num_msgs; ++i) {
4089 /* Set/clear a bit for each criterion */
4091 /* 0 messages in the list or a null list means that we are
4092 * interested in deleting any messages which meet the other criteria.
4094 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4095 delete_this |= 0x01;
4098 for (j=0; j<num_dmsgnums; ++j) {
4099 if (msglist[i] == dmsgnums[j]) {
4100 delete_this |= 0x01;
4105 if (IsEmptyStr(content_type)) {
4106 delete_this |= 0x02;
4108 GetMetaData(&smi, msglist[i]);
4109 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4110 delete_this |= 0x02;
4114 /* Delete message only if all bits are set */
4115 if (delete_this == 0x03) {
4116 dellist[num_deleted++] = msglist[i];
4121 num_msgs = sort_msglist(msglist, num_msgs);
4122 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4123 msglist, (int)(num_msgs * sizeof(long)));
4125 qrbuf.QRhighest = msglist[num_msgs - 1];
4129 /* Go through the messages we pulled out of the index, and decrement
4130 * their reference counts by 1. If this is the only room the message
4131 * was in, the reference count will reach zero and the message will
4132 * automatically be deleted from the database. We do this in a
4133 * separate pass because there might be plug-in hooks getting called,
4134 * and we don't want that happening during an S_ROOMS critical
4137 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4138 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4139 AdjRefCount(dellist[i], -1);
4142 /* Now free the memory we used, and go away. */
4143 if (msglist != NULL) free(msglist);
4144 if (dellist != NULL) free(dellist);
4145 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4146 if (need_to_free_re) regfree(&re);
4147 return (num_deleted);
4153 * Check whether the current user has permission to delete messages from
4154 * the current room (returns 1 for yes, 0 for no)
4156 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4158 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4159 if (ra & UA_DELETEALLOWED) return(1);
4167 * Delete message from current room
4169 void cmd_dele(char *args)
4178 extract_token(msgset, args, 0, '|', sizeof msgset);
4179 num_msgs = num_tokens(msgset, ',');
4181 cprintf("%d Nothing to do.\n", CIT_OK);
4185 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4186 cprintf("%d Higher access required.\n",
4187 ERROR + HIGHER_ACCESS_REQUIRED);
4192 * Build our message set to be moved/copied
4194 msgs = malloc(num_msgs * sizeof(long));
4195 for (i=0; i<num_msgs; ++i) {
4196 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4197 msgs[i] = atol(msgtok);
4200 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4204 cprintf("%d %d message%s deleted.\n", CIT_OK,
4205 num_deleted, ((num_deleted != 1) ? "s" : ""));
4207 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4215 * move or copy a message to another room
4217 void cmd_move(char *args)
4224 char targ[ROOMNAMELEN];
4225 struct ctdlroom qtemp;
4232 extract_token(msgset, args, 0, '|', sizeof msgset);
4233 num_msgs = num_tokens(msgset, ',');
4235 cprintf("%d Nothing to do.\n", CIT_OK);
4239 extract_token(targ, args, 1, '|', sizeof targ);
4240 convert_room_name_macros(targ, sizeof targ);
4241 targ[ROOMNAMELEN - 1] = 0;
4242 is_copy = extract_int(args, 2);
4244 if (getroom(&qtemp, targ) != 0) {
4245 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4249 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4250 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4254 getuser(&CC->user, CC->curr_user);
4255 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4257 /* Check for permission to perform this operation.
4258 * Remember: "CC->room" is source, "qtemp" is target.
4262 /* Aides can move/copy */
4263 if (CC->user.axlevel >= 6) permit = 1;
4265 /* Room aides can move/copy */
4266 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4268 /* Permit move/copy from personal rooms */
4269 if ((CC->room.QRflags & QR_MAILBOX)
4270 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4272 /* Permit only copy from public to personal room */
4274 && (!(CC->room.QRflags & QR_MAILBOX))
4275 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4277 /* Permit message removal from collaborative delete rooms */
4278 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4280 /* Users allowed to post into the target room may move into it too. */
4281 if ((CC->room.QRflags & QR_MAILBOX) &&
4282 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4284 /* User must have access to target room */
4285 if (!(ra & UA_KNOWN)) permit = 0;
4288 cprintf("%d Higher access required.\n",
4289 ERROR + HIGHER_ACCESS_REQUIRED);
4294 * Build our message set to be moved/copied
4296 msgs = malloc(num_msgs * sizeof(long));
4297 for (i=0; i<num_msgs; ++i) {
4298 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4299 msgs[i] = atol(msgtok);
4305 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4307 cprintf("%d Cannot store message(s) in %s: error %d\n",
4313 /* Now delete the message from the source room,
4314 * if this is a 'move' rather than a 'copy' operation.
4317 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4321 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4327 * GetMetaData() - Get the supplementary record for a message
4329 void GetMetaData(struct MetaData *smibuf, long msgnum)
4332 struct cdbdata *cdbsmi;
4335 memset(smibuf, 0, sizeof(struct MetaData));
4336 smibuf->meta_msgnum = msgnum;
4337 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4339 /* Use the negative of the message number for its supp record index */
4340 TheIndex = (0L - msgnum);
4342 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4343 if (cdbsmi == NULL) {
4344 return; /* record not found; go with defaults */
4346 memcpy(smibuf, cdbsmi->ptr,
4347 ((cdbsmi->len > sizeof(struct MetaData)) ?
4348 sizeof(struct MetaData) : cdbsmi->len));
4355 * PutMetaData() - (re)write supplementary record for a message
4357 void PutMetaData(struct MetaData *smibuf)
4361 /* Use the negative of the message number for the metadata db index */
4362 TheIndex = (0L - smibuf->meta_msgnum);
4364 cdb_store(CDB_MSGMAIN,
4365 &TheIndex, (int)sizeof(long),
4366 smibuf, (int)sizeof(struct MetaData));
4371 * AdjRefCount - submit an adjustment to the reference count for a message.
4372 * (These are just queued -- we actually process them later.)
4374 void AdjRefCount(long msgnum, int incr)
4376 struct arcq new_arcq;
4379 begin_critical_section(S_SUPPMSGMAIN);
4380 if (arcfp == NULL) {
4381 arcfp = fopen(file_arcq, "ab+");
4383 end_critical_section(S_SUPPMSGMAIN);
4385 /* msgnum < 0 means that we're trying to close the file */
4387 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4388 begin_critical_section(S_SUPPMSGMAIN);
4389 if (arcfp != NULL) {
4393 end_critical_section(S_SUPPMSGMAIN);
4398 * If we can't open the queue, perform the operation synchronously.
4400 if (arcfp == NULL) {
4401 TDAP_AdjRefCount(msgnum, incr);
4405 new_arcq.arcq_msgnum = msgnum;
4406 new_arcq.arcq_delta = incr;
4407 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4415 * TDAP_ProcessAdjRefCountQueue()
4417 * Process the queue of message count adjustments that was created by calls
4418 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4419 * for each one. This should be an "off hours" operation.
4421 int TDAP_ProcessAdjRefCountQueue(void)
4423 char file_arcq_temp[PATH_MAX];
4426 struct arcq arcq_rec;
4427 int num_records_processed = 0;
4429 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4431 begin_critical_section(S_SUPPMSGMAIN);
4432 if (arcfp != NULL) {
4437 r = link(file_arcq, file_arcq_temp);
4439 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4440 end_critical_section(S_SUPPMSGMAIN);
4441 return(num_records_processed);
4445 end_critical_section(S_SUPPMSGMAIN);
4447 fp = fopen(file_arcq_temp, "rb");
4449 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4450 return(num_records_processed);
4453 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4454 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4455 ++num_records_processed;
4459 r = unlink(file_arcq_temp);
4461 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4464 return(num_records_processed);
4470 * TDAP_AdjRefCount - adjust the reference count for a message.
4471 * This one does it "for real" because it's called by
4472 * the autopurger function that processes the queue
4473 * created by AdjRefCount(). If a message's reference
4474 * count becomes zero, we also delete the message from
4475 * disk and de-index it.
4477 void TDAP_AdjRefCount(long msgnum, int incr)
4480 struct MetaData smi;
4483 /* This is a *tight* critical section; please keep it that way, as
4484 * it may get called while nested in other critical sections.
4485 * Complicating this any further will surely cause deadlock!
4487 begin_critical_section(S_SUPPMSGMAIN);
4488 GetMetaData(&smi, msgnum);
4489 smi.meta_refcount += incr;
4491 end_critical_section(S_SUPPMSGMAIN);
4492 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4493 msgnum, incr, smi.meta_refcount);
4495 /* If the reference count is now zero, delete the message
4496 * (and its supplementary record as well).
4498 if (smi.meta_refcount == 0) {
4499 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4501 /* Call delete hooks with NULL room to show it has gone altogether */
4502 PerformDeleteHooks(NULL, msgnum);
4504 /* Remove from message base */
4506 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4507 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4509 /* Remove metadata record */
4510 delnum = (0L - msgnum);
4511 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4517 * Write a generic object to this room
4519 * Note: this could be much more efficient. Right now we use two temporary
4520 * files, and still pull the message into memory as with all others.
4522 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4523 char *content_type, /* MIME type of this object */
4524 char *raw_message, /* Data to be written */
4525 off_t raw_length, /* Size of raw_message */
4526 struct ctdluser *is_mailbox, /* Mailbox room? */
4527 int is_binary, /* Is encoding necessary? */
4528 int is_unique, /* Del others of this type? */
4529 unsigned int flags /* Internal save flags */
4533 struct ctdlroom qrbuf;
4534 char roomname[ROOMNAMELEN];
4535 struct CtdlMessage *msg;
4536 char *encoded_message = NULL;
4538 if (is_mailbox != NULL) {
4539 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4542 safestrncpy(roomname, req_room, sizeof(roomname));
4545 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4548 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4551 encoded_message = malloc((size_t)(raw_length + 4096));
4554 sprintf(encoded_message, "Content-type: %s\n", content_type);
4557 sprintf(&encoded_message[strlen(encoded_message)],
4558 "Content-transfer-encoding: base64\n\n"
4562 sprintf(&encoded_message[strlen(encoded_message)],
4563 "Content-transfer-encoding: 7bit\n\n"
4569 &encoded_message[strlen(encoded_message)],
4577 &encoded_message[strlen(encoded_message)],
4583 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4584 msg = malloc(sizeof(struct CtdlMessage));
4585 memset(msg, 0, sizeof(struct CtdlMessage));
4586 msg->cm_magic = CTDLMESSAGE_MAGIC;
4587 msg->cm_anon_type = MES_NORMAL;
4588 msg->cm_format_type = 4;
4589 msg->cm_fields['A'] = strdup(CC->user.fullname);
4590 msg->cm_fields['O'] = strdup(req_room);
4591 msg->cm_fields['N'] = strdup(config.c_nodename);
4592 msg->cm_fields['H'] = strdup(config.c_humannode);
4593 msg->cm_flags = flags;
4595 msg->cm_fields['M'] = encoded_message;
4597 /* Create the requested room if we have to. */
4598 if (getroom(&qrbuf, roomname) != 0) {
4599 create_room(roomname,
4600 ( (is_mailbox != NULL) ? 5 : 3 ),
4601 "", 0, 1, 0, VIEW_BBS);
4603 /* If the caller specified this object as unique, delete all
4604 * other objects of this type that are currently in the room.
4607 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4608 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4611 /* Now write the data */
4612 CtdlSubmitMsg(msg, NULL, roomname, 0);
4613 CtdlFreeMessage(msg);
4621 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4622 config_msgnum = msgnum;
4626 char *CtdlGetSysConfig(char *sysconfname) {
4627 char hold_rm[ROOMNAMELEN];
4630 struct CtdlMessage *msg;
4633 strcpy(hold_rm, CC->room.QRname);
4634 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4635 getroom(&CC->room, hold_rm);
4640 /* We want the last (and probably only) config in this room */
4641 begin_critical_section(S_CONFIG);
4642 config_msgnum = (-1L);
4643 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4644 CtdlGetSysConfigBackend, NULL);
4645 msgnum = config_msgnum;
4646 end_critical_section(S_CONFIG);
4652 msg = CtdlFetchMessage(msgnum, 1);
4654 conf = strdup(msg->cm_fields['M']);
4655 CtdlFreeMessage(msg);
4662 getroom(&CC->room, hold_rm);
4664 if (conf != NULL) do {
4665 extract_token(buf, conf, 0, '\n', sizeof buf);
4666 strcpy(conf, &conf[strlen(buf)+1]);
4667 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4673 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4674 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4679 * Determine whether a given Internet address belongs to the current user
4681 int CtdlIsMe(char *addr, int addr_buf_len)
4683 struct recptypes *recp;
4686 recp = validate_recipients(addr, NULL, 0);
4687 if (recp == NULL) return(0);
4689 if (recp->num_local == 0) {
4690 free_recipients(recp);
4694 for (i=0; i<recp->num_local; ++i) {
4695 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4696 if (!strcasecmp(addr, CC->user.fullname)) {
4697 free_recipients(recp);
4702 free_recipients(recp);
4708 * Citadel protocol command to do the same
4710 void cmd_isme(char *argbuf) {
4713 if (CtdlAccessCheck(ac_logged_in)) return;
4714 extract_token(addr, argbuf, 0, '|', sizeof addr);
4716 if (CtdlIsMe(addr, sizeof addr)) {
4717 cprintf("%d %s\n", CIT_OK, addr);
4720 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4726 /*****************************************************************************/
4727 /* MODULE INITIALIZATION STUFF */
4728 /*****************************************************************************/
4730 CTDL_MODULE_INIT(msgbase)
4732 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4733 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4734 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4735 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4736 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4737 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4738 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4739 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4740 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4741 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4742 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4743 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4745 /* return our Subversion id for the Log */