2 * Implements the message store.
4 * Copyright (c) 1987-2012 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
21 #if TIME_WITH_SYS_TIME
22 # include <sys/time.h>
26 # include <sys/time.h>
39 #include <sys/types.h>
44 #include <libcitadel.h>
47 #include "serv_extensions.h"
51 #include "sysdep_decls.h"
52 #include "citserver.h"
59 #include "internet_addressing.h"
60 #include "euidindex.h"
61 #include "journaling.h"
62 #include "citadel_dirs.h"
63 #include "clientsocket.h"
66 #include "ctdl_module.h"
69 struct addresses_to_be_filed *atbf = NULL;
71 /* This temp file holds the queue of operations for AdjRefCount() */
72 static FILE *arcfp = NULL;
73 void AdjRefCountList(long *msgnum, long nmsg, int incr);
75 int MessageDebugEnabled = 0;
78 * These are the four-character field headers we use when outputting
79 * messages in Citadel format (as opposed to RFC822 format).
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,
85 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
86 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
87 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
88 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
89 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
119 void CtdlMsgSetCM_Fields(struct CtdlMessage *Msg, const char which, const char *buf, long length)
121 if (Msg->cm_fields[which] != NULL)
122 free (Msg->cm_fields[which]);
123 Msg->cm_fields[which] = malloc(length + 1);
124 memcpy(Msg->cm_fields[which], buf, length);
125 Msg->cm_fields[which][length] = '\0';
129 * This function is self explanatory.
130 * (What can I say, I'm in a weird mood today...)
132 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
136 for (i = 0; i < strlen(name); ++i) {
137 if (name[i] == '@') {
138 while (isspace(name[i - 1]) && i > 0) {
139 strcpy(&name[i - 1], &name[i]);
142 while (isspace(name[i + 1])) {
143 strcpy(&name[i + 1], &name[i + 2]);
151 * Aliasing for network mail.
152 * (Error messages have been commented out, because this is a server.)
154 int alias(char *name)
155 { /* process alias and routing info for mail */
156 struct CitContext *CCC = CC;
159 char aaa[SIZ], bbb[SIZ];
160 char *ignetcfg = NULL;
161 char *ignetmap = NULL;
167 char original_name[256];
168 safestrncpy(original_name, name, sizeof original_name);
171 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
172 stripallbut(name, '<', '>');
174 fp = fopen(file_mail_aliases, "r");
176 fp = fopen("/dev/null", "r");
183 while (fgets(aaa, sizeof aaa, fp) != NULL) {
184 while (isspace(name[0]))
185 strcpy(name, &name[1]);
186 aaa[strlen(aaa) - 1] = 0;
188 for (a = 0; a < strlen(aaa); ++a) {
190 strcpy(bbb, &aaa[a + 1]);
194 if (!strcasecmp(name, aaa))
199 /* Hit the Global Address Book */
200 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
204 if (strcasecmp(original_name, name)) {
205 MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
208 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
209 for (a=0; a<strlen(name); ++a) {
210 if (name[a] == '@') {
211 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
213 MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
218 /* determine local or remote type, see citadel.h */
219 at = haschar(name, '@');
220 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
221 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
222 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
224 /* figure out the delivery mode */
225 extract_token(node, name, 1, '@', sizeof node);
227 /* If there are one or more dots in the nodename, we assume that it
228 * is an FQDN and will attempt SMTP delivery to the Internet.
230 if (haschar(node, '.') > 0) {
231 return(MES_INTERNET);
234 /* Otherwise we look in the IGnet maps for a valid Citadel node.
235 * Try directly-connected nodes first...
237 ignetcfg = CtdlGetSysConfig(IGNETCFG);
238 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
239 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
240 extract_token(testnode, buf, 0, '|', sizeof testnode);
241 if (!strcasecmp(node, testnode)) {
249 * Then try nodes that are two or more hops away.
251 ignetmap = CtdlGetSysConfig(IGNETMAP);
252 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
253 extract_token(buf, ignetmap, i, '\n', sizeof buf);
254 extract_token(testnode, buf, 0, '|', sizeof testnode);
255 if (!strcasecmp(node, testnode)) {
262 /* If we get to this point it's an invalid node name */
268 * Back end for the MSGS command: output message number only.
270 void simple_listing(long msgnum, void *userdata)
272 cprintf("%ld\n", msgnum);
278 * Back end for the MSGS command: output header summary.
280 void headers_listing(long msgnum, void *userdata)
282 struct CtdlMessage *msg;
284 msg = CtdlFetchMessage(msgnum, 0);
286 cprintf("%ld|0|||||\n", msgnum);
290 cprintf("%ld|%s|%s|%s|%s|%s|\n",
292 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
293 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
294 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
295 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
296 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
298 CtdlFreeMessage(msg);
302 * Back end for the MSGS command: output EUID header.
304 void headers_euid(long msgnum, void *userdata)
306 struct CtdlMessage *msg;
308 msg = CtdlFetchMessage(msgnum, 0);
310 cprintf("%ld||\n", msgnum);
314 cprintf("%ld|%s|%s\n",
316 (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""),
317 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"));
318 CtdlFreeMessage(msg);
325 /* Determine if a given message matches the fields in a message template.
326 * Return 0 for a successful match.
328 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
331 /* If there aren't any fields in the template, all messages will
334 if (template == NULL) return(0);
336 /* Null messages are bogus. */
337 if (msg == NULL) return(1);
339 for (i='A'; i<='Z'; ++i) {
340 if (template->cm_fields[i] != NULL) {
341 if (msg->cm_fields[i] == NULL) {
342 /* Considered equal if temmplate is empty string */
343 if (IsEmptyStr(template->cm_fields[i])) continue;
346 if (strcasecmp(msg->cm_fields[i],
347 template->cm_fields[i])) return 1;
351 /* All compares succeeded: we have a match! */
358 * Retrieve the "seen" message list for the current room.
360 void CtdlGetSeen(char *buf, int which_set) {
361 struct CitContext *CCC = CC;
364 /* Learn about the user and room in question */
365 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
367 if (which_set == ctdlsetseen_seen)
368 safestrncpy(buf, vbuf.v_seen, SIZ);
369 if (which_set == ctdlsetseen_answered)
370 safestrncpy(buf, vbuf.v_answered, SIZ);
376 * Manipulate the "seen msgs" string (or other message set strings)
378 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
379 int target_setting, int which_set,
380 struct ctdluser *which_user, struct ctdlroom *which_room) {
381 struct CitContext *CCC = CC;
382 struct cdbdata *cdbfr;
387 long hi = (-1L); /// TODO: we just write here. y?
396 char *is_set; /* actually an array of booleans */
398 /* Don't bother doing *anything* if we were passed a list of zero messages */
399 if (num_target_msgnums < 1) {
403 /* If no room was specified, we go with the current room. */
405 which_room = &CCC->room;
408 /* If no user was specified, we go with the current user. */
410 which_user = &CCC->user;
413 MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
414 num_target_msgnums, target_msgnums[0],
415 (target_setting ? "SET" : "CLEAR"),
419 /* Learn about the user and room in question */
420 CtdlGetRelationship(&vbuf, which_user, which_room);
422 /* Load the message list */
423 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
425 msglist = (long *) cdbfr->ptr;
426 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
427 num_msgs = cdbfr->len / sizeof(long);
430 return; /* No messages at all? No further action. */
433 is_set = malloc(num_msgs * sizeof(char));
434 memset(is_set, 0, (num_msgs * sizeof(char)) );
436 /* Decide which message set we're manipulating */
438 case ctdlsetseen_seen:
439 vset = NewStrBufPlain(vbuf.v_seen, -1);
441 case ctdlsetseen_answered:
442 vset = NewStrBufPlain(vbuf.v_answered, -1);
449 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
450 MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
451 for (i=0; i<num_msgs; ++i) {
452 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
454 MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
455 for (k=0; k<num_target_msgnums; ++k) {
456 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
460 MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
462 /* Translate the existing sequence set into an array of booleans */
463 setstr = NewStrBuf();
467 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
469 StrBufExtract_token(lostr, setstr, 0, ':');
470 if (StrBufNum_tokens(setstr, ':') >= 2) {
471 StrBufExtract_token(histr, setstr, 1, ':');
475 StrBufAppendBuf(histr, lostr, 0);
478 if (!strcmp(ChrPtr(histr), "*")) {
485 for (i = 0; i < num_msgs; ++i) {
486 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
496 /* Now translate the array of booleans back into a sequence set */
502 for (i=0; i<num_msgs; ++i) {
506 for (k=0; k<num_target_msgnums; ++k) {
507 if (msglist[i] == target_msgnums[k]) {
508 is_seen = target_setting;
512 if ((was_seen == 0) && (is_seen == 1)) {
515 else if ((was_seen == 1) && (is_seen == 0)) {
518 if (StrLength(vset) > 0) {
519 StrBufAppendBufPlain(vset, HKEY(","), 0);
522 StrBufAppendPrintf(vset, "%ld", hi);
525 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
529 if ((is_seen) && (i == num_msgs - 1)) {
530 if (StrLength(vset) > 0) {
531 StrBufAppendBufPlain(vset, HKEY(","), 0);
533 if ((i==0) || (was_seen == 0)) {
534 StrBufAppendPrintf(vset, "%ld", msglist[i]);
537 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
545 * We will have to stuff this string back into a 4096 byte buffer, so if it's
546 * larger than that now, truncate it by removing tokens from the beginning.
547 * The limit of 100 iterations is there to prevent an infinite loop in case
548 * something unexpected happens.
550 int number_of_truncations = 0;
551 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
552 StrBufRemove_token(vset, 0, ',');
553 ++number_of_truncations;
557 * If we're truncating the sequence set of messages marked with the 'seen' flag,
558 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
559 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
561 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
563 first_tok = NewStrBuf();
564 StrBufExtract_token(first_tok, vset, 0, ',');
565 StrBufRemove_token(vset, 0, ',');
567 if (StrBufNum_tokens(first_tok, ':') > 1) {
568 StrBufRemove_token(first_tok, 0, ':');
572 new_set = NewStrBuf();
573 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
574 StrBufAppendBuf(new_set, first_tok, 0);
575 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
576 StrBufAppendBuf(new_set, vset, 0);
579 FreeStrBuf(&first_tok);
583 MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
585 /* Decide which message set we're manipulating */
587 case ctdlsetseen_seen:
588 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
590 case ctdlsetseen_answered:
591 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
597 CtdlSetRelationship(&vbuf, which_user, which_room);
603 * API function to perform an operation for each qualifying message in the
604 * current room. (Returns the number of messages processed.)
606 int CtdlForEachMessage(int mode, long ref, char *search_string,
608 struct CtdlMessage *compare,
609 ForEachMsgCallback CallBack,
612 struct CitContext *CCC = CC;
615 struct cdbdata *cdbfr;
616 long *msglist = NULL;
618 int num_processed = 0;
621 struct CtdlMessage *msg = NULL;
624 int printed_lastold = 0;
625 int num_search_msgs = 0;
626 long *search_msgs = NULL;
628 int need_to_free_re = 0;
631 if ((content_type) && (!IsEmptyStr(content_type))) {
632 regcomp(&re, content_type, 0);
636 /* Learn about the user and room in question */
637 if (server_shutting_down) {
638 if (need_to_free_re) regfree(&re);
641 CtdlGetUser(&CCC->user, CCC->curr_user);
643 if (server_shutting_down) {
644 if (need_to_free_re) regfree(&re);
647 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
649 if (server_shutting_down) {
650 if (need_to_free_re) regfree(&re);
654 /* Load the message list */
655 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
657 if (need_to_free_re) regfree(&re);
658 return 0; /* No messages at all? No further action. */
661 msglist = (long *) cdbfr->ptr;
662 num_msgs = cdbfr->len / sizeof(long);
664 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
665 cdb_free(cdbfr); /* we own this memory now */
668 * Now begin the traversal.
670 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
672 /* If the caller is looking for a specific MIME type, filter
673 * out all messages which are not of the type requested.
675 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
677 /* This call to GetMetaData() sits inside this loop
678 * so that we only do the extra database read per msg
679 * if we need to. Doing the extra read all the time
680 * really kills the server. If we ever need to use
681 * metadata for another search criterion, we need to
682 * move the read somewhere else -- but still be smart
683 * enough to only do the read if the caller has
684 * specified something that will need it.
686 if (server_shutting_down) {
687 if (need_to_free_re) regfree(&re);
691 GetMetaData(&smi, msglist[a]);
693 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
694 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
700 num_msgs = sort_msglist(msglist, num_msgs);
702 /* If a template was supplied, filter out the messages which
703 * don't match. (This could induce some delays!)
706 if (compare != NULL) {
707 for (a = 0; a < num_msgs; ++a) {
708 if (server_shutting_down) {
709 if (need_to_free_re) regfree(&re);
713 msg = CtdlFetchMessage(msglist[a], 1);
715 if (CtdlMsgCmp(msg, compare)) {
718 CtdlFreeMessage(msg);
724 /* If a search string was specified, get a message list from
725 * the full text index and remove messages which aren't on both
729 * Since the lists are sorted and strictly ascending, and the
730 * output list is guaranteed to be shorter than or equal to the
731 * input list, we overwrite the bottom of the input list. This
732 * eliminates the need to memmove big chunks of the list over and
735 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
737 /* Call search module via hook mechanism.
738 * NULL means use any search function available.
739 * otherwise replace with a char * to name of search routine
741 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
743 if (num_search_msgs > 0) {
747 orig_num_msgs = num_msgs;
749 for (i=0; i<orig_num_msgs; ++i) {
750 for (j=0; j<num_search_msgs; ++j) {
751 if (msglist[i] == search_msgs[j]) {
752 msglist[num_msgs++] = msglist[i];
758 num_msgs = 0; /* No messages qualify */
760 if (search_msgs != NULL) free(search_msgs);
762 /* Now that we've purged messages which don't contain the search
763 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
770 * Now iterate through the message list, according to the
771 * criteria supplied by the caller.
774 for (a = 0; a < num_msgs; ++a) {
775 if (server_shutting_down) {
776 if (need_to_free_re) regfree(&re);
778 return num_processed;
780 thismsg = msglist[a];
781 if (mode == MSGS_ALL) {
785 is_seen = is_msg_in_sequence_set(
786 vbuf.v_seen, thismsg);
787 if (is_seen) lastold = thismsg;
793 || ((mode == MSGS_OLD) && (is_seen))
794 || ((mode == MSGS_NEW) && (!is_seen))
795 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
796 || ((mode == MSGS_FIRST) && (a < ref))
797 || ((mode == MSGS_GT) && (thismsg > ref))
798 || ((mode == MSGS_LT) && (thismsg < ref))
799 || ((mode == MSGS_EQ) && (thismsg == ref))
802 if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
804 CallBack(lastold, userdata);
808 if (CallBack) CallBack(thismsg, userdata);
812 if (need_to_free_re) regfree(&re);
815 * We cache the most recent msglist in order to do security checks later
817 if (CCC->client_socket > 0) {
818 if (CCC->cached_msglist != NULL) {
819 free(CCC->cached_msglist);
821 CCC->cached_msglist = msglist;
822 CCC->cached_num_msgs = num_msgs;
828 return num_processed;
834 * cmd_msgs() - get list of message #'s in this room
835 * implements the MSGS server command using CtdlForEachMessage()
837 void cmd_msgs(char *cmdbuf)
846 int with_template = 0;
847 struct CtdlMessage *template = NULL;
848 char search_string[1024];
849 ForEachMsgCallback CallBack;
851 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
853 extract_token(which, cmdbuf, 0, '|', sizeof which);
854 cm_ref = extract_int(cmdbuf, 1);
855 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
856 with_template = extract_int(cmdbuf, 2);
857 switch (extract_int(cmdbuf, 3))
861 CallBack = simple_listing;
864 CallBack = headers_listing;
867 CallBack = headers_euid;
872 if (!strncasecmp(which, "OLD", 3))
874 else if (!strncasecmp(which, "NEW", 3))
876 else if (!strncasecmp(which, "FIRST", 5))
878 else if (!strncasecmp(which, "LAST", 4))
880 else if (!strncasecmp(which, "GT", 2))
882 else if (!strncasecmp(which, "LT", 2))
884 else if (!strncasecmp(which, "SEARCH", 6))
889 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
890 cprintf("%d Full text index is not enabled on this server.\n",
891 ERROR + CMD_NOT_SUPPORTED);
897 cprintf("%d Send template then receive message list\n",
899 template = (struct CtdlMessage *)
900 malloc(sizeof(struct CtdlMessage));
901 memset(template, 0, sizeof(struct CtdlMessage));
902 template->cm_magic = CTDLMESSAGE_MAGIC;
903 template->cm_anon_type = MES_NORMAL;
905 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
906 extract_token(tfield, buf, 0, '|', sizeof tfield);
907 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
908 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
909 if (!strcasecmp(tfield, msgkeys[i])) {
910 template->cm_fields[i] =
918 cprintf("%d \n", LISTING_FOLLOWS);
921 CtdlForEachMessage(mode,
922 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
923 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
928 if (template != NULL) CtdlFreeMessage(template);
936 * help_subst() - support routine for help file viewer
938 void help_subst(char *strbuf, char *source, char *dest)
943 while (p = pattern2(strbuf, source), (p >= 0)) {
944 strcpy(workbuf, &strbuf[p + strlen(source)]);
945 strcpy(&strbuf[p], dest);
946 strcat(strbuf, workbuf);
951 void do_help_subst(char *buffer)
955 help_subst(buffer, "^nodename", config.c_nodename);
956 help_subst(buffer, "^humannode", config.c_humannode);
957 help_subst(buffer, "^fqdn", config.c_fqdn);
958 help_subst(buffer, "^username", CC->user.fullname);
959 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
960 help_subst(buffer, "^usernum", buf2);
961 help_subst(buffer, "^sysadm", config.c_sysadm);
962 help_subst(buffer, "^variantname", CITADEL);
963 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
964 help_subst(buffer, "^maxsessions", buf2);
965 help_subst(buffer, "^bbsdir", ctdl_message_dir);
971 * memfmout() - Citadel text formatter and paginator.
972 * Although the original purpose of this routine was to format
973 * text to the reader's screen width, all we're really using it
974 * for here is to format text out to 80 columns before sending it
975 * to the client. The client software may reformat it again.
978 char *mptr, /* where are we going to get our text from? */
979 const char *nl /* string to terminate lines with */
981 struct CitContext *CCC = CC;
983 unsigned char ch = 0;
990 while (ch=*(mptr++), ch != 0) {
993 if (client_write(outbuf, len) == -1)
995 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
999 if (client_write(nl, nllen) == -1)
1001 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1006 else if (ch == '\r') {
1007 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
1009 else if (isspace(ch)) {
1010 if (column > 72) { /* Beyond 72 columns, break on the next space */
1011 if (client_write(outbuf, len) == -1)
1013 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1017 if (client_write(nl, nllen) == -1)
1019 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1032 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
1033 if (client_write(outbuf, len) == -1)
1035 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1039 if (client_write(nl, nllen) == -1)
1041 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1049 if (client_write(outbuf, len) == -1)
1051 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1055 client_write(nl, nllen);
1063 * Callback function for mime parser that simply lists the part
1065 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1066 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1067 char *cbid, void *cbuserdata)
1071 ma = (struct ma_info *)cbuserdata;
1072 if (ma->is_ma == 0) {
1073 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1086 * Callback function for multipart prefix
1088 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1089 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1090 char *cbid, void *cbuserdata)
1094 ma = (struct ma_info *)cbuserdata;
1095 if (!strcasecmp(cbtype, "multipart/alternative")) {
1099 if (ma->is_ma == 0) {
1100 cprintf("pref=%s|%s\n", partnum, cbtype);
1105 * Callback function for multipart sufffix
1107 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1108 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1109 char *cbid, void *cbuserdata)
1113 ma = (struct ma_info *)cbuserdata;
1114 if (ma->is_ma == 0) {
1115 cprintf("suff=%s|%s\n", partnum, cbtype);
1117 if (!strcasecmp(cbtype, "multipart/alternative")) {
1124 * Callback function for mime parser that opens a section for downloading
1126 void mime_download(char *name, char *filename, char *partnum, char *disp,
1127 void *content, char *cbtype, char *cbcharset, size_t length,
1128 char *encoding, char *cbid, void *cbuserdata)
1131 CitContext *CCC = MyContext();
1133 /* Silently go away if there's already a download open. */
1134 if (CCC->download_fp != NULL)
1138 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1139 || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1141 CCC->download_fp = tmpfile();
1142 if (CCC->download_fp == NULL) {
1143 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1145 cprintf("%d cannot open temporary file: %s\n",
1146 ERROR + INTERNAL_ERROR, strerror(errno));
1150 rv = fwrite(content, length, 1, CC->download_fp);
1152 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1154 cprintf("%d unable to write tempfile.\n",
1156 fclose(CCC->download_fp);
1157 CCC->download_fp = NULL;
1160 fflush(CCC->download_fp);
1161 rewind(CCC->download_fp);
1163 OpenCmdResult(filename, cbtype);
1170 * Callback function for mime parser that outputs a section all at once.
1171 * We can specify the desired section by part number *or* content-id.
1173 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1174 void *content, char *cbtype, char *cbcharset, size_t length,
1175 char *encoding, char *cbid, void *cbuserdata)
1177 int *found_it = (int *)cbuserdata;
1180 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1181 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1184 cprintf("%d %d|-1|%s|%s|%s\n",
1191 client_write(content, length);
1197 * Load a message from disk into memory.
1198 * This is used by CtdlOutputMsg() and other fetch functions.
1200 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1201 * using the CtdlMessageFree() function.
1203 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1205 struct CitContext *CCC = CC;
1206 struct cdbdata *dmsgtext;
1207 struct CtdlMessage *ret = NULL;
1211 cit_uint8_t field_header;
1213 MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1214 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1215 if (dmsgtext == NULL) {
1218 mptr = dmsgtext->ptr;
1219 upper_bound = mptr + dmsgtext->len;
1221 /* Parse the three bytes that begin EVERY message on disk.
1222 * The first is always 0xFF, the on-disk magic number.
1223 * The second is the anonymous/public type byte.
1224 * The third is the format type byte (vari, fixed, or MIME).
1228 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1232 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1233 memset(ret, 0, sizeof(struct CtdlMessage));
1235 ret->cm_magic = CTDLMESSAGE_MAGIC;
1236 ret->cm_anon_type = *mptr++; /* Anon type byte */
1237 ret->cm_format_type = *mptr++; /* Format type byte */
1240 * The rest is zero or more arbitrary fields. Load them in.
1241 * We're done when we encounter either a zero-length field or
1242 * have just processed the 'M' (message text) field.
1245 if (mptr >= upper_bound) {
1248 field_header = *mptr++;
1249 ret->cm_fields[field_header] = strdup(mptr);
1251 while (*mptr++ != 0); /* advance to next field */
1253 } while ((mptr < upper_bound) && (field_header != 'M'));
1257 /* Always make sure there's something in the msg text field. If
1258 * it's NULL, the message text is most likely stored separately,
1259 * so go ahead and fetch that. Failing that, just set a dummy
1260 * body so other code doesn't barf.
1262 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1263 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1264 if (dmsgtext != NULL) {
1265 ret->cm_fields['M'] = dmsgtext->ptr;
1266 dmsgtext->ptr = NULL;
1270 if (ret->cm_fields['M'] == NULL) {
1271 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1274 /* Perform "before read" hooks (aborting if any return nonzero) */
1275 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1276 CtdlFreeMessage(ret);
1285 * Returns 1 if the supplied pointer points to a valid Citadel message.
1286 * If the pointer is NULL or the magic number check fails, returns 0.
1288 int is_valid_message(struct CtdlMessage *msg) {
1291 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1292 struct CitContext *CCC = CC;
1293 MSGM_syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1299 void CtdlFreeMessageContents(struct CtdlMessage *msg)
1303 for (i = 0; i < 256; ++i)
1304 if (msg->cm_fields[i] != NULL) {
1305 free(msg->cm_fields[i]);
1308 msg->cm_magic = 0; /* just in case */
1311 * 'Destructor' for struct CtdlMessage
1313 void CtdlFreeMessage(struct CtdlMessage *msg)
1315 if (is_valid_message(msg) == 0)
1317 if (msg != NULL) free (msg);
1320 CtdlFreeMessageContents(msg);
1324 int DupCMField(int i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
1327 len = strlen(OrgMsg->cm_fields[i]);
1328 NewMsg->cm_fields[i] = malloc(len + 1);
1329 if (NewMsg->cm_fields[i] == NULL)
1331 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
1332 NewMsg->cm_fields[i][len] = '\0';
1336 struct CtdlMessage * CtdlDuplicateMessage(struct CtdlMessage *OrgMsg)
1339 struct CtdlMessage *NewMsg;
1341 if (is_valid_message(OrgMsg) == 0)
1343 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
1347 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
1349 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
1351 for (i = 0; i < 256; ++i)
1353 if (OrgMsg->cm_fields[i] != NULL)
1355 if (!DupCMField(i, OrgMsg, NewMsg))
1357 CtdlFreeMessage(NewMsg);
1369 * Pre callback function for multipart/alternative
1371 * NOTE: this differs from the standard behavior for a reason. Normally when
1372 * displaying multipart/alternative you want to show the _last_ usable
1373 * format in the message. Here we show the _first_ one, because it's
1374 * usually text/plain. Since this set of functions is designed for text
1375 * output to non-MIME-aware clients, this is the desired behavior.
1378 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1379 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1380 char *cbid, void *cbuserdata)
1382 struct CitContext *CCC = CC;
1385 ma = (struct ma_info *)cbuserdata;
1386 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1387 if (!strcasecmp(cbtype, "multipart/alternative")) {
1391 if (!strcasecmp(cbtype, "message/rfc822")) {
1397 * Post callback function for multipart/alternative
1399 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1400 void *content, char *cbtype, char *cbcharset, size_t length,
1401 char *encoding, char *cbid, void *cbuserdata)
1403 struct CitContext *CCC = CC;
1406 ma = (struct ma_info *)cbuserdata;
1407 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1408 if (!strcasecmp(cbtype, "multipart/alternative")) {
1412 if (!strcasecmp(cbtype, "message/rfc822")) {
1418 * Inline callback function for mime parser that wants to display text
1420 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1421 void *content, char *cbtype, char *cbcharset, size_t length,
1422 char *encoding, char *cbid, void *cbuserdata)
1424 struct CitContext *CCC = CC;
1430 ma = (struct ma_info *)cbuserdata;
1432 MSG_syslog(LOG_DEBUG,
1433 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1434 partnum, filename, cbtype, (long)length);
1437 * If we're in the middle of a multipart/alternative scope and
1438 * we've already printed another section, skip this one.
1440 if ( (ma->is_ma) && (ma->did_print) ) {
1441 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1446 if ( (!strcasecmp(cbtype, "text/plain"))
1447 || (IsEmptyStr(cbtype)) ) {
1450 client_write(wptr, length);
1451 if (wptr[length-1] != '\n') {
1458 if (!strcasecmp(cbtype, "text/html")) {
1459 ptr = html_to_ascii(content, length, 80, 0);
1461 client_write(ptr, wlen);
1462 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1469 if (ma->use_fo_hooks) {
1470 if (PerformFixedOutputHooks(cbtype, content, length)) {
1471 /* above function returns nonzero if it handled the part */
1476 if (strncasecmp(cbtype, "multipart/", 10)) {
1477 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1478 partnum, filename, cbtype, (long)length);
1484 * The client is elegant and sophisticated and wants to be choosy about
1485 * MIME content types, so figure out which multipart/alternative part
1486 * we're going to send.
1488 * We use a system of weights. When we find a part that matches one of the
1489 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1490 * and then set ma->chosen_pref to that MIME type's position in our preference
1491 * list. If we then hit another match, we only replace the first match if
1492 * the preference value is lower.
1494 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1495 void *content, char *cbtype, char *cbcharset, size_t length,
1496 char *encoding, char *cbid, void *cbuserdata)
1498 struct CitContext *CCC = CC;
1503 ma = (struct ma_info *)cbuserdata;
1505 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1506 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1507 // I don't know if there are any side effects! Please TEST TEST TEST
1508 //if (ma->is_ma > 0) {
1510 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1511 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1512 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1513 if (i < ma->chosen_pref) {
1514 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1515 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1516 ma->chosen_pref = i;
1523 * Now that we've chosen our preferred part, output it.
1525 void output_preferred(char *name,
1537 struct CitContext *CCC = CC;
1540 int add_newline = 0;
1543 char *decoded = NULL;
1544 size_t bytes_decoded;
1547 ma = (struct ma_info *)cbuserdata;
1549 /* This is not the MIME part you're looking for... */
1550 if (strcasecmp(partnum, ma->chosen_part)) return;
1552 /* If the content-type of this part is in our preferred formats
1553 * list, we can simply output it verbatim.
1555 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1556 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1557 if (!strcasecmp(buf, cbtype)) {
1558 /* Yeah! Go! W00t!! */
1559 if (ma->dont_decode == 0)
1560 rc = mime_decode_now (content,
1566 break; /* Give us the chance, maybe theres another one. */
1568 if (rc == 0) text_content = (char *)content;
1570 text_content = decoded;
1571 length = bytes_decoded;
1574 if (text_content[length-1] != '\n') {
1577 cprintf("Content-type: %s", cbtype);
1578 if (!IsEmptyStr(cbcharset)) {
1579 cprintf("; charset=%s", cbcharset);
1581 cprintf("\nContent-length: %d\n",
1582 (int)(length + add_newline) );
1583 if (!IsEmptyStr(encoding)) {
1584 cprintf("Content-transfer-encoding: %s\n", encoding);
1587 cprintf("Content-transfer-encoding: 7bit\n");
1589 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1591 if (client_write(text_content, length) == -1)
1593 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1596 if (add_newline) cprintf("\n");
1597 if (decoded != NULL) free(decoded);
1602 /* No translations required or possible: output as text/plain */
1603 cprintf("Content-type: text/plain\n\n");
1605 if (ma->dont_decode == 0)
1606 rc = mime_decode_now (content,
1612 return; /* Give us the chance, maybe theres another one. */
1614 if (rc == 0) text_content = (char *)content;
1616 text_content = decoded;
1617 length = bytes_decoded;
1620 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1621 length, encoding, cbid, cbuserdata);
1622 if (decoded != NULL) free(decoded);
1627 char desired_section[64];
1634 * Callback function for
1636 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1637 void *content, char *cbtype, char *cbcharset, size_t length,
1638 char *encoding, char *cbid, void *cbuserdata)
1640 struct encapmsg *encap;
1642 encap = (struct encapmsg *)cbuserdata;
1644 /* Only proceed if this is the desired section... */
1645 if (!strcasecmp(encap->desired_section, partnum)) {
1646 encap->msglen = length;
1647 encap->msg = malloc(length + 2);
1648 memcpy(encap->msg, content, length);
1655 * Determine whether the specified message exists in the cached_msglist
1656 * (This is a security check)
1658 int check_cached_msglist(long msgnum) {
1659 struct CitContext *CCC = CC;
1661 /* cases in which we skip the check */
1662 if (!CCC) return om_ok; /* not a session */
1663 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1664 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1665 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1668 /* Do a binary search within the cached_msglist for the requested msgnum */
1670 int max = (CC->cached_num_msgs - 1);
1672 while (max >= min) {
1673 int middle = min + (max-min) / 2 ;
1674 if (msgnum == CCC->cached_msglist[middle]) {
1677 if (msgnum > CC->cached_msglist[middle]) {
1685 return om_access_denied;
1690 * Determine whether the currently logged in session has permission to read
1691 * messages in the current room.
1693 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1694 if ( (!(CC->logged_in))
1695 && (!(CC->internal_pgm))
1696 && (!config.c_guest_logins)
1698 return(om_not_logged_in);
1705 * Get a message off disk. (returns om_* values found in msgbase.h)
1708 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1709 int mode, /* how would you like that message? */
1710 int headers_only, /* eschew the message body? */
1711 int do_proto, /* do Citadel protocol responses? */
1712 int crlf, /* Use CRLF newlines instead of LF? */
1713 char *section, /* NULL or a message/rfc822 section */
1714 int flags, /* various flags; see msgbase.h */
1718 struct CitContext *CCC = CC;
1719 struct CtdlMessage *TheMessage = NULL;
1720 int retcode = CIT_OK;
1721 struct encapmsg encap;
1724 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1726 (section ? section : "<>")
1729 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1732 if (r == om_not_logged_in) {
1733 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1736 cprintf("%d An unknown error has occurred.\n", ERROR);
1743 * Check to make sure the message is actually IN this room
1745 r = check_cached_msglist(msg_num);
1746 if (r == om_access_denied) {
1747 /* Not in the cache? We get ONE shot to check it again. */
1748 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1749 r = check_cached_msglist(msg_num);
1752 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1753 msg_num, CCC->room.QRname
1756 if (r == om_access_denied) {
1757 cprintf("%d message %ld was not found in this room\n",
1758 ERROR + HIGHER_ACCESS_REQUIRED,
1767 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1768 * request that we don't even bother loading the body into memory.
1770 if (headers_only == HEADERS_FAST) {
1771 TheMessage = CtdlFetchMessage(msg_num, 0);
1774 TheMessage = CtdlFetchMessage(msg_num, 1);
1777 if (TheMessage == NULL) {
1778 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1779 ERROR + MESSAGE_NOT_FOUND, msg_num);
1780 return(om_no_such_msg);
1783 /* Here is the weird form of this command, to process only an
1784 * encapsulated message/rfc822 section.
1786 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1787 memset(&encap, 0, sizeof encap);
1788 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1789 mime_parser(TheMessage->cm_fields['M'],
1791 *extract_encapsulated_message,
1792 NULL, NULL, (void *)&encap, 0
1795 if ((Author != NULL) && (*Author == NULL))
1797 *Author = TheMessage->cm_fields['A'];
1798 TheMessage->cm_fields['A'] = NULL;
1800 if ((Address != NULL) && (*Address == NULL))
1802 *Address = TheMessage->cm_fields['F'];
1803 TheMessage->cm_fields['F'] = NULL;
1805 CtdlFreeMessage(TheMessage);
1809 encap.msg[encap.msglen] = 0;
1810 TheMessage = convert_internet_message(encap.msg);
1811 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1813 /* Now we let it fall through to the bottom of this
1814 * function, because TheMessage now contains the
1815 * encapsulated message instead of the top-level
1816 * message. Isn't that neat?
1822 cprintf("%d msg %ld has no part %s\n",
1823 ERROR + MESSAGE_NOT_FOUND,
1827 retcode = om_no_such_msg;
1832 /* Ok, output the message now */
1833 if (retcode == CIT_OK)
1834 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1835 if ((Author != NULL) && (*Author == NULL))
1837 *Author = TheMessage->cm_fields['A'];
1838 TheMessage->cm_fields['A'] = NULL;
1840 if ((Address != NULL) && (*Address == NULL))
1842 *Address = TheMessage->cm_fields['F'];
1843 TheMessage->cm_fields['F'] = NULL;
1846 CtdlFreeMessage(TheMessage);
1852 char *qp_encode_email_addrs(char *source)
1854 struct CitContext *CCC = CC;
1855 char *user, *node, *name;
1856 const char headerStr[] = "=?UTF-8?Q?";
1860 int need_to_encode = 0;
1866 long nAddrPtrMax = 50;
1871 if (source == NULL) return source;
1872 if (IsEmptyStr(source)) return source;
1873 if (MessageDebugEnabled != 0) cit_backtrace();
1874 MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1876 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1877 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1878 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1881 while (!IsEmptyStr (&source[i])) {
1882 if (nColons >= nAddrPtrMax){
1885 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1886 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1887 free (AddrPtr), AddrPtr = ptr;
1889 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1890 memset(&ptr[nAddrPtrMax], 0,
1891 sizeof (long) * nAddrPtrMax);
1893 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1894 free (AddrUtf8), AddrUtf8 = ptr;
1897 if (((unsigned char) source[i] < 32) ||
1898 ((unsigned char) source[i] > 126)) {
1900 AddrUtf8[nColons] = 1;
1902 if (source[i] == '"')
1903 InQuotes = !InQuotes;
1904 if (!InQuotes && source[i] == ',') {
1905 AddrPtr[nColons] = i;
1910 if (need_to_encode == 0) {
1917 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1918 Encoded = (char*) malloc (EncodedMaxLen);
1920 for (i = 0; i < nColons; i++)
1921 source[AddrPtr[i]++] = '\0';
1922 /* TODO: if libidn, this might get larger*/
1923 user = malloc(SourceLen + 1);
1924 node = malloc(SourceLen + 1);
1925 name = malloc(SourceLen + 1);
1929 for (i = 0; i < nColons && nPtr != NULL; i++) {
1930 nmax = EncodedMaxLen - (nPtr - Encoded);
1932 process_rfc822_addr(&source[AddrPtr[i]],
1936 /* TODO: libIDN here ! */
1937 if (IsEmptyStr(name)) {
1938 n = snprintf(nPtr, nmax,
1939 (i==0)?"%s@%s" : ",%s@%s",
1943 EncodedName = rfc2047encode(name, strlen(name));
1944 n = snprintf(nPtr, nmax,
1945 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1946 EncodedName, user, node);
1951 n = snprintf(nPtr, nmax,
1952 (i==0)?"%s" : ",%s",
1953 &source[AddrPtr[i]]);
1959 ptr = (char*) malloc(EncodedMaxLen * 2);
1960 memcpy(ptr, Encoded, EncodedMaxLen);
1961 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1962 free(Encoded), Encoded = ptr;
1964 i--; /* do it once more with properly lengthened buffer */
1967 for (i = 0; i < nColons; i++)
1968 source[--AddrPtr[i]] = ',';
1979 /* If the last item in a list of recipients was truncated to a partial address,
1980 * remove it completely in order to avoid choking libSieve
1982 void sanitize_truncated_recipient(char *str)
1985 if (num_tokens(str, ',') < 2) return;
1987 int len = strlen(str);
1988 if (len < 900) return;
1989 if (len > 998) str[998] = 0;
1991 char *cptr = strrchr(str, ',');
1994 char *lptr = strchr(cptr, '<');
1995 char *rptr = strchr(cptr, '>');
1997 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
2003 void OutputCtdlMsgHeaders(
2004 struct CtdlMessage *TheMessage,
2005 int do_proto) /* do Citadel protocol responses? */
2011 char display_name[256];
2013 /* begin header processing loop for Citadel message format */
2014 safestrncpy(display_name, "<unknown>", sizeof display_name);
2015 if (TheMessage->cm_fields['A']) {
2016 strcpy(buf, TheMessage->cm_fields['A']);
2017 if (TheMessage->cm_anon_type == MES_ANONONLY) {
2018 safestrncpy(display_name, "****", sizeof display_name);
2020 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
2021 safestrncpy(display_name, "anonymous", sizeof display_name);
2024 safestrncpy(display_name, buf, sizeof display_name);
2026 if ((is_room_aide())
2027 && ((TheMessage->cm_anon_type == MES_ANONONLY)
2028 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
2029 size_t tmp = strlen(display_name);
2030 snprintf(&display_name[tmp],
2031 sizeof display_name - tmp,
2036 /* Don't show Internet address for users on the
2037 * local Citadel network.
2040 if (TheMessage->cm_fields['N'] != NULL)
2041 if (!IsEmptyStr(TheMessage->cm_fields['N']))
2042 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
2046 /* Now spew the header fields in the order we like them. */
2047 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
2048 for (i=0; i<n; ++i) {
2049 k = (int) allkeys[i];
2051 if ( (TheMessage->cm_fields[k] != NULL)
2052 && (msgkeys[k] != NULL) ) {
2053 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
2054 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
2057 if (do_proto) cprintf("%s=%s\n",
2061 else if ((k == 'F') && (suppress_f)) {
2064 /* Masquerade display name if needed */
2066 if (do_proto) cprintf("%s=%s\n",
2068 TheMessage->cm_fields[k]
2077 void OutputRFC822MsgHeaders(
2078 struct CtdlMessage *TheMessage,
2079 int flags, /* should the bessage be exported clean */
2081 char *mid, long sizeof_mid,
2082 char *suser, long sizeof_suser,
2083 char *luser, long sizeof_luser,
2084 char *fuser, long sizeof_fuser,
2085 char *snode, long sizeof_snode)
2087 char datestamp[100];
2088 int subject_found = 0;
2095 for (i = 0; i < 256; ++i) {
2096 if (TheMessage->cm_fields[i]) {
2097 mptr = mpptr = TheMessage->cm_fields[i];
2100 safestrncpy(luser, mptr, sizeof_luser);
2101 safestrncpy(suser, mptr, sizeof_suser);
2103 else if (i == 'Y') {
2104 if ((flags & QP_EADDR) != 0) {
2105 mptr = qp_encode_email_addrs(mptr);
2107 sanitize_truncated_recipient(mptr);
2108 cprintf("CC: %s%s", mptr, nl);
2110 else if (i == 'P') {
2111 cprintf("Return-Path: %s%s", mptr, nl);
2113 else if (i == 'L') {
2114 cprintf("List-ID: %s%s", mptr, nl);
2116 else if (i == 'V') {
2117 if ((flags & QP_EADDR) != 0)
2118 mptr = qp_encode_email_addrs(mptr);
2120 while ((*hptr != '\0') && isspace(*hptr))
2122 if (!IsEmptyStr(hptr))
2123 cprintf("Envelope-To: %s%s", hptr, nl);
2125 else if (i == 'U') {
2126 cprintf("Subject: %s%s", mptr, nl);
2130 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2132 safestrncpy(fuser, mptr, sizeof_fuser);
2133 /* else if (i == 'O')
2134 cprintf("X-Citadel-Room: %s%s",
2137 safestrncpy(snode, mptr, sizeof_snode);
2140 if (haschar(mptr, '@') == 0)
2142 sanitize_truncated_recipient(mptr);
2143 cprintf("To: %s@%s", mptr, config.c_fqdn);
2148 if ((flags & QP_EADDR) != 0) {
2149 mptr = qp_encode_email_addrs(mptr);
2151 sanitize_truncated_recipient(mptr);
2152 cprintf("To: %s", mptr);
2156 else if (i == 'T') {
2157 datestring(datestamp, sizeof datestamp,
2158 atol(mptr), DATESTRING_RFC822);
2159 cprintf("Date: %s%s", datestamp, nl);
2161 else if (i == 'W') {
2162 cprintf("References: ");
2163 k = num_tokens(mptr, '|');
2164 for (j=0; j<k; ++j) {
2165 extract_token(buf, mptr, j, '|', sizeof buf);
2166 cprintf("<%s>", buf);
2175 else if (i == 'K') {
2177 while ((*hptr != '\0') && isspace(*hptr))
2179 if (!IsEmptyStr(hptr))
2180 cprintf("Reply-To: %s%s", mptr, nl);
2186 if (subject_found == 0) {
2187 cprintf("Subject: (no subject)%s", nl);
2192 void Dump_RFC822HeadersBody(
2193 struct CtdlMessage *TheMessage,
2194 int headers_only, /* eschew the message body? */
2195 int flags, /* should the bessage be exported clean? */
2199 cit_uint8_t prev_ch;
2201 const char *StartOfText = StrBufNOTNULL;
2204 int nllen = strlen(nl);
2207 mptr = TheMessage->cm_fields['M'];
2211 while (*mptr != '\0') {
2212 if (*mptr == '\r') {
2219 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2221 eoh = *(mptr+1) == '\n';
2225 StartOfText = strchr(StartOfText, '\n');
2226 StartOfText = strchr(StartOfText, '\n');
2229 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2230 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2231 ((headers_only != HEADERS_NONE) &&
2232 (headers_only != HEADERS_ONLY))
2234 if (*mptr == '\n') {
2235 memcpy(&outbuf[outlen], nl, nllen);
2237 outbuf[outlen] = '\0';
2240 outbuf[outlen++] = *mptr;
2244 if (flags & ESC_DOT)
2246 if ((prev_ch == '\n') &&
2248 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2250 outbuf[outlen++] = '.';
2255 if (outlen > 1000) {
2256 if (client_write(outbuf, outlen) == -1)
2258 struct CitContext *CCC = CC;
2259 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2266 client_write(outbuf, outlen);
2272 /* If the format type on disk is 1 (fixed-format), then we want
2273 * everything to be output completely literally ... regardless of
2274 * what message transfer format is in use.
2276 void DumpFormatFixed(
2277 struct CtdlMessage *TheMessage,
2278 int mode, /* how would you like that message? */
2285 int nllen = strlen (nl);
2288 mptr = TheMessage->cm_fields['M'];
2290 if (mode == MT_MIME) {
2291 cprintf("Content-type: text/plain\n\n");
2295 while (ch = *mptr++, ch > 0) {
2299 if ((buflen > 250) && (!xlline)){
2303 while ((buflen > 0) &&
2304 (!isspace(buf[buflen])))
2310 mptr -= tbuflen - buflen;
2315 /* if we reach the outer bounds of our buffer,
2316 abort without respect what whe purge. */
2319 (buflen > SIZ - nllen - 2)))
2323 memcpy (&buf[buflen], nl, nllen);
2327 if (client_write(buf, buflen) == -1)
2329 struct CitContext *CCC = CC;
2330 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2342 if (!IsEmptyStr(buf))
2343 cprintf("%s%s", buf, nl);
2347 * Get a message off disk. (returns om_* values found in msgbase.h)
2349 int CtdlOutputPreLoadedMsg(
2350 struct CtdlMessage *TheMessage,
2351 int mode, /* how would you like that message? */
2352 int headers_only, /* eschew the message body? */
2353 int do_proto, /* do Citadel protocol responses? */
2354 int crlf, /* Use CRLF newlines instead of LF? */
2355 int flags /* should the bessage be exported clean? */
2357 struct CitContext *CCC = CC;
2360 const char *nl; /* newline string */
2363 /* Buffers needed for RFC822 translation. These are all filled
2364 * using functions that are bounds-checked, and therefore we can
2365 * make them substantially smaller than SIZ.
2373 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2374 ((TheMessage == NULL) ? "NULL" : "not null"),
2375 mode, headers_only, do_proto, crlf);
2377 strcpy(mid, "unknown");
2378 nl = (crlf ? "\r\n" : "\n");
2380 if (!is_valid_message(TheMessage)) {
2381 MSGM_syslog(LOG_ERR,
2382 "ERROR: invalid preloaded message for output\n");
2384 return(om_no_such_msg);
2387 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2388 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2390 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2391 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2394 /* Are we downloading a MIME component? */
2395 if (mode == MT_DOWNLOAD) {
2396 if (TheMessage->cm_format_type != FMT_RFC822) {
2398 cprintf("%d This is not a MIME message.\n",
2399 ERROR + ILLEGAL_VALUE);
2400 } else if (CCC->download_fp != NULL) {
2401 if (do_proto) cprintf(
2402 "%d You already have a download open.\n",
2403 ERROR + RESOURCE_BUSY);
2405 /* Parse the message text component */
2406 mptr = TheMessage->cm_fields['M'];
2407 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2408 /* If there's no file open by this time, the requested
2409 * section wasn't found, so print an error
2411 if (CCC->download_fp == NULL) {
2412 if (do_proto) cprintf(
2413 "%d Section %s not found.\n",
2414 ERROR + FILE_NOT_FOUND,
2415 CCC->download_desired_section);
2418 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2421 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2422 * in a single server operation instead of opening a download file.
2424 if (mode == MT_SPEW_SECTION) {
2425 if (TheMessage->cm_format_type != FMT_RFC822) {
2427 cprintf("%d This is not a MIME message.\n",
2428 ERROR + ILLEGAL_VALUE);
2430 /* Parse the message text component */
2433 mptr = TheMessage->cm_fields['M'];
2434 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2435 /* If section wasn't found, print an error
2438 if (do_proto) cprintf(
2439 "%d Section %s not found.\n",
2440 ERROR + FILE_NOT_FOUND,
2441 CCC->download_desired_section);
2444 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2447 /* now for the user-mode message reading loops */
2448 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2450 /* Does the caller want to skip the headers? */
2451 if (headers_only == HEADERS_NONE) goto START_TEXT;
2453 /* Tell the client which format type we're using. */
2454 if ( (mode == MT_CITADEL) && (do_proto) ) {
2455 cprintf("type=%d\n", TheMessage->cm_format_type);
2458 /* nhdr=yes means that we're only displaying headers, no body */
2459 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2460 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2463 cprintf("nhdr=yes\n");
2466 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2467 OutputCtdlMsgHeaders(TheMessage, do_proto);
2470 /* begin header processing loop for RFC822 transfer format */
2474 strcpy(snode, NODENAME);
2475 if (mode == MT_RFC822)
2476 OutputRFC822MsgHeaders(
2481 suser, sizeof(suser),
2482 luser, sizeof(luser),
2483 fuser, sizeof(fuser),
2484 snode, sizeof(snode)
2488 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2489 suser[i] = tolower(suser[i]);
2490 if (!isalnum(suser[i])) suser[i]='_';
2493 if (mode == MT_RFC822) {
2494 if (!strcasecmp(snode, NODENAME)) {
2495 safestrncpy(snode, FQDN, sizeof snode);
2498 /* Construct a fun message id */
2499 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2500 if (strchr(mid, '@')==NULL) {
2501 cprintf("@%s", snode);
2505 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2506 cprintf("From: \"----\" <x@x.org>%s", nl);
2508 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2509 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2511 else if (!IsEmptyStr(fuser)) {
2512 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2515 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2518 /* Blank line signifying RFC822 end-of-headers */
2519 if (TheMessage->cm_format_type != FMT_RFC822) {
2524 /* end header processing loop ... at this point, we're in the text */
2526 if (headers_only == HEADERS_FAST) goto DONE;
2528 /* Tell the client about the MIME parts in this message */
2529 if (TheMessage->cm_format_type == FMT_RFC822) {
2530 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2531 mptr = TheMessage->cm_fields['M'];
2532 memset(&ma, 0, sizeof(struct ma_info));
2533 mime_parser(mptr, NULL,
2534 (do_proto ? *list_this_part : NULL),
2535 (do_proto ? *list_this_pref : NULL),
2536 (do_proto ? *list_this_suff : NULL),
2539 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2540 Dump_RFC822HeadersBody(
2549 if (headers_only == HEADERS_ONLY) {
2553 /* signify start of msg text */
2554 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2555 if (do_proto) cprintf("text\n");
2558 if (TheMessage->cm_format_type == FMT_FIXED)
2561 mode, /* how would you like that message? */
2564 /* If the message on disk is format 0 (Citadel vari-format), we
2565 * output using the formatter at 80 columns. This is the final output
2566 * form if the transfer format is RFC822, but if the transfer format
2567 * is Citadel proprietary, it'll still work, because the indentation
2568 * for new paragraphs is correct and the client will reformat the
2569 * message to the reader's screen width.
2571 if (TheMessage->cm_format_type == FMT_CITADEL) {
2572 mptr = TheMessage->cm_fields['M'];
2574 if (mode == MT_MIME) {
2575 cprintf("Content-type: text/x-citadel-variformat\n\n");
2580 /* If the message on disk is format 4 (MIME), we've gotta hand it
2581 * off to the MIME parser. The client has already been told that
2582 * this message is format 1 (fixed format), so the callback function
2583 * we use will display those parts as-is.
2585 if (TheMessage->cm_format_type == FMT_RFC822) {
2586 memset(&ma, 0, sizeof(struct ma_info));
2588 if (mode == MT_MIME) {
2589 ma.use_fo_hooks = 0;
2590 strcpy(ma.chosen_part, "1");
2591 ma.chosen_pref = 9999;
2592 ma.dont_decode = CCC->msg4_dont_decode;
2593 mime_parser(mptr, NULL,
2594 *choose_preferred, *fixed_output_pre,
2595 *fixed_output_post, (void *)&ma, 1);
2596 mime_parser(mptr, NULL,
2597 *output_preferred, NULL, NULL, (void *)&ma, 1);
2600 ma.use_fo_hooks = 1;
2601 mime_parser(mptr, NULL,
2602 *fixed_output, *fixed_output_pre,
2603 *fixed_output_post, (void *)&ma, 0);
2608 DONE: /* now we're done */
2609 if (do_proto) cprintf("000\n");
2615 * display a message (mode 0 - Citadel proprietary)
2617 void cmd_msg0(char *cmdbuf)
2620 int headers_only = HEADERS_ALL;
2622 msgid = extract_long(cmdbuf, 0);
2623 headers_only = extract_int(cmdbuf, 1);
2625 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
2631 * display a message (mode 2 - RFC822)
2633 void cmd_msg2(char *cmdbuf)
2636 int headers_only = HEADERS_ALL;
2638 msgid = extract_long(cmdbuf, 0);
2639 headers_only = extract_int(cmdbuf, 1);
2641 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
2647 * display a message (mode 3 - IGnet raw format - internal programs only)
2649 void cmd_msg3(char *cmdbuf)
2652 struct CtdlMessage *msg = NULL;
2655 if (CC->internal_pgm == 0) {
2656 cprintf("%d This command is for internal programs only.\n",
2657 ERROR + HIGHER_ACCESS_REQUIRED);
2661 msgnum = extract_long(cmdbuf, 0);
2662 msg = CtdlFetchMessage(msgnum, 1);
2664 cprintf("%d Message %ld not found.\n",
2665 ERROR + MESSAGE_NOT_FOUND, msgnum);
2669 serialize_message(&smr, msg);
2670 CtdlFreeMessage(msg);
2673 cprintf("%d Unable to serialize message\n",
2674 ERROR + INTERNAL_ERROR);
2678 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2679 client_write((char *)smr.ser, (int)smr.len);
2686 * Display a message using MIME content types
2688 void cmd_msg4(char *cmdbuf)
2693 msgid = extract_long(cmdbuf, 0);
2694 extract_token(section, cmdbuf, 1, '|', sizeof section);
2695 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
2701 * Client tells us its preferred message format(s)
2703 void cmd_msgp(char *cmdbuf)
2705 if (!strcasecmp(cmdbuf, "dont_decode")) {
2706 CC->msg4_dont_decode = 1;
2707 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2710 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2711 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2717 * Open a component of a MIME message as a download file
2719 void cmd_opna(char *cmdbuf)
2722 char desired_section[128];
2724 msgid = extract_long(cmdbuf, 0);
2725 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2726 safestrncpy(CC->download_desired_section, desired_section,
2727 sizeof CC->download_desired_section);
2728 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
2733 * Open a component of a MIME message and transmit it all at once
2735 void cmd_dlat(char *cmdbuf)
2738 char desired_section[128];
2740 msgid = extract_long(cmdbuf, 0);
2741 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2742 safestrncpy(CC->download_desired_section, desired_section,
2743 sizeof CC->download_desired_section);
2744 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
2749 * Save one or more message pointers into a specified room
2750 * (Returns 0 for success, nonzero for failure)
2751 * roomname may be NULL to use the current room
2753 * Note that the 'supplied_msg' field may be set to NULL, in which case
2754 * the message will be fetched from disk, by number, if we need to perform
2755 * replication checks. This adds an additional database read, so if the
2756 * caller already has the message in memory then it should be supplied. (Obviously
2757 * this mode of operation only works if we're saving a single message.)
2759 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2760 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2762 struct CitContext *CCC = CC;
2764 char hold_rm[ROOMNAMELEN];
2765 struct cdbdata *cdbfr;
2768 long highest_msg = 0L;
2771 struct CtdlMessage *msg = NULL;
2773 long *msgs_to_be_merged = NULL;
2774 int num_msgs_to_be_merged = 0;
2776 MSG_syslog(LOG_DEBUG,
2777 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2778 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2781 strcpy(hold_rm, CCC->room.QRname);
2784 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2785 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2786 if (num_newmsgs > 1) supplied_msg = NULL;
2788 /* Now the regular stuff */
2789 if (CtdlGetRoomLock(&CCC->room,
2790 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2792 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2793 return(ERROR + ROOM_NOT_FOUND);
2797 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2798 num_msgs_to_be_merged = 0;
2801 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2802 if (cdbfr == NULL) {
2806 msglist = (long *) cdbfr->ptr;
2807 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2808 num_msgs = cdbfr->len / sizeof(long);
2813 /* Create a list of msgid's which were supplied by the caller, but do
2814 * not already exist in the target room. It is absolutely taboo to
2815 * have more than one reference to the same message in a room.
2817 for (i=0; i<num_newmsgs; ++i) {
2819 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2820 if (msglist[j] == newmsgidlist[i]) {
2825 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2829 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2832 * Now merge the new messages
2834 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2835 if (msglist == NULL) {
2836 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2837 free(msgs_to_be_merged);
2838 return (ERROR + INTERNAL_ERROR);
2840 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2841 num_msgs += num_msgs_to_be_merged;
2843 /* Sort the message list, so all the msgid's are in order */
2844 num_msgs = sort_msglist(msglist, num_msgs);
2846 /* Determine the highest message number */
2847 highest_msg = msglist[num_msgs - 1];
2849 /* Write it back to disk. */
2850 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2851 msglist, (int)(num_msgs * sizeof(long)));
2853 /* Free up the memory we used. */
2856 /* Update the highest-message pointer and unlock the room. */
2857 CCC->room.QRhighest = highest_msg;
2858 CtdlPutRoomLock(&CCC->room);
2860 /* Perform replication checks if necessary */
2861 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
2862 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2864 for (i=0; i<num_msgs_to_be_merged; ++i) {
2865 msgid = msgs_to_be_merged[i];
2867 if (supplied_msg != NULL) {
2871 msg = CtdlFetchMessage(msgid, 0);
2875 ReplicationChecks(msg);
2877 /* If the message has an Exclusive ID, index that... */
2878 if (msg->cm_fields['E'] != NULL) {
2879 index_message_by_euid(msg->cm_fields['E'], &CCC->room, msgid);
2882 /* Free up the memory we may have allocated */
2883 if (msg != supplied_msg) {
2884 CtdlFreeMessage(msg);
2892 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2895 /* Submit this room for processing by hooks */
2896 PerformRoomHooks(&CCC->room);
2898 /* Go back to the room we were in before we wandered here... */
2899 CtdlGetRoom(&CCC->room, hold_rm);
2901 /* Bump the reference count for all messages which were merged */
2902 if (!suppress_refcount_adj) {
2903 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2906 /* Free up memory... */
2907 if (msgs_to_be_merged != NULL) {
2908 free(msgs_to_be_merged);
2911 /* Return success. */
2917 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2920 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2921 int do_repl_check, struct CtdlMessage *supplied_msg)
2923 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2930 * Message base operation to save a new message to the message store
2931 * (returns new message number)
2933 * This is the back end for CtdlSubmitMsg() and should not be directly
2934 * called by server-side modules.
2937 long send_message(struct CtdlMessage *msg) {
2938 struct CitContext *CCC = CC;
2946 /* Get a new message number */
2947 newmsgid = get_new_message_number();
2948 snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2949 (long unsigned int) time(NULL),
2950 (long unsigned int) newmsgid,
2954 /* Generate an ID if we don't have one already */
2955 if (msg->cm_fields['I']==NULL) {
2956 msg->cm_fields['I'] = strdup(msgidbuf);
2959 /* If the message is big, set its body aside for storage elsewhere */
2960 if (msg->cm_fields['M'] != NULL) {
2961 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2963 holdM = msg->cm_fields['M'];
2964 msg->cm_fields['M'] = NULL;
2968 /* Serialize our data structure for storage in the database */
2969 serialize_message(&smr, msg);
2972 msg->cm_fields['M'] = holdM;
2976 cprintf("%d Unable to serialize message\n",
2977 ERROR + INTERNAL_ERROR);
2981 /* Write our little bundle of joy into the message base */
2982 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2983 smr.ser, smr.len) < 0) {
2984 MSGM_syslog(LOG_ERR, "Can't store message\n");
2988 cdb_store(CDB_BIGMSGS,
2998 /* Free the memory we used for the serialized message */
3001 /* Return the *local* message ID to the caller
3002 * (even if we're storing an incoming network message)
3010 * Serialize a struct CtdlMessage into the format used on disk and network.
3012 * This function loads up a "struct ser_ret" (defined in server.h) which
3013 * contains the length of the serialized message and a pointer to the
3014 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3016 void serialize_message(struct ser_ret *ret, /* return values */
3017 struct CtdlMessage *msg) /* unserialized msg */
3019 struct CitContext *CCC = CC;
3020 size_t wlen, fieldlen;
3022 static char *forder = FORDER;
3025 * Check for valid message format
3027 if (is_valid_message(msg) == 0) {
3028 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
3035 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
3036 ret->len = ret->len +
3037 strlen(msg->cm_fields[(int)forder[i]]) + 2;
3039 ret->ser = malloc(ret->len);
3040 if (ret->ser == NULL) {
3041 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3042 (long)ret->len, strerror(errno));
3049 ret->ser[1] = msg->cm_anon_type;
3050 ret->ser[2] = msg->cm_format_type;
3053 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
3054 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
3055 ret->ser[wlen++] = (char)forder[i];
3056 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
3057 wlen = wlen + fieldlen + 1;
3059 if (ret->len != wlen) {
3060 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
3061 (long)ret->len, (long)wlen);
3069 * Check to see if any messages already exist in the current room which
3070 * carry the same Exclusive ID as this one. If any are found, delete them.
3072 void ReplicationChecks(struct CtdlMessage *msg) {
3073 struct CitContext *CCC = CC;
3074 long old_msgnum = (-1L);
3076 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3078 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3081 /* No exclusive id? Don't do anything. */
3082 if (msg == NULL) return;
3083 if (msg->cm_fields['E'] == NULL) return;
3084 if (IsEmptyStr(msg->cm_fields['E'])) return;
3085 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3086 msg->cm_fields['E'], CCC->room.QRname);*/
3088 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CCC->room);
3089 if (old_msgnum > 0L) {
3090 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3091 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3098 * Save a message to disk and submit it into the delivery system.
3100 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3101 struct recptypes *recps, /* recipients (if mail) */
3102 const char *force, /* force a particular room? */
3103 int flags /* should the message be exported clean? */
3106 char submit_filename[128];
3107 char generated_timestamp[32];
3108 char hold_rm[ROOMNAMELEN];
3109 char actual_rm[ROOMNAMELEN];
3110 char force_room[ROOMNAMELEN];
3111 char content_type[SIZ]; /* We have to learn this */
3112 char recipient[SIZ];
3115 const char *mptr = NULL;
3116 struct ctdluser userbuf;
3118 struct MetaData smi;
3119 FILE *network_fp = NULL;
3120 static int seqnum = 1;
3121 struct CtdlMessage *imsg = NULL;
3123 size_t instr_alloc = 0;
3125 char *hold_R, *hold_D;
3126 char *collected_addresses = NULL;
3127 struct addresses_to_be_filed *aptr = NULL;
3128 StrBuf *saved_rfc822_version = NULL;
3129 int qualified_for_journaling = 0;
3130 CitContext *CCC = MyContext();
3131 char bounce_to[1024] = "";
3134 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3135 if (is_valid_message(msg) == 0) return(-1); /* self check */
3137 /* If this message has no timestamp, we take the liberty of
3138 * giving it one, right now.
3140 if (msg->cm_fields['T'] == NULL) {
3141 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3142 msg->cm_fields['T'] = strdup(generated_timestamp);
3145 /* If this message has no path, we generate one.
3147 if (msg->cm_fields['P'] == NULL) {
3148 if (msg->cm_fields['A'] != NULL) {
3149 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3150 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3151 if (isspace(msg->cm_fields['P'][a])) {
3152 msg->cm_fields['P'][a] = ' ';
3157 msg->cm_fields['P'] = strdup("unknown");
3161 if (force == NULL) {
3162 strcpy(force_room, "");
3165 strcpy(force_room, force);
3168 /* Learn about what's inside, because it's what's inside that counts */
3169 if (msg->cm_fields['M'] == NULL) {
3170 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3174 switch (msg->cm_format_type) {
3176 strcpy(content_type, "text/x-citadel-variformat");
3179 strcpy(content_type, "text/plain");
3182 strcpy(content_type, "text/plain");
3183 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3186 safestrncpy(content_type, &mptr[13], sizeof content_type);
3187 striplt(content_type);
3188 aptr = content_type;
3189 while (!IsEmptyStr(aptr)) {
3201 /* Goto the correct room */
3202 room = (recps) ? CCC->room.QRname : SENTITEMS;
3203 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3204 strcpy(hold_rm, CCC->room.QRname);
3205 strcpy(actual_rm, CCC->room.QRname);
3206 if (recps != NULL) {
3207 strcpy(actual_rm, SENTITEMS);
3210 /* If the user is a twit, move to the twit room for posting */
3212 if (CCC->user.axlevel == AxProbU) {
3213 strcpy(hold_rm, actual_rm);
3214 strcpy(actual_rm, config.c_twitroom);
3215 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3219 /* ...or if this message is destined for Aide> then go there. */
3220 if (!IsEmptyStr(force_room)) {
3221 strcpy(actual_rm, force_room);
3224 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3225 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3226 /* CtdlGetRoom(&CCC->room, actual_rm); */
3227 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3231 * If this message has no O (room) field, generate one.
3233 if (msg->cm_fields['O'] == NULL) {
3234 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3237 /* Perform "before save" hooks (aborting if any return nonzero) */
3238 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3239 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3242 * If this message has an Exclusive ID, and the room is replication
3243 * checking enabled, then do replication checks.
3245 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3246 ReplicationChecks(msg);
3249 /* Save it to disk */
3250 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3251 newmsgid = send_message(msg);
3252 if (newmsgid <= 0L) return(-5);
3254 /* Write a supplemental message info record. This doesn't have to
3255 * be a critical section because nobody else knows about this message
3258 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3259 memset(&smi, 0, sizeof(struct MetaData));
3260 smi.meta_msgnum = newmsgid;
3261 smi.meta_refcount = 0;
3262 safestrncpy(smi.meta_content_type, content_type,
3263 sizeof smi.meta_content_type);
3266 * Measure how big this message will be when rendered as RFC822.
3267 * We do this for two reasons:
3268 * 1. We need the RFC822 length for the new metadata record, so the
3269 * POP and IMAP services don't have to calculate message lengths
3270 * while the user is waiting (multiplied by potentially hundreds
3271 * or thousands of messages).
3272 * 2. If journaling is enabled, we will need an RFC822 version of the
3273 * message to attach to the journalized copy.
3275 if (CCC->redirect_buffer != NULL) {
3276 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3279 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3280 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3281 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3282 saved_rfc822_version = CCC->redirect_buffer;
3283 CCC->redirect_buffer = NULL;
3287 /* Now figure out where to store the pointers */
3288 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3290 /* If this is being done by the networker delivering a private
3291 * message, we want to BYPASS saving the sender's copy (because there
3292 * is no local sender; it would otherwise go to the Trashcan).
3294 if ((!CCC->internal_pgm) || (recps == NULL)) {
3295 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3296 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3297 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3301 /* For internet mail, drop a copy in the outbound queue room */
3302 if ((recps != NULL) && (recps->num_internet > 0)) {
3303 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3306 /* If other rooms are specified, drop them there too. */
3307 if ((recps != NULL) && (recps->num_room > 0))
3308 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3309 extract_token(recipient, recps->recp_room, i,
3310 '|', sizeof recipient);
3311 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3312 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3315 /* Bump this user's messages posted counter. */
3316 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3317 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3318 CCC->user.posted = CCC->user.posted + 1;
3319 CtdlPutUserLock(&CCC->user);
3321 /* Decide where bounces need to be delivered */
3322 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3323 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3325 else if (CCC->logged_in) {
3326 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3329 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3332 /* If this is private, local mail, make a copy in the
3333 * recipient's mailbox and bump the reference count.
3335 if ((recps != NULL) && (recps->num_local > 0))
3336 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3337 extract_token(recipient, recps->recp_local, i,
3338 '|', sizeof recipient);
3339 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3341 if (CtdlGetUser(&userbuf, recipient) == 0) {
3342 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3343 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3344 CtdlBumpNewMailCounter(userbuf.usernum);
3345 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3346 /* Generate a instruction message for the Funambol notification
3347 * server, in the same style as the SMTP queue
3350 instr = malloc(instr_alloc);
3351 snprintf(instr, instr_alloc,
3352 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3354 SPOOLMIME, newmsgid, (long)time(NULL),
3358 imsg = malloc(sizeof(struct CtdlMessage));
3359 memset(imsg, 0, sizeof(struct CtdlMessage));
3360 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3361 imsg->cm_anon_type = MES_NORMAL;
3362 imsg->cm_format_type = FMT_RFC822;
3363 imsg->cm_fields['U'] = strdup("QMSG");
3364 imsg->cm_fields['A'] = strdup("Citadel");
3365 imsg->cm_fields['J'] = strdup("do not journal");
3366 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3367 imsg->cm_fields['2'] = strdup(recipient);
3368 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3369 CtdlFreeMessage(imsg);
3373 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3374 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3378 /* Perform "after save" hooks */
3379 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3380 if (msg->cm_fields['3'] != NULL) free(msg->cm_fields['3']);
3381 msg->cm_fields['3'] = malloc(20);
3382 snprintf(msg->cm_fields['3'], 20, "%ld", newmsgid);
3383 PerformMessageHooks(msg, EVT_AFTERSAVE);
3384 free(msg->cm_fields['3']);
3385 msg->cm_fields['3'] = NULL;
3387 /* For IGnet mail, we have to save a new copy into the spooler for
3388 * each recipient, with the R and D fields set to the recipient and
3389 * destination-node. This has two ugly side effects: all other
3390 * recipients end up being unlisted in this recipient's copy of the
3391 * message, and it has to deliver multiple messages to the same
3392 * node. We'll revisit this again in a year or so when everyone has
3393 * a network spool receiver that can handle the new style messages.
3395 if ((recps != NULL) && (recps->num_ignet > 0))
3396 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3397 extract_token(recipient, recps->recp_ignet, i,
3398 '|', sizeof recipient);
3400 hold_R = msg->cm_fields['R'];
3401 hold_D = msg->cm_fields['D'];
3402 msg->cm_fields['R'] = malloc(SIZ);
3403 msg->cm_fields['D'] = malloc(128);
3404 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3405 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3407 serialize_message(&smr, msg);
3409 snprintf(submit_filename, sizeof submit_filename,
3410 "%s/netmail.%04lx.%04x.%04x",
3412 (long) getpid(), CCC->cs_pid, ++seqnum);
3413 network_fp = fopen(submit_filename, "wb+");
3414 if (network_fp != NULL) {
3415 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3417 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3425 free(msg->cm_fields['R']);
3426 free(msg->cm_fields['D']);
3427 msg->cm_fields['R'] = hold_R;
3428 msg->cm_fields['D'] = hold_D;
3431 /* Go back to the room we started from */
3432 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3433 if (strcasecmp(hold_rm, CCC->room.QRname))
3434 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3436 /* For internet mail, generate delivery instructions.
3437 * Yes, this is recursive. Deal with it. Infinite recursion does
3438 * not happen because the delivery instructions message does not
3439 * contain a recipient.
3441 if ((recps != NULL) && (recps->num_internet > 0)) {
3442 StrBuf *SpoolMsg = NewStrBuf();
3445 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3447 StrBufPrintf(SpoolMsg,
3448 "Content-type: "SPOOLMIME"\n"
3457 if (recps->envelope_from != NULL) {
3458 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3459 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3460 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3462 if (recps->sending_room != NULL) {
3463 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3464 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3465 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3468 nTokens = num_tokens(recps->recp_internet, '|');
3469 for (i = 0; i < nTokens; i++) {
3471 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3473 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3474 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3475 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3479 imsg = malloc(sizeof(struct CtdlMessage));
3480 memset(imsg, 0, sizeof(struct CtdlMessage));
3481 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3482 imsg->cm_anon_type = MES_NORMAL;
3483 imsg->cm_format_type = FMT_RFC822;
3484 imsg->cm_fields['U'] = strdup("QMSG");
3485 imsg->cm_fields['A'] = strdup("Citadel");
3486 imsg->cm_fields['J'] = strdup("do not journal");
3487 imsg->cm_fields['M'] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3488 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3489 CtdlFreeMessage(imsg);
3493 * Any addresses to harvest for someone's address book?
3495 if ( (CCC->logged_in) && (recps != NULL) ) {
3496 collected_addresses = harvest_collected_addresses(msg);
3499 if (collected_addresses != NULL) {
3500 aptr = (struct addresses_to_be_filed *)
3501 malloc(sizeof(struct addresses_to_be_filed));
3502 CtdlMailboxName(actual_rm, sizeof actual_rm,
3503 &CCC->user, USERCONTACTSROOM);
3504 aptr->roomname = strdup(actual_rm);
3505 aptr->collected_addresses = collected_addresses;
3506 begin_critical_section(S_ATBF);
3509 end_critical_section(S_ATBF);
3513 * Determine whether this message qualifies for journaling.
3515 if (msg->cm_fields['J'] != NULL) {
3516 qualified_for_journaling = 0;
3519 if (recps == NULL) {
3520 qualified_for_journaling = config.c_journal_pubmsgs;
3522 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3523 qualified_for_journaling = config.c_journal_email;
3526 qualified_for_journaling = config.c_journal_pubmsgs;
3531 * Do we have to perform journaling? If so, hand off the saved
3532 * RFC822 version will be handed off to the journaler for background
3533 * submit. Otherwise, we have to free the memory ourselves.
3535 if (saved_rfc822_version != NULL) {
3536 if (qualified_for_journaling) {
3537 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3540 FreeStrBuf(&saved_rfc822_version);
3550 * Convenience function for generating small administrative messages.
3552 void quickie_message(const char *from,
3553 const char *fromaddr,
3558 const char *subject)
3560 struct CtdlMessage *msg;
3561 struct recptypes *recp = NULL;
3563 msg = malloc(sizeof(struct CtdlMessage));
3564 memset(msg, 0, sizeof(struct CtdlMessage));
3565 msg->cm_magic = CTDLMESSAGE_MAGIC;
3566 msg->cm_anon_type = MES_NORMAL;
3567 msg->cm_format_type = format_type;
3570 msg->cm_fields['A'] = strdup(from);
3572 else if (fromaddr != NULL) {
3573 msg->cm_fields['A'] = strdup(fromaddr);
3574 if (strchr(msg->cm_fields['A'], '@')) {
3575 *strchr(msg->cm_fields['A'], '@') = 0;
3579 msg->cm_fields['A'] = strdup("Citadel");
3582 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3583 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3584 msg->cm_fields['N'] = strdup(NODENAME);
3586 msg->cm_fields['R'] = strdup(to);
3587 recp = validate_recipients(to, NULL, 0);
3589 if (subject != NULL) {
3590 msg->cm_fields['U'] = strdup(subject);
3592 msg->cm_fields['M'] = strdup(text);
3594 CtdlSubmitMsg(msg, recp, room, 0);
3595 CtdlFreeMessage(msg);
3596 if (recp != NULL) free_recipients(recp);
3599 void flood_protect_quickie_message(const char *from,
3600 const char *fromaddr,
3605 const char *subject,
3607 const char **CritStr,
3612 u_char rawdigest[MD5_DIGEST_LEN];
3613 struct MD5Context md5context;
3615 struct cdbdata *cdbut;
3618 time_t ts = time(NULL);
3619 time_t tsday = ts / (8*60*60); /* just care for a day... */
3621 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3622 MD5Init(&md5context);
3624 for (i = 0; i < nCriterions; i++)
3625 MD5Update(&md5context,
3626 (const unsigned char*)CritStr[i], CritStrLen[i]);
3627 MD5Update(&md5context,
3628 (const unsigned char*)timestamp, tslen);
3629 MD5Final(rawdigest, &md5context);
3631 guid = NewStrBufPlain(NULL,
3632 MD5_DIGEST_LEN * 2 + 12);
3633 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3634 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3635 if (StrLength(guid) > 40)
3636 StrBufCutAt(guid, 40, NULL);
3637 /* Find out if we've already sent a similar message */
3638 memcpy(ut.ut_msgid, SKEY(guid));
3639 ut.ut_timestamp = ts;
3641 cdbut = cdb_fetch(CDB_USETABLE, SKEY(guid));
3643 if (cdbut != NULL) {
3644 /* yes, we did. flood protection kicks in. */
3646 "not sending message again\n");
3650 /* rewrite the record anyway, to update the timestamp */
3651 cdb_store(CDB_USETABLE,
3653 &ut, sizeof(struct UseTable) );
3657 if (cdbut != NULL) return;
3658 /* no, this message isn't sent recently; go ahead. */
3659 quickie_message(from,
3670 * Back end function used by CtdlMakeMessage() and similar functions
3672 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3674 size_t maxlen, /* maximum message length */
3675 StrBuf *exist, /* if non-null, append to it;
3676 exist is ALWAYS freed */
3677 int crlf, /* CRLF newlines instead of LF */
3678 int *sock /* socket handle or 0 for this session's client socket */
3687 LineBuf = NewStrBufPlain(NULL, SIZ);
3688 if (exist == NULL) {
3689 Message = NewStrBufPlain(NULL, 4 * SIZ);
3692 Message = NewStrBufDup(exist);
3695 /* Do we need to change leading ".." to "." for SMTP escaping? */
3696 if ((tlen == 1) && (*terminator == '.')) {
3700 /* read in the lines of message text one by one */
3703 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3708 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3710 if ((StrLength(LineBuf) == tlen) &&
3711 (!strcmp(ChrPtr(LineBuf), terminator)))
3714 if ( (!flushing) && (!finished) ) {
3716 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3719 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3722 /* Unescape SMTP-style input of two dots at the beginning of the line */
3724 (StrLength(LineBuf) == 2) &&
3725 (!strcmp(ChrPtr(LineBuf), "..")))
3727 StrBufCutLeft(LineBuf, 1);
3730 StrBufAppendBuf(Message, LineBuf, 0);
3733 /* if we've hit the max msg length, flush the rest */
3734 if (StrLength(Message) >= maxlen) flushing = 1;
3736 } while (!finished);
3737 FreeStrBuf(&LineBuf);
3741 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3745 FreeStrBuf(&(*Msg)->MsgBuf);
3751 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3753 size_t maxlen, /* maximum message length */
3754 size_t expectlen, /* if we expect a message, how long should it be? */
3755 StrBuf *exist, /* if non-null, append to it;
3756 exist is ALWAYS freed */
3757 long eLen, /* length of exist */
3758 int crlf /* CRLF newlines instead of LF */
3761 ReadAsyncMsg *NewMsg;
3763 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3764 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3766 if (exist == NULL) {
3769 if (expectlen == 0) {
3773 len = expectlen + 10;
3775 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3778 NewMsg->MsgBuf = NewStrBufDup(exist);
3780 /* Do we need to change leading ".." to "." for SMTP escaping? */
3781 if ((tlen == 1) && (*terminator == '.')) {
3785 NewMsg->terminator = terminator;
3786 NewMsg->tlen = tlen;
3788 NewMsg->maxlen = maxlen;
3790 NewMsg->crlf = crlf;
3796 * Back end function used by CtdlMakeMessage() and similar functions
3798 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3800 ReadAsyncMsg *ReadMsg;
3801 int MsgFinished = 0;
3802 eReadState Finished = eMustReadMore;
3807 const char *pch = ChrPtr(IO->SendBuf.Buf);
3808 const char *pchh = IO->SendBuf.ReadWritePointer;
3814 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3815 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3816 ((CitContext*)(IO->CitContext))->ServiceName,
3819 fd = fopen(fn, "a+");
3822 ReadMsg = IO->ReadMsg;
3824 /* read in the lines of message text one by one */
3826 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3829 case eMustReadMore: /// read new from socket...
3831 if (IO->RecvBuf.ReadWritePointer != NULL) {
3832 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3833 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3835 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3839 fprintf(fd, "BufferEmpty! \n");
3845 case eBufferNotEmpty: /* shouldn't happen... */
3846 case eReadSuccess: /// done for now...
3848 case eReadFail: /// WHUT?
3854 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3855 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3858 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3861 else if (!ReadMsg->flushing) {
3864 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3867 /* Unescape SMTP-style input of two dots at the beginning of the line */
3868 if ((ReadMsg->dodot) &&
3869 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3870 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3873 fprintf(fd, "UnEscaped!\n");
3875 StrBufCutLeft(IO->IOBuf, 1);
3878 if (ReadMsg->crlf) {
3879 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3882 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3885 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3888 /* if we've hit the max msg length, flush the rest */
3889 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3891 } while (!MsgFinished);
3894 fprintf(fd, "Done with reading; %s.\n, ",
3895 (MsgFinished)?"Message Finished": "FAILED");
3899 return eReadSuccess;
3906 * Back end function used by CtdlMakeMessage() and similar functions
3908 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3910 size_t maxlen, /* maximum message length */
3911 StrBuf *exist, /* if non-null, append to it;
3912 exist is ALWAYS freed */
3913 int crlf, /* CRLF newlines instead of LF */
3914 int *sock /* socket handle or 0 for this session's client socket */
3919 Message = CtdlReadMessageBodyBuf(terminator,
3925 if (Message == NULL)
3928 return SmashStrBuf(&Message);
3933 * Build a binary message to be saved on disk.
3934 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3935 * will become part of the message. This means you are no longer
3936 * responsible for managing that memory -- it will be freed along with
3937 * the rest of the fields when CtdlFreeMessage() is called.)
3940 struct CtdlMessage *CtdlMakeMessage(
3941 struct ctdluser *author, /* author's user structure */
3942 char *recipient, /* NULL if it's not mail */
3943 char *recp_cc, /* NULL if it's not mail */
3944 char *room, /* room where it's going */
3945 int type, /* see MES_ types in header file */
3946 int format_type, /* variformat, plain text, MIME... */
3947 char *fake_name, /* who we're masquerading as */
3948 char *my_email, /* which of my email addresses to use (empty is ok) */
3949 char *subject, /* Subject (optional) */
3950 char *supplied_euid, /* ...or NULL if this is irrelevant */
3951 char *preformatted_text, /* ...or NULL to read text from client */
3952 char *references /* Thread references */
3954 char dest_node[256];
3956 struct CtdlMessage *msg;
3958 StrBuf *FakeEncAuthor = NULL;
3960 msg = malloc(sizeof(struct CtdlMessage));
3961 memset(msg, 0, sizeof(struct CtdlMessage));
3962 msg->cm_magic = CTDLMESSAGE_MAGIC;
3963 msg->cm_anon_type = type;
3964 msg->cm_format_type = format_type;
3966 /* Don't confuse the poor folks if it's not routed mail. */
3967 strcpy(dest_node, "");
3969 if (recipient != NULL) striplt(recipient);
3970 if (recp_cc != NULL) striplt(recp_cc);
3972 /* Path or Return-Path */
3973 if (my_email == NULL) my_email = "";
3975 if (!IsEmptyStr(my_email)) {
3976 msg->cm_fields['P'] = strdup(my_email);
3979 snprintf(buf, sizeof buf, "%s", author->fullname);
3980 msg->cm_fields['P'] = strdup(buf);
3982 convert_spaces_to_underscores(msg->cm_fields['P']);
3984 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3985 msg->cm_fields['T'] = strdup(buf);
3987 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3988 FakeAuthor = NewStrBufPlain (fake_name, -1);
3991 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3993 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3994 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3995 FreeStrBuf(&FakeAuthor);
3997 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3998 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
4001 msg->cm_fields['O'] = strdup(CC->room.QRname);
4004 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
4005 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
4007 if ((recipient != NULL) && (recipient[0] != 0)) {
4008 msg->cm_fields['R'] = strdup(recipient);
4010 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
4011 msg->cm_fields['Y'] = strdup(recp_cc);
4013 if (dest_node[0] != 0) {
4014 msg->cm_fields['D'] = strdup(dest_node);
4017 if (!IsEmptyStr(my_email)) {
4018 msg->cm_fields['F'] = strdup(my_email);
4020 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
4021 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
4024 if (subject != NULL) {
4027 length = strlen(subject);
4033 while ((subject[i] != '\0') &&
4034 (IsAscii = isascii(subject[i]) != 0 ))
4037 msg->cm_fields['U'] = strdup(subject);
4038 else /* ok, we've got utf8 in the string. */
4040 msg->cm_fields['U'] = rfc2047encode(subject, length);
4046 if (supplied_euid != NULL) {
4047 msg->cm_fields['E'] = strdup(supplied_euid);
4050 if ((references != NULL) && (!IsEmptyStr(references))) {
4051 if (msg->cm_fields['W'] != NULL)
4052 free(msg->cm_fields['W']);
4053 msg->cm_fields['W'] = strdup(references);
4056 if (preformatted_text != NULL) {
4057 msg->cm_fields['M'] = preformatted_text;
4060 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4067 * Check to see whether we have permission to post a message in the current
4068 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
4069 * returns 0 on success.
4071 int CtdlDoIHavePermissionToPostInThisRoom(
4074 const char* RemoteIdentifier,
4080 if (!(CC->logged_in) &&
4081 (PostPublic == POST_LOGGED_IN)) {
4082 snprintf(errmsgbuf, n, "Not logged in.");
4083 return (ERROR + NOT_LOGGED_IN);
4085 else if (PostPublic == CHECK_EXISTANCE) {
4086 return (0); // We're Evaling whether a recipient exists
4088 else if (!(CC->logged_in)) {
4090 if ((CC->room.QRflags & QR_READONLY)) {
4091 snprintf(errmsgbuf, n, "Not logged in.");
4092 return (ERROR + NOT_LOGGED_IN);
4094 if (CC->room.QRflags2 & QR2_MODERATED) {
4095 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4096 return (ERROR + NOT_LOGGED_IN);
4098 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4100 return CtdlNetconfigCheckRoomaccess(errmsgbuf, n, RemoteIdentifier);
4106 if ((CC->user.axlevel < AxProbU)
4107 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4108 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4109 return (ERROR + HIGHER_ACCESS_REQUIRED);
4112 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4114 if (ra & UA_POSTALLOWED) {
4115 strcpy(errmsgbuf, "OK to post or reply here");
4119 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4121 * To be thorough, we ought to check to see if the message they are
4122 * replying to is actually a valid one in this room, but unless this
4123 * actually becomes a problem we'll go with high performance instead.
4125 strcpy(errmsgbuf, "OK to reply here");
4129 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4130 /* Clarify what happened with a better error message */
4131 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4132 return (ERROR + HIGHER_ACCESS_REQUIRED);
4135 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4136 return (ERROR + HIGHER_ACCESS_REQUIRED);
4142 * Check to see if the specified user has Internet mail permission
4143 * (returns nonzero if permission is granted)
4145 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4147 /* Do not allow twits to send Internet mail */
4148 if (who->axlevel <= AxProbU) return(0);
4150 /* Globally enabled? */
4151 if (config.c_restrict == 0) return(1);
4153 /* User flagged ok? */
4154 if (who->flags & US_INTERNET) return(2);
4156 /* Admin level access? */
4157 if (who->axlevel >= AxAideU) return(3);
4159 /* No mail for you! */
4165 * Validate recipients, count delivery types and errors, and handle aliasing
4166 * FIXME check for dupes!!!!!
4168 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4169 * were specified, or the number of addresses found invalid.
4171 * Caller needs to free the result using free_recipients()
4173 struct recptypes *validate_recipients(const char *supplied_recipients,
4174 const char *RemoteIdentifier,
4176 struct CitContext *CCC = CC;
4177 struct recptypes *ret;
4178 char *recipients = NULL;
4180 char this_recp[256];
4181 char this_recp_cooked[256];
4188 struct ctdluser tempUS;
4189 struct ctdlroom tempQR;
4190 struct ctdlroom tempQR2;
4196 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4197 if (ret == NULL) return(NULL);
4199 /* Set all strings to null and numeric values to zero */
4200 memset(ret, 0, sizeof(struct recptypes));
4202 if (supplied_recipients == NULL) {
4203 recipients = strdup("");
4206 recipients = strdup(supplied_recipients);
4209 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4210 * actually need, but it's healthier for the heap than doing lots of tiny
4211 * realloc() calls instead.
4213 len = strlen(recipients) + 1024;
4214 ret->errormsg = malloc(len);
4215 ret->recp_local = malloc(len);
4216 ret->recp_internet = malloc(len);
4217 ret->recp_ignet = malloc(len);
4218 ret->recp_room = malloc(len);
4219 ret->display_recp = malloc(len);
4220 ret->recp_orgroom = malloc(len);
4221 org_recp = malloc(len);
4223 ret->errormsg[0] = 0;
4224 ret->recp_local[0] = 0;
4225 ret->recp_internet[0] = 0;
4226 ret->recp_ignet[0] = 0;
4227 ret->recp_room[0] = 0;
4228 ret->recp_orgroom[0] = 0;
4229 ret->display_recp[0] = 0;
4231 ret->recptypes_magic = RECPTYPES_MAGIC;
4233 /* Change all valid separator characters to commas */
4234 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4235 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4236 recipients[i] = ',';
4240 /* Now start extracting recipients... */
4242 while (!IsEmptyStr(recipients)) {
4243 for (i=0; i<=strlen(recipients); ++i) {
4244 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4245 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4246 safestrncpy(this_recp, recipients, i+1);
4248 if (recipients[i] == ',') {
4249 strcpy(recipients, &recipients[i+1]);
4252 strcpy(recipients, "");
4259 if (IsEmptyStr(this_recp))
4261 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4264 strcpy(org_recp, this_recp);
4267 mailtype = alias(this_recp);
4269 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4270 if (this_recp[j]=='_') {
4271 this_recp_cooked[j] = ' ';
4274 this_recp_cooked[j] = this_recp[j];
4277 this_recp_cooked[j] = '\0';
4282 if (!strcasecmp(this_recp, "sysop")) {
4284 strcpy(this_recp, config.c_aideroom);
4285 if (!IsEmptyStr(ret->recp_room)) {
4286 strcat(ret->recp_room, "|");
4288 strcat(ret->recp_room, this_recp);
4290 else if ( (!strncasecmp(this_recp, "room_", 5))
4291 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4293 /* Save room so we can restore it later */
4294 tempQR2 = CCC->room;
4297 /* Check permissions to send mail to this room */
4298 err = CtdlDoIHavePermissionToPostInThisRoom(
4303 0 /* 0 = not a reply */
4312 if (!IsEmptyStr(ret->recp_room)) {
4313 strcat(ret->recp_room, "|");
4315 strcat(ret->recp_room, &this_recp_cooked[5]);
4317 if (!IsEmptyStr(ret->recp_orgroom)) {
4318 strcat(ret->recp_orgroom, "|");
4320 strcat(ret->recp_orgroom, org_recp);
4324 /* Restore room in case something needs it */
4325 CCC->room = tempQR2;
4328 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4330 strcpy(this_recp, tempUS.fullname);
4331 if (!IsEmptyStr(ret->recp_local)) {
4332 strcat(ret->recp_local, "|");
4334 strcat(ret->recp_local, this_recp);
4336 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4338 strcpy(this_recp, tempUS.fullname);
4339 if (!IsEmptyStr(ret->recp_local)) {
4340 strcat(ret->recp_local, "|");
4342 strcat(ret->recp_local, this_recp);
4350 /* Yes, you're reading this correctly: if the target
4351 * domain points back to the local system or an attached
4352 * Citadel directory, the address is invalid. That's
4353 * because if the address were valid, we would have
4354 * already translated it to a local address by now.
4356 if (IsDirectory(this_recp, 0)) {
4361 ++ret->num_internet;
4362 if (!IsEmptyStr(ret->recp_internet)) {
4363 strcat(ret->recp_internet, "|");
4365 strcat(ret->recp_internet, this_recp);
4370 if (!IsEmptyStr(ret->recp_ignet)) {
4371 strcat(ret->recp_ignet, "|");
4373 strcat(ret->recp_ignet, this_recp);
4381 if (IsEmptyStr(errmsg)) {
4382 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4385 snprintf(append, sizeof append, "%s", errmsg);
4387 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4388 if (!IsEmptyStr(ret->errormsg)) {
4389 strcat(ret->errormsg, "; ");
4391 strcat(ret->errormsg, append);
4395 if (IsEmptyStr(ret->display_recp)) {
4396 strcpy(append, this_recp);
4399 snprintf(append, sizeof append, ", %s", this_recp);
4401 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4402 strcat(ret->display_recp, append);
4408 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4409 ret->num_room + ret->num_error) == 0) {
4410 ret->num_error = (-1);
4411 strcpy(ret->errormsg, "No recipients specified.");
4414 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4415 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4416 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4417 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4418 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4419 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4427 * Destructor for struct recptypes
4429 void free_recipients(struct recptypes *valid) {
4431 if (valid == NULL) {
4435 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4436 struct CitContext *CCC = CC;
4437 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4441 if (valid->errormsg != NULL) free(valid->errormsg);
4442 if (valid->recp_local != NULL) free(valid->recp_local);
4443 if (valid->recp_internet != NULL) free(valid->recp_internet);
4444 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4445 if (valid->recp_room != NULL) free(valid->recp_room);
4446 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4447 if (valid->display_recp != NULL) free(valid->display_recp);
4448 if (valid->bounce_to != NULL) free(valid->bounce_to);
4449 if (valid->envelope_from != NULL) free(valid->envelope_from);
4450 if (valid->sending_room != NULL) free(valid->sending_room);
4457 * message entry - mode 0 (normal)
4459 void cmd_ent0(char *entargs)
4461 struct CitContext *CCC = CC;
4466 char supplied_euid[128];
4468 int format_type = 0;
4469 char newusername[256];
4470 char newuseremail[256];
4471 struct CtdlMessage *msg;
4475 struct recptypes *valid = NULL;
4476 struct recptypes *valid_to = NULL;
4477 struct recptypes *valid_cc = NULL;
4478 struct recptypes *valid_bcc = NULL;
4480 int subject_required = 0;
4485 int newuseremail_ok = 0;
4486 char references[SIZ];
4491 post = extract_int(entargs, 0);
4492 extract_token(recp, entargs, 1, '|', sizeof recp);
4493 anon_flag = extract_int(entargs, 2);
4494 format_type = extract_int(entargs, 3);
4495 extract_token(subject, entargs, 4, '|', sizeof subject);
4496 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4497 do_confirm = extract_int(entargs, 6);
4498 extract_token(cc, entargs, 7, '|', sizeof cc);
4499 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4500 switch(CC->room.QRdefaultview) {
4503 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4506 supplied_euid[0] = 0;
4509 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4510 extract_token(references, entargs, 11, '|', sizeof references);
4511 for (ptr=references; *ptr != 0; ++ptr) {
4512 if (*ptr == '!') *ptr = '|';
4515 /* first check to make sure the request is valid. */
4517 err = CtdlDoIHavePermissionToPostInThisRoom(
4522 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4526 cprintf("%d %s\n", err, errmsg);
4530 /* Check some other permission type things. */
4532 if (IsEmptyStr(newusername)) {
4533 strcpy(newusername, CCC->user.fullname);
4535 if ( (CCC->user.axlevel < AxAideU)
4536 && (strcasecmp(newusername, CCC->user.fullname))
4537 && (strcasecmp(newusername, CCC->cs_inet_fn))
4539 cprintf("%d You don't have permission to author messages as '%s'.\n",
4540 ERROR + HIGHER_ACCESS_REQUIRED,
4547 if (IsEmptyStr(newuseremail)) {
4548 newuseremail_ok = 1;
4551 if (!IsEmptyStr(newuseremail)) {
4552 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4553 newuseremail_ok = 1;
4555 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4556 j = num_tokens(CCC->cs_inet_other_emails, '|');
4557 for (i=0; i<j; ++i) {
4558 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4559 if (!strcasecmp(newuseremail, buf)) {
4560 newuseremail_ok = 1;
4566 if (!newuseremail_ok) {
4567 cprintf("%d You don't have permission to author messages as '%s'.\n",
4568 ERROR + HIGHER_ACCESS_REQUIRED,
4574 CCC->cs_flags |= CS_POSTING;
4576 /* In mailbox rooms we have to behave a little differently --
4577 * make sure the user has specified at least one recipient. Then
4578 * validate the recipient(s). We do this for the Mail> room, as
4579 * well as any room which has the "Mailbox" view set - unless it
4580 * is the DRAFTS room which does not require recipients
4583 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4584 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4585 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4586 if (CCC->user.axlevel < AxProbU) {
4587 strcpy(recp, "sysop");
4592 valid_to = validate_recipients(recp, NULL, 0);
4593 if (valid_to->num_error > 0) {
4594 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4595 free_recipients(valid_to);
4599 valid_cc = validate_recipients(cc, NULL, 0);
4600 if (valid_cc->num_error > 0) {
4601 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4602 free_recipients(valid_to);
4603 free_recipients(valid_cc);
4607 valid_bcc = validate_recipients(bcc, NULL, 0);
4608 if (valid_bcc->num_error > 0) {
4609 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4610 free_recipients(valid_to);
4611 free_recipients(valid_cc);
4612 free_recipients(valid_bcc);
4616 /* Recipient required, but none were specified */
4617 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4618 free_recipients(valid_to);
4619 free_recipients(valid_cc);
4620 free_recipients(valid_bcc);
4621 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4625 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4626 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4627 cprintf("%d You do not have permission "
4628 "to send Internet mail.\n",
4629 ERROR + HIGHER_ACCESS_REQUIRED);
4630 free_recipients(valid_to);
4631 free_recipients(valid_cc);
4632 free_recipients(valid_bcc);
4637 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)
4638 && (CCC->user.axlevel < AxNetU) ) {
4639 cprintf("%d Higher access required for network mail.\n",
4640 ERROR + HIGHER_ACCESS_REQUIRED);
4641 free_recipients(valid_to);
4642 free_recipients(valid_cc);
4643 free_recipients(valid_bcc);
4647 if ((RESTRICT_INTERNET == 1)
4648 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4649 && ((CCC->user.flags & US_INTERNET) == 0)
4650 && (!CCC->internal_pgm)) {
4651 cprintf("%d You don't have access to Internet mail.\n",
4652 ERROR + HIGHER_ACCESS_REQUIRED);
4653 free_recipients(valid_to);
4654 free_recipients(valid_cc);
4655 free_recipients(valid_bcc);
4661 /* Is this a room which has anonymous-only or anonymous-option? */
4662 anonymous = MES_NORMAL;
4663 if (CCC->room.QRflags & QR_ANONONLY) {
4664 anonymous = MES_ANONONLY;
4666 if (CCC->room.QRflags & QR_ANONOPT) {
4667 if (anon_flag == 1) { /* only if the user requested it */
4668 anonymous = MES_ANONOPT;
4672 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4676 /* Recommend to the client that the use of a message subject is
4677 * strongly recommended in this room, if either the SUBJECTREQ flag
4678 * is set, or if there is one or more Internet email recipients.
4680 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4681 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4682 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4683 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4685 /* If we're only checking the validity of the request, return
4686 * success without creating the message.
4689 cprintf("%d %s|%d\n", CIT_OK,
4690 ((valid_to != NULL) ? valid_to->display_recp : ""),
4692 free_recipients(valid_to);
4693 free_recipients(valid_cc);
4694 free_recipients(valid_bcc);
4698 /* We don't need these anymore because we'll do it differently below */
4699 free_recipients(valid_to);
4700 free_recipients(valid_cc);
4701 free_recipients(valid_bcc);
4703 /* Read in the message from the client. */
4705 cprintf("%d send message\n", START_CHAT_MODE);
4707 cprintf("%d send message\n", SEND_LISTING);
4710 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4711 CCC->room.QRname, anonymous, format_type,
4712 newusername, newuseremail, subject,
4713 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4716 /* Put together one big recipients struct containing to/cc/bcc all in
4717 * one. This is for the envelope.
4719 char *all_recps = malloc(SIZ * 3);
4720 strcpy(all_recps, recp);
4721 if (!IsEmptyStr(cc)) {
4722 if (!IsEmptyStr(all_recps)) {
4723 strcat(all_recps, ",");
4725 strcat(all_recps, cc);
4727 if (!IsEmptyStr(bcc)) {
4728 if (!IsEmptyStr(all_recps)) {
4729 strcat(all_recps, ",");
4731 strcat(all_recps, bcc);
4733 if (!IsEmptyStr(all_recps)) {
4734 valid = validate_recipients(all_recps, NULL, 0);
4741 if ((valid != NULL) && (valid->num_room == 1))
4743 /* posting into an ML room? set the envelope from
4744 * to the actual mail address so others get a valid
4747 msg->cm_fields['V'] = strdup(valid->recp_orgroom);
4751 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4753 cprintf("%ld\n", msgnum);
4755 if (StrLength(CCC->StatusMessage) > 0) {
4756 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4758 else if (msgnum >= 0L) {
4759 client_write(HKEY("Message accepted.\n"));
4762 client_write(HKEY("Internal error.\n"));
4765 if (msg->cm_fields['E'] != NULL) {
4766 cprintf("%s\n", msg->cm_fields['E']);
4773 CtdlFreeMessage(msg);
4775 if (valid != NULL) {
4776 free_recipients(valid);
4784 * API function to delete messages which match a set of criteria
4785 * (returns the actual number of messages deleted)
4787 int CtdlDeleteMessages(char *room_name, /* which room */
4788 long *dmsgnums, /* array of msg numbers to be deleted */
4789 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4790 char *content_type /* or "" for any. regular expressions expected. */
4793 struct CitContext *CCC = CC;
4794 struct ctdlroom qrbuf;
4795 struct cdbdata *cdbfr;
4796 long *msglist = NULL;
4797 long *dellist = NULL;
4800 int num_deleted = 0;
4802 struct MetaData smi;
4805 int need_to_free_re = 0;
4807 if (content_type) if (!IsEmptyStr(content_type)) {
4808 regcomp(&re, content_type, 0);
4809 need_to_free_re = 1;
4811 MSG_syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4812 room_name, num_dmsgnums, content_type);
4814 /* get room record, obtaining a lock... */
4815 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4816 MSG_syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4818 if (need_to_free_re) regfree(&re);
4819 return (0); /* room not found */
4821 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4823 if (cdbfr != NULL) {
4824 dellist = malloc(cdbfr->len);
4825 msglist = (long *) cdbfr->ptr;
4826 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4827 num_msgs = cdbfr->len / sizeof(long);
4831 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4832 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4833 int have_more_del = 1;
4835 num_msgs = sort_msglist(msglist, num_msgs);
4836 if (num_dmsgnums > 1)
4837 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4840 StrBuf *dbg = NewStrBuf();
4841 for (i = 0; i < num_dmsgnums; i++)
4842 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4843 MSG_syslog(LOG_DEBUG, "Deleting before: %s", ChrPtr(dbg));
4848 while ((i < num_msgs) && (have_more_del)) {
4852 /* Set/clear a bit for each criterion */
4854 /* 0 messages in the list or a null list means that we are
4855 * interested in deleting any messages which meet the other criteria.
4858 delete_this |= 0x01;
4861 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
4862 if (msglist[i] == dmsgnums[j]) {
4863 delete_this |= 0x01;
4866 have_more_del = (j < num_dmsgnums);
4869 if (have_contenttype) {
4870 GetMetaData(&smi, msglist[i]);
4871 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4872 delete_this |= 0x02;
4875 delete_this |= 0x02;
4878 /* Delete message only if all bits are set */
4879 if (delete_this == 0x03) {
4880 dellist[num_deleted++] = msglist[i];
4887 StrBuf *dbg = NewStrBuf();
4888 for (i = 0; i < num_deleted; i++)
4889 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
4890 MSG_syslog(LOG_DEBUG, "Deleting: %s", ChrPtr(dbg));
4894 num_msgs = sort_msglist(msglist, num_msgs);
4895 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4896 msglist, (int)(num_msgs * sizeof(long)));
4899 qrbuf.QRhighest = msglist[num_msgs - 1];
4901 qrbuf.QRhighest = 0;
4903 CtdlPutRoomLock(&qrbuf);
4905 /* Go through the messages we pulled out of the index, and decrement
4906 * their reference counts by 1. If this is the only room the message
4907 * was in, the reference count will reach zero and the message will
4908 * automatically be deleted from the database. We do this in a
4909 * separate pass because there might be plug-in hooks getting called,
4910 * and we don't want that happening during an S_ROOMS critical
4914 for (i=0; i<num_deleted; ++i) {
4915 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4917 AdjRefCountList(dellist, num_deleted, -1);
4919 /* Now free the memory we used, and go away. */
4920 if (msglist != NULL) free(msglist);
4921 if (dellist != NULL) free(dellist);
4922 MSG_syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4923 if (need_to_free_re) regfree(&re);
4924 return (num_deleted);
4930 * Check whether the current user has permission to delete messages from
4931 * the current room (returns 1 for yes, 0 for no)
4933 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4935 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4936 if (ra & UA_DELETEALLOWED) return(1);
4944 * Delete message from current room
4946 void cmd_dele(char *args)
4955 extract_token(msgset, args, 0, '|', sizeof msgset);
4956 num_msgs = num_tokens(msgset, ',');
4958 cprintf("%d Nothing to do.\n", CIT_OK);
4962 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4963 cprintf("%d Higher access required.\n",
4964 ERROR + HIGHER_ACCESS_REQUIRED);
4969 * Build our message set to be moved/copied
4971 msgs = malloc(num_msgs * sizeof(long));
4972 for (i=0; i<num_msgs; ++i) {
4973 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4974 msgs[i] = atol(msgtok);
4977 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4981 cprintf("%d %d message%s deleted.\n", CIT_OK,
4982 num_deleted, ((num_deleted != 1) ? "s" : ""));
4984 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4992 * move or copy a message to another room
4994 void cmd_move(char *args)
5001 char targ[ROOMNAMELEN];
5002 struct ctdlroom qtemp;
5009 extract_token(msgset, args, 0, '|', sizeof msgset);
5010 num_msgs = num_tokens(msgset, ',');
5012 cprintf("%d Nothing to do.\n", CIT_OK);
5016 extract_token(targ, args, 1, '|', sizeof targ);
5017 convert_room_name_macros(targ, sizeof targ);
5018 targ[ROOMNAMELEN - 1] = 0;
5019 is_copy = extract_int(args, 2);
5021 if (CtdlGetRoom(&qtemp, targ) != 0) {
5022 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
5026 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
5027 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
5031 CtdlGetUser(&CC->user, CC->curr_user);
5032 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
5034 /* Check for permission to perform this operation.
5035 * Remember: "CC->room" is source, "qtemp" is target.
5039 /* Admins can move/copy */
5040 if (CC->user.axlevel >= AxAideU) permit = 1;
5042 /* Room aides can move/copy */
5043 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
5045 /* Permit move/copy from personal rooms */
5046 if ((CC->room.QRflags & QR_MAILBOX)
5047 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5049 /* Permit only copy from public to personal room */
5051 && (!(CC->room.QRflags & QR_MAILBOX))
5052 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5054 /* Permit message removal from collaborative delete rooms */
5055 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5057 /* Users allowed to post into the target room may move into it too. */
5058 if ((CC->room.QRflags & QR_MAILBOX) &&
5059 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
5061 /* User must have access to target room */
5062 if (!(ra & UA_KNOWN)) permit = 0;
5065 cprintf("%d Higher access required.\n",
5066 ERROR + HIGHER_ACCESS_REQUIRED);
5071 * Build our message set to be moved/copied
5073 msgs = malloc(num_msgs * sizeof(long));
5074 for (i=0; i<num_msgs; ++i) {
5075 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5076 msgs[i] = atol(msgtok);
5082 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5084 cprintf("%d Cannot store message(s) in %s: error %d\n",
5090 /* Now delete the message from the source room,
5091 * if this is a 'move' rather than a 'copy' operation.
5094 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5098 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5104 * GetMetaData() - Get the supplementary record for a message
5106 void GetMetaData(struct MetaData *smibuf, long msgnum)
5109 struct cdbdata *cdbsmi;
5112 memset(smibuf, 0, sizeof(struct MetaData));
5113 smibuf->meta_msgnum = msgnum;
5114 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5116 /* Use the negative of the message number for its supp record index */
5117 TheIndex = (0L - msgnum);
5119 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5120 if (cdbsmi == NULL) {
5121 return; /* record not found; go with defaults */
5123 memcpy(smibuf, cdbsmi->ptr,
5124 ((cdbsmi->len > sizeof(struct MetaData)) ?
5125 sizeof(struct MetaData) : cdbsmi->len));
5132 * PutMetaData() - (re)write supplementary record for a message
5134 void PutMetaData(struct MetaData *smibuf)
5138 /* Use the negative of the message number for the metadata db index */
5139 TheIndex = (0L - smibuf->meta_msgnum);
5141 cdb_store(CDB_MSGMAIN,
5142 &TheIndex, (int)sizeof(long),
5143 smibuf, (int)sizeof(struct MetaData));
5148 * AdjRefCount - submit an adjustment to the reference count for a message.
5149 * (These are just queued -- we actually process them later.)
5151 void AdjRefCount(long msgnum, int incr)
5153 struct CitContext *CCC = CC;
5154 struct arcq new_arcq;
5157 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5159 begin_critical_section(S_SUPPMSGMAIN);
5160 if (arcfp == NULL) {
5161 arcfp = fopen(file_arcq, "ab+");
5162 chown(file_arcq, CTDLUID, (-1));
5163 chmod(file_arcq, 0600);
5165 end_critical_section(S_SUPPMSGMAIN);
5167 /* msgnum < 0 means that we're trying to close the file */
5169 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5170 begin_critical_section(S_SUPPMSGMAIN);
5171 if (arcfp != NULL) {
5175 end_critical_section(S_SUPPMSGMAIN);
5180 * If we can't open the queue, perform the operation synchronously.
5182 if (arcfp == NULL) {
5183 TDAP_AdjRefCount(msgnum, incr);
5187 new_arcq.arcq_msgnum = msgnum;
5188 new_arcq.arcq_delta = incr;
5189 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5191 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5200 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5202 struct CitContext *CCC = CC;
5203 long i, the_size, offset;
5204 struct arcq *new_arcq;
5207 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5209 begin_critical_section(S_SUPPMSGMAIN);
5210 if (arcfp == NULL) {
5211 arcfp = fopen(file_arcq, "ab+");
5212 chown(file_arcq, CTDLUID, (-1));
5213 chmod(file_arcq, 0600);
5215 end_critical_section(S_SUPPMSGMAIN);
5218 * If we can't open the queue, perform the operation synchronously.
5220 if (arcfp == NULL) {
5221 for (i = 0; i < nmsg; i++)
5222 TDAP_AdjRefCount(msgnum[i], incr);
5226 the_size = sizeof(struct arcq) * nmsg;
5227 new_arcq = malloc(the_size);
5228 for (i = 0; i < nmsg; i++) {
5229 new_arcq[i].arcq_msgnum = msgnum[i];
5230 new_arcq[i].arcq_delta = incr;
5234 while ((rv >= 0) && (offset < the_size))
5236 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5238 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5254 * TDAP_ProcessAdjRefCountQueue()
5256 * Process the queue of message count adjustments that was created by calls
5257 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5258 * for each one. This should be an "off hours" operation.
5260 int TDAP_ProcessAdjRefCountQueue(void)
5262 struct CitContext *CCC = CC;
5263 char file_arcq_temp[PATH_MAX];
5266 struct arcq arcq_rec;
5267 int num_records_processed = 0;
5269 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5271 begin_critical_section(S_SUPPMSGMAIN);
5272 if (arcfp != NULL) {
5277 r = link(file_arcq, file_arcq_temp);
5279 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5280 end_critical_section(S_SUPPMSGMAIN);
5281 return(num_records_processed);
5285 end_critical_section(S_SUPPMSGMAIN);
5287 fp = fopen(file_arcq_temp, "rb");
5289 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5290 return(num_records_processed);
5293 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5294 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5295 ++num_records_processed;
5299 r = unlink(file_arcq_temp);
5301 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5304 return(num_records_processed);
5310 * TDAP_AdjRefCount - adjust the reference count for a message.
5311 * This one does it "for real" because it's called by
5312 * the autopurger function that processes the queue
5313 * created by AdjRefCount(). If a message's reference
5314 * count becomes zero, we also delete the message from
5315 * disk and de-index it.
5317 void TDAP_AdjRefCount(long msgnum, int incr)
5319 struct CitContext *CCC = CC;
5321 struct MetaData smi;
5324 /* This is a *tight* critical section; please keep it that way, as
5325 * it may get called while nested in other critical sections.
5326 * Complicating this any further will surely cause deadlock!
5328 begin_critical_section(S_SUPPMSGMAIN);
5329 GetMetaData(&smi, msgnum);
5330 smi.meta_refcount += incr;
5332 end_critical_section(S_SUPPMSGMAIN);
5333 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5334 msgnum, incr, smi.meta_refcount
5337 /* If the reference count is now zero, delete the message
5338 * (and its supplementary record as well).
5340 if (smi.meta_refcount == 0) {
5341 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5343 /* Call delete hooks with NULL room to show it has gone altogether */
5344 PerformDeleteHooks(NULL, msgnum);
5346 /* Remove from message base */
5348 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5349 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5351 /* Remove metadata record */
5352 delnum = (0L - msgnum);
5353 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5359 * Write a generic object to this room
5361 * Note: this could be much more efficient. Right now we use two temporary
5362 * files, and still pull the message into memory as with all others.
5364 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5365 char *content_type, /* MIME type of this object */
5366 char *raw_message, /* Data to be written */
5367 off_t raw_length, /* Size of raw_message */
5368 struct ctdluser *is_mailbox, /* Mailbox room? */
5369 int is_binary, /* Is encoding necessary? */
5370 int is_unique, /* Del others of this type? */
5371 unsigned int flags /* Internal save flags */
5374 struct CitContext *CCC = CC;
5375 struct ctdlroom qrbuf;
5376 char roomname[ROOMNAMELEN];
5377 struct CtdlMessage *msg;
5378 char *encoded_message = NULL;
5380 if (is_mailbox != NULL) {
5381 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5384 safestrncpy(roomname, req_room, sizeof(roomname));
5387 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5390 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5393 encoded_message = malloc((size_t)(raw_length + 4096));
5396 sprintf(encoded_message, "Content-type: %s\n", content_type);
5399 sprintf(&encoded_message[strlen(encoded_message)],
5400 "Content-transfer-encoding: base64\n\n"
5404 sprintf(&encoded_message[strlen(encoded_message)],
5405 "Content-transfer-encoding: 7bit\n\n"
5411 &encoded_message[strlen(encoded_message)],
5419 &encoded_message[strlen(encoded_message)],
5425 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5426 msg = malloc(sizeof(struct CtdlMessage));
5427 memset(msg, 0, sizeof(struct CtdlMessage));
5428 msg->cm_magic = CTDLMESSAGE_MAGIC;
5429 msg->cm_anon_type = MES_NORMAL;
5430 msg->cm_format_type = 4;
5431 msg->cm_fields['A'] = strdup(CCC->user.fullname);
5432 msg->cm_fields['O'] = strdup(req_room);
5433 msg->cm_fields['N'] = strdup(config.c_nodename);
5434 msg->cm_fields['H'] = strdup(config.c_humannode);
5435 msg->cm_flags = flags;
5437 msg->cm_fields['M'] = encoded_message;
5439 /* Create the requested room if we have to. */
5440 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5441 CtdlCreateRoom(roomname,
5442 ( (is_mailbox != NULL) ? 5 : 3 ),
5443 "", 0, 1, 0, VIEW_BBS);
5445 /* If the caller specified this object as unique, delete all
5446 * other objects of this type that are currently in the room.
5449 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5450 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5453 /* Now write the data */
5454 CtdlSubmitMsg(msg, NULL, roomname, 0);
5455 CtdlFreeMessage(msg);
5463 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5464 config_msgnum = msgnum;
5468 char *CtdlGetSysConfig(char *sysconfname) {
5469 char hold_rm[ROOMNAMELEN];
5472 struct CtdlMessage *msg;
5475 strcpy(hold_rm, CC->room.QRname);
5476 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5477 CtdlGetRoom(&CC->room, hold_rm);
5482 /* We want the last (and probably only) config in this room */
5483 begin_critical_section(S_CONFIG);
5484 config_msgnum = (-1L);
5485 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5486 CtdlGetSysConfigBackend, NULL);
5487 msgnum = config_msgnum;
5488 end_critical_section(S_CONFIG);
5494 msg = CtdlFetchMessage(msgnum, 1);
5496 conf = strdup(msg->cm_fields['M']);
5497 CtdlFreeMessage(msg);
5504 CtdlGetRoom(&CC->room, hold_rm);
5506 if (conf != NULL) do {
5507 extract_token(buf, conf, 0, '\n', sizeof buf);
5508 strcpy(conf, &conf[strlen(buf)+1]);
5509 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5515 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5516 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5521 * Determine whether a given Internet address belongs to the current user
5523 int CtdlIsMe(char *addr, int addr_buf_len)
5525 struct recptypes *recp;
5528 recp = validate_recipients(addr, NULL, 0);
5529 if (recp == NULL) return(0);
5531 if (recp->num_local == 0) {
5532 free_recipients(recp);
5536 for (i=0; i<recp->num_local; ++i) {
5537 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5538 if (!strcasecmp(addr, CC->user.fullname)) {
5539 free_recipients(recp);
5544 free_recipients(recp);
5550 * Citadel protocol command to do the same
5552 void cmd_isme(char *argbuf) {
5555 if (CtdlAccessCheck(ac_logged_in)) return;
5556 extract_token(addr, argbuf, 0, '|', sizeof addr);
5558 if (CtdlIsMe(addr, sizeof addr)) {
5559 cprintf("%d %s\n", CIT_OK, addr);
5562 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5568 /*****************************************************************************/
5569 /* MODULE INITIALIZATION STUFF */
5570 /*****************************************************************************/
5571 void SetMessageDebugEnabled(const int n)
5573 MessageDebugEnabled = n;
5575 CTDL_MODULE_INIT(msgbase)
5578 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5580 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5581 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5582 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5583 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5584 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5585 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5586 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5587 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5588 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5589 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5590 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5591 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5594 /* return our Subversion id for the Log */