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, CCC->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) {
1216 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1219 mptr = dmsgtext->ptr;
1220 upper_bound = mptr + dmsgtext->len;
1222 /* Parse the three bytes that begin EVERY message on disk.
1223 * The first is always 0xFF, the on-disk magic number.
1224 * The second is the anonymous/public type byte.
1225 * The third is the format type byte (vari, fixed, or MIME).
1229 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1233 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1234 memset(ret, 0, sizeof(struct CtdlMessage));
1236 ret->cm_magic = CTDLMESSAGE_MAGIC;
1237 ret->cm_anon_type = *mptr++; /* Anon type byte */
1238 ret->cm_format_type = *mptr++; /* Format type byte */
1241 * The rest is zero or more arbitrary fields. Load them in.
1242 * We're done when we encounter either a zero-length field or
1243 * have just processed the 'M' (message text) field.
1246 if (mptr >= upper_bound) {
1249 field_header = *mptr++;
1250 ret->cm_fields[field_header] = strdup(mptr);
1252 while (*mptr++ != 0); /* advance to next field */
1254 } while ((mptr < upper_bound) && (field_header != 'M'));
1258 /* Always make sure there's something in the msg text field. If
1259 * it's NULL, the message text is most likely stored separately,
1260 * so go ahead and fetch that. Failing that, just set a dummy
1261 * body so other code doesn't barf.
1263 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1264 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1265 if (dmsgtext != NULL) {
1266 ret->cm_fields['M'] = dmsgtext->ptr;
1267 dmsgtext->ptr = NULL;
1271 if (ret->cm_fields['M'] == NULL) {
1272 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1275 /* Perform "before read" hooks (aborting if any return nonzero) */
1276 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1277 CtdlFreeMessage(ret);
1286 * Returns 1 if the supplied pointer points to a valid Citadel message.
1287 * If the pointer is NULL or the magic number check fails, returns 0.
1289 int is_valid_message(struct CtdlMessage *msg) {
1292 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1293 struct CitContext *CCC = CC;
1294 MSGM_syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1300 void CtdlFreeMessageContents(struct CtdlMessage *msg)
1304 for (i = 0; i < 256; ++i)
1305 if (msg->cm_fields[i] != NULL) {
1306 free(msg->cm_fields[i]);
1309 msg->cm_magic = 0; /* just in case */
1312 * 'Destructor' for struct CtdlMessage
1314 void CtdlFreeMessage(struct CtdlMessage *msg)
1316 if (is_valid_message(msg) == 0)
1318 if (msg != NULL) free (msg);
1321 CtdlFreeMessageContents(msg);
1325 int DupCMField(int i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
1328 len = strlen(OrgMsg->cm_fields[i]);
1329 NewMsg->cm_fields[i] = malloc(len + 1);
1330 if (NewMsg->cm_fields[i] == NULL)
1332 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
1333 NewMsg->cm_fields[i][len] = '\0';
1337 struct CtdlMessage * CtdlDuplicateMessage(struct CtdlMessage *OrgMsg)
1340 struct CtdlMessage *NewMsg;
1342 if (is_valid_message(OrgMsg) == 0)
1344 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
1348 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
1350 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
1352 for (i = 0; i < 256; ++i)
1354 if (OrgMsg->cm_fields[i] != NULL)
1356 if (!DupCMField(i, OrgMsg, NewMsg))
1358 CtdlFreeMessage(NewMsg);
1370 * Pre callback function for multipart/alternative
1372 * NOTE: this differs from the standard behavior for a reason. Normally when
1373 * displaying multipart/alternative you want to show the _last_ usable
1374 * format in the message. Here we show the _first_ one, because it's
1375 * usually text/plain. Since this set of functions is designed for text
1376 * output to non-MIME-aware clients, this is the desired behavior.
1379 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1380 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1381 char *cbid, void *cbuserdata)
1383 struct CitContext *CCC = CC;
1386 ma = (struct ma_info *)cbuserdata;
1387 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1388 if (!strcasecmp(cbtype, "multipart/alternative")) {
1392 if (!strcasecmp(cbtype, "message/rfc822")) {
1398 * Post callback function for multipart/alternative
1400 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1401 void *content, char *cbtype, char *cbcharset, size_t length,
1402 char *encoding, char *cbid, void *cbuserdata)
1404 struct CitContext *CCC = CC;
1407 ma = (struct ma_info *)cbuserdata;
1408 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1409 if (!strcasecmp(cbtype, "multipart/alternative")) {
1413 if (!strcasecmp(cbtype, "message/rfc822")) {
1419 * Inline callback function for mime parser that wants to display text
1421 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1422 void *content, char *cbtype, char *cbcharset, size_t length,
1423 char *encoding, char *cbid, void *cbuserdata)
1425 struct CitContext *CCC = CC;
1431 ma = (struct ma_info *)cbuserdata;
1433 MSG_syslog(LOG_DEBUG,
1434 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1435 partnum, filename, cbtype, (long)length);
1438 * If we're in the middle of a multipart/alternative scope and
1439 * we've already printed another section, skip this one.
1441 if ( (ma->is_ma) && (ma->did_print) ) {
1442 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1447 if ( (!strcasecmp(cbtype, "text/plain"))
1448 || (IsEmptyStr(cbtype)) ) {
1451 client_write(wptr, length);
1452 if (wptr[length-1] != '\n') {
1459 if (!strcasecmp(cbtype, "text/html")) {
1460 ptr = html_to_ascii(content, length, 80, 0);
1462 client_write(ptr, wlen);
1463 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1470 if (ma->use_fo_hooks) {
1471 if (PerformFixedOutputHooks(cbtype, content, length)) {
1472 /* above function returns nonzero if it handled the part */
1477 if (strncasecmp(cbtype, "multipart/", 10)) {
1478 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1479 partnum, filename, cbtype, (long)length);
1485 * The client is elegant and sophisticated and wants to be choosy about
1486 * MIME content types, so figure out which multipart/alternative part
1487 * we're going to send.
1489 * We use a system of weights. When we find a part that matches one of the
1490 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1491 * and then set ma->chosen_pref to that MIME type's position in our preference
1492 * list. If we then hit another match, we only replace the first match if
1493 * the preference value is lower.
1495 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1496 void *content, char *cbtype, char *cbcharset, size_t length,
1497 char *encoding, char *cbid, void *cbuserdata)
1499 struct CitContext *CCC = CC;
1504 ma = (struct ma_info *)cbuserdata;
1506 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1507 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1508 // I don't know if there are any side effects! Please TEST TEST TEST
1509 //if (ma->is_ma > 0) {
1511 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1512 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1513 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1514 if (i < ma->chosen_pref) {
1515 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1516 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1517 ma->chosen_pref = i;
1524 * Now that we've chosen our preferred part, output it.
1526 void output_preferred(char *name,
1538 struct CitContext *CCC = CC;
1541 int add_newline = 0;
1544 char *decoded = NULL;
1545 size_t bytes_decoded;
1548 ma = (struct ma_info *)cbuserdata;
1550 /* This is not the MIME part you're looking for... */
1551 if (strcasecmp(partnum, ma->chosen_part)) return;
1553 /* If the content-type of this part is in our preferred formats
1554 * list, we can simply output it verbatim.
1556 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1557 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1558 if (!strcasecmp(buf, cbtype)) {
1559 /* Yeah! Go! W00t!! */
1560 if (ma->dont_decode == 0)
1561 rc = mime_decode_now (content,
1567 break; /* Give us the chance, maybe theres another one. */
1569 if (rc == 0) text_content = (char *)content;
1571 text_content = decoded;
1572 length = bytes_decoded;
1575 if (text_content[length-1] != '\n') {
1578 cprintf("Content-type: %s", cbtype);
1579 if (!IsEmptyStr(cbcharset)) {
1580 cprintf("; charset=%s", cbcharset);
1582 cprintf("\nContent-length: %d\n",
1583 (int)(length + add_newline) );
1584 if (!IsEmptyStr(encoding)) {
1585 cprintf("Content-transfer-encoding: %s\n", encoding);
1588 cprintf("Content-transfer-encoding: 7bit\n");
1590 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1592 if (client_write(text_content, length) == -1)
1594 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1597 if (add_newline) cprintf("\n");
1598 if (decoded != NULL) free(decoded);
1603 /* No translations required or possible: output as text/plain */
1604 cprintf("Content-type: text/plain\n\n");
1606 if (ma->dont_decode == 0)
1607 rc = mime_decode_now (content,
1613 return; /* Give us the chance, maybe theres another one. */
1615 if (rc == 0) text_content = (char *)content;
1617 text_content = decoded;
1618 length = bytes_decoded;
1621 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1622 length, encoding, cbid, cbuserdata);
1623 if (decoded != NULL) free(decoded);
1628 char desired_section[64];
1635 * Callback function for
1637 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1638 void *content, char *cbtype, char *cbcharset, size_t length,
1639 char *encoding, char *cbid, void *cbuserdata)
1641 struct encapmsg *encap;
1643 encap = (struct encapmsg *)cbuserdata;
1645 /* Only proceed if this is the desired section... */
1646 if (!strcasecmp(encap->desired_section, partnum)) {
1647 encap->msglen = length;
1648 encap->msg = malloc(length + 2);
1649 memcpy(encap->msg, content, length);
1656 * Determine whether the specified message exists in the cached_msglist
1657 * (This is a security check)
1659 int check_cached_msglist(long msgnum) {
1660 struct CitContext *CCC = CC;
1662 /* cases in which we skip the check */
1663 if (!CCC) return om_ok; /* not a session */
1664 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1665 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1666 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1669 /* Do a binary search within the cached_msglist for the requested msgnum */
1671 int max = (CC->cached_num_msgs - 1);
1673 while (max >= min) {
1674 int middle = min + (max-min) / 2 ;
1675 if (msgnum == CCC->cached_msglist[middle]) {
1678 if (msgnum > CC->cached_msglist[middle]) {
1686 return om_access_denied;
1691 * Determine whether the currently logged in session has permission to read
1692 * messages in the current room.
1694 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1695 if ( (!(CC->logged_in))
1696 && (!(CC->internal_pgm))
1697 && (!config.c_guest_logins)
1699 return(om_not_logged_in);
1706 * Get a message off disk. (returns om_* values found in msgbase.h)
1709 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1710 int mode, /* how would you like that message? */
1711 int headers_only, /* eschew the message body? */
1712 int do_proto, /* do Citadel protocol responses? */
1713 int crlf, /* Use CRLF newlines instead of LF? */
1714 char *section, /* NULL or a message/rfc822 section */
1715 int flags, /* various flags; see msgbase.h */
1719 struct CitContext *CCC = CC;
1720 struct CtdlMessage *TheMessage = NULL;
1721 int retcode = CIT_OK;
1722 struct encapmsg encap;
1725 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1727 (section ? section : "<>")
1730 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1733 if (r == om_not_logged_in) {
1734 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1737 cprintf("%d An unknown error has occurred.\n", ERROR);
1744 * Check to make sure the message is actually IN this room
1746 r = check_cached_msglist(msg_num);
1747 if (r == om_access_denied) {
1748 /* Not in the cache? We get ONE shot to check it again. */
1749 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1750 r = check_cached_msglist(msg_num);
1753 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1754 msg_num, CCC->room.QRname
1757 if (r == om_access_denied) {
1758 cprintf("%d message %ld was not found in this room\n",
1759 ERROR + HIGHER_ACCESS_REQUIRED,
1768 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1769 * request that we don't even bother loading the body into memory.
1771 if (headers_only == HEADERS_FAST) {
1772 TheMessage = CtdlFetchMessage(msg_num, 0);
1775 TheMessage = CtdlFetchMessage(msg_num, 1);
1778 if (TheMessage == NULL) {
1779 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1780 ERROR + MESSAGE_NOT_FOUND, msg_num);
1781 return(om_no_such_msg);
1784 /* Here is the weird form of this command, to process only an
1785 * encapsulated message/rfc822 section.
1787 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1788 memset(&encap, 0, sizeof encap);
1789 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1790 mime_parser(TheMessage->cm_fields['M'],
1792 *extract_encapsulated_message,
1793 NULL, NULL, (void *)&encap, 0
1796 if ((Author != NULL) && (*Author == NULL))
1798 *Author = TheMessage->cm_fields['A'];
1799 TheMessage->cm_fields['A'] = NULL;
1801 if ((Address != NULL) && (*Address == NULL))
1803 *Address = TheMessage->cm_fields['F'];
1804 TheMessage->cm_fields['F'] = NULL;
1806 CtdlFreeMessage(TheMessage);
1810 encap.msg[encap.msglen] = 0;
1811 TheMessage = convert_internet_message(encap.msg);
1812 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1814 /* Now we let it fall through to the bottom of this
1815 * function, because TheMessage now contains the
1816 * encapsulated message instead of the top-level
1817 * message. Isn't that neat?
1823 cprintf("%d msg %ld has no part %s\n",
1824 ERROR + MESSAGE_NOT_FOUND,
1828 retcode = om_no_such_msg;
1833 /* Ok, output the message now */
1834 if (retcode == CIT_OK)
1835 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1836 if ((Author != NULL) && (*Author == NULL))
1838 *Author = TheMessage->cm_fields['A'];
1839 TheMessage->cm_fields['A'] = NULL;
1841 if ((Address != NULL) && (*Address == NULL))
1843 *Address = TheMessage->cm_fields['F'];
1844 TheMessage->cm_fields['F'] = NULL;
1847 CtdlFreeMessage(TheMessage);
1853 char *qp_encode_email_addrs(char *source)
1855 struct CitContext *CCC = CC;
1856 char *user, *node, *name;
1857 const char headerStr[] = "=?UTF-8?Q?";
1861 int need_to_encode = 0;
1867 long nAddrPtrMax = 50;
1872 if (source == NULL) return source;
1873 if (IsEmptyStr(source)) return source;
1874 if (MessageDebugEnabled != 0) cit_backtrace();
1875 MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1877 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1878 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1879 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1882 while (!IsEmptyStr (&source[i])) {
1883 if (nColons >= nAddrPtrMax){
1886 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1887 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1888 free (AddrPtr), AddrPtr = ptr;
1890 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1891 memset(&ptr[nAddrPtrMax], 0,
1892 sizeof (long) * nAddrPtrMax);
1894 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1895 free (AddrUtf8), AddrUtf8 = ptr;
1898 if (((unsigned char) source[i] < 32) ||
1899 ((unsigned char) source[i] > 126)) {
1901 AddrUtf8[nColons] = 1;
1903 if (source[i] == '"')
1904 InQuotes = !InQuotes;
1905 if (!InQuotes && source[i] == ',') {
1906 AddrPtr[nColons] = i;
1911 if (need_to_encode == 0) {
1918 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1919 Encoded = (char*) malloc (EncodedMaxLen);
1921 for (i = 0; i < nColons; i++)
1922 source[AddrPtr[i]++] = '\0';
1923 /* TODO: if libidn, this might get larger*/
1924 user = malloc(SourceLen + 1);
1925 node = malloc(SourceLen + 1);
1926 name = malloc(SourceLen + 1);
1930 for (i = 0; i < nColons && nPtr != NULL; i++) {
1931 nmax = EncodedMaxLen - (nPtr - Encoded);
1933 process_rfc822_addr(&source[AddrPtr[i]],
1937 /* TODO: libIDN here ! */
1938 if (IsEmptyStr(name)) {
1939 n = snprintf(nPtr, nmax,
1940 (i==0)?"%s@%s" : ",%s@%s",
1944 EncodedName = rfc2047encode(name, strlen(name));
1945 n = snprintf(nPtr, nmax,
1946 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1947 EncodedName, user, node);
1952 n = snprintf(nPtr, nmax,
1953 (i==0)?"%s" : ",%s",
1954 &source[AddrPtr[i]]);
1960 ptr = (char*) malloc(EncodedMaxLen * 2);
1961 memcpy(ptr, Encoded, EncodedMaxLen);
1962 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1963 free(Encoded), Encoded = ptr;
1965 i--; /* do it once more with properly lengthened buffer */
1968 for (i = 0; i < nColons; i++)
1969 source[--AddrPtr[i]] = ',';
1980 /* If the last item in a list of recipients was truncated to a partial address,
1981 * remove it completely in order to avoid choking libSieve
1983 void sanitize_truncated_recipient(char *str)
1986 if (num_tokens(str, ',') < 2) return;
1988 int len = strlen(str);
1989 if (len < 900) return;
1990 if (len > 998) str[998] = 0;
1992 char *cptr = strrchr(str, ',');
1995 char *lptr = strchr(cptr, '<');
1996 char *rptr = strchr(cptr, '>');
1998 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
2004 void OutputCtdlMsgHeaders(
2005 struct CtdlMessage *TheMessage,
2006 int do_proto) /* do Citadel protocol responses? */
2012 char display_name[256];
2014 /* begin header processing loop for Citadel message format */
2015 safestrncpy(display_name, "<unknown>", sizeof display_name);
2016 if (TheMessage->cm_fields['A']) {
2017 strcpy(buf, TheMessage->cm_fields['A']);
2018 if (TheMessage->cm_anon_type == MES_ANONONLY) {
2019 safestrncpy(display_name, "****", sizeof display_name);
2021 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
2022 safestrncpy(display_name, "anonymous", sizeof display_name);
2025 safestrncpy(display_name, buf, sizeof display_name);
2027 if ((is_room_aide())
2028 && ((TheMessage->cm_anon_type == MES_ANONONLY)
2029 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
2030 size_t tmp = strlen(display_name);
2031 snprintf(&display_name[tmp],
2032 sizeof display_name - tmp,
2037 /* Don't show Internet address for users on the
2038 * local Citadel network.
2041 if (TheMessage->cm_fields['N'] != NULL)
2042 if (!IsEmptyStr(TheMessage->cm_fields['N']))
2043 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
2047 /* Now spew the header fields in the order we like them. */
2048 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
2049 for (i=0; i<n; ++i) {
2050 k = (int) allkeys[i];
2052 if ( (TheMessage->cm_fields[k] != NULL)
2053 && (msgkeys[k] != NULL) ) {
2054 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
2055 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
2058 if (do_proto) cprintf("%s=%s\n",
2062 else if ((k == 'F') && (suppress_f)) {
2065 /* Masquerade display name if needed */
2067 if (do_proto) cprintf("%s=%s\n",
2069 TheMessage->cm_fields[k]
2078 void OutputRFC822MsgHeaders(
2079 struct CtdlMessage *TheMessage,
2080 int flags, /* should the bessage be exported clean */
2082 char *mid, long sizeof_mid,
2083 char *suser, long sizeof_suser,
2084 char *luser, long sizeof_luser,
2085 char *fuser, long sizeof_fuser,
2086 char *snode, long sizeof_snode)
2088 char datestamp[100];
2089 int subject_found = 0;
2096 for (i = 0; i < 256; ++i) {
2097 if (TheMessage->cm_fields[i]) {
2098 mptr = mpptr = TheMessage->cm_fields[i];
2101 safestrncpy(luser, mptr, sizeof_luser);
2102 safestrncpy(suser, mptr, sizeof_suser);
2104 else if (i == 'Y') {
2105 if ((flags & QP_EADDR) != 0) {
2106 mptr = qp_encode_email_addrs(mptr);
2108 sanitize_truncated_recipient(mptr);
2109 cprintf("CC: %s%s", mptr, nl);
2111 else if (i == 'P') {
2112 cprintf("Return-Path: %s%s", mptr, nl);
2114 else if (i == 'L') {
2115 cprintf("List-ID: %s%s", mptr, nl);
2117 else if (i == 'V') {
2118 if ((flags & QP_EADDR) != 0)
2119 mptr = qp_encode_email_addrs(mptr);
2121 while ((*hptr != '\0') && isspace(*hptr))
2123 if (!IsEmptyStr(hptr))
2124 cprintf("Envelope-To: %s%s", hptr, nl);
2126 else if (i == 'U') {
2127 cprintf("Subject: %s%s", mptr, nl);
2131 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2133 safestrncpy(fuser, mptr, sizeof_fuser);
2134 /* else if (i == 'O')
2135 cprintf("X-Citadel-Room: %s%s",
2138 safestrncpy(snode, mptr, sizeof_snode);
2141 if (haschar(mptr, '@') == 0)
2143 sanitize_truncated_recipient(mptr);
2144 cprintf("To: %s@%s", mptr, config.c_fqdn);
2149 if ((flags & QP_EADDR) != 0) {
2150 mptr = qp_encode_email_addrs(mptr);
2152 sanitize_truncated_recipient(mptr);
2153 cprintf("To: %s", mptr);
2157 else if (i == 'T') {
2158 datestring(datestamp, sizeof datestamp,
2159 atol(mptr), DATESTRING_RFC822);
2160 cprintf("Date: %s%s", datestamp, nl);
2162 else if (i == 'W') {
2163 cprintf("References: ");
2164 k = num_tokens(mptr, '|');
2165 for (j=0; j<k; ++j) {
2166 extract_token(buf, mptr, j, '|', sizeof buf);
2167 cprintf("<%s>", buf);
2176 else if (i == 'K') {
2178 while ((*hptr != '\0') && isspace(*hptr))
2180 if (!IsEmptyStr(hptr))
2181 cprintf("Reply-To: %s%s", mptr, nl);
2187 if (subject_found == 0) {
2188 cprintf("Subject: (no subject)%s", nl);
2193 void Dump_RFC822HeadersBody(
2194 struct CtdlMessage *TheMessage,
2195 int headers_only, /* eschew the message body? */
2196 int flags, /* should the bessage be exported clean? */
2200 cit_uint8_t prev_ch;
2202 const char *StartOfText = StrBufNOTNULL;
2205 int nllen = strlen(nl);
2208 mptr = TheMessage->cm_fields['M'];
2212 while (*mptr != '\0') {
2213 if (*mptr == '\r') {
2220 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2222 eoh = *(mptr+1) == '\n';
2226 StartOfText = strchr(StartOfText, '\n');
2227 StartOfText = strchr(StartOfText, '\n');
2230 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2231 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2232 ((headers_only != HEADERS_NONE) &&
2233 (headers_only != HEADERS_ONLY))
2235 if (*mptr == '\n') {
2236 memcpy(&outbuf[outlen], nl, nllen);
2238 outbuf[outlen] = '\0';
2241 outbuf[outlen++] = *mptr;
2245 if (flags & ESC_DOT)
2247 if ((prev_ch == '\n') &&
2249 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2251 outbuf[outlen++] = '.';
2256 if (outlen > 1000) {
2257 if (client_write(outbuf, outlen) == -1)
2259 struct CitContext *CCC = CC;
2260 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2267 client_write(outbuf, outlen);
2273 /* If the format type on disk is 1 (fixed-format), then we want
2274 * everything to be output completely literally ... regardless of
2275 * what message transfer format is in use.
2277 void DumpFormatFixed(
2278 struct CtdlMessage *TheMessage,
2279 int mode, /* how would you like that message? */
2286 int nllen = strlen (nl);
2289 mptr = TheMessage->cm_fields['M'];
2291 if (mode == MT_MIME) {
2292 cprintf("Content-type: text/plain\n\n");
2296 while (ch = *mptr++, ch > 0) {
2300 if ((buflen > 250) && (!xlline)){
2304 while ((buflen > 0) &&
2305 (!isspace(buf[buflen])))
2311 mptr -= tbuflen - buflen;
2316 /* if we reach the outer bounds of our buffer,
2317 abort without respect what whe purge. */
2320 (buflen > SIZ - nllen - 2)))
2324 memcpy (&buf[buflen], nl, nllen);
2328 if (client_write(buf, buflen) == -1)
2330 struct CitContext *CCC = CC;
2331 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2343 if (!IsEmptyStr(buf))
2344 cprintf("%s%s", buf, nl);
2348 * Get a message off disk. (returns om_* values found in msgbase.h)
2350 int CtdlOutputPreLoadedMsg(
2351 struct CtdlMessage *TheMessage,
2352 int mode, /* how would you like that message? */
2353 int headers_only, /* eschew the message body? */
2354 int do_proto, /* do Citadel protocol responses? */
2355 int crlf, /* Use CRLF newlines instead of LF? */
2356 int flags /* should the bessage be exported clean? */
2358 struct CitContext *CCC = CC;
2361 const char *nl; /* newline string */
2364 /* Buffers needed for RFC822 translation. These are all filled
2365 * using functions that are bounds-checked, and therefore we can
2366 * make them substantially smaller than SIZ.
2374 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2375 ((TheMessage == NULL) ? "NULL" : "not null"),
2376 mode, headers_only, do_proto, crlf);
2378 strcpy(mid, "unknown");
2379 nl = (crlf ? "\r\n" : "\n");
2381 if (!is_valid_message(TheMessage)) {
2382 MSGM_syslog(LOG_ERR,
2383 "ERROR: invalid preloaded message for output\n");
2385 return(om_no_such_msg);
2388 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2389 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2391 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2392 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2395 /* Are we downloading a MIME component? */
2396 if (mode == MT_DOWNLOAD) {
2397 if (TheMessage->cm_format_type != FMT_RFC822) {
2399 cprintf("%d This is not a MIME message.\n",
2400 ERROR + ILLEGAL_VALUE);
2401 } else if (CCC->download_fp != NULL) {
2402 if (do_proto) cprintf(
2403 "%d You already have a download open.\n",
2404 ERROR + RESOURCE_BUSY);
2406 /* Parse the message text component */
2407 mptr = TheMessage->cm_fields['M'];
2408 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2409 /* If there's no file open by this time, the requested
2410 * section wasn't found, so print an error
2412 if (CCC->download_fp == NULL) {
2413 if (do_proto) cprintf(
2414 "%d Section %s not found.\n",
2415 ERROR + FILE_NOT_FOUND,
2416 CCC->download_desired_section);
2419 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2422 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2423 * in a single server operation instead of opening a download file.
2425 if (mode == MT_SPEW_SECTION) {
2426 if (TheMessage->cm_format_type != FMT_RFC822) {
2428 cprintf("%d This is not a MIME message.\n",
2429 ERROR + ILLEGAL_VALUE);
2431 /* Parse the message text component */
2434 mptr = TheMessage->cm_fields['M'];
2435 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2436 /* If section wasn't found, print an error
2439 if (do_proto) cprintf(
2440 "%d Section %s not found.\n",
2441 ERROR + FILE_NOT_FOUND,
2442 CCC->download_desired_section);
2445 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2448 /* now for the user-mode message reading loops */
2449 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2451 /* Does the caller want to skip the headers? */
2452 if (headers_only == HEADERS_NONE) goto START_TEXT;
2454 /* Tell the client which format type we're using. */
2455 if ( (mode == MT_CITADEL) && (do_proto) ) {
2456 cprintf("type=%d\n", TheMessage->cm_format_type);
2459 /* nhdr=yes means that we're only displaying headers, no body */
2460 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2461 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2464 cprintf("nhdr=yes\n");
2467 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2468 OutputCtdlMsgHeaders(TheMessage, do_proto);
2471 /* begin header processing loop for RFC822 transfer format */
2475 strcpy(snode, NODENAME);
2476 if (mode == MT_RFC822)
2477 OutputRFC822MsgHeaders(
2482 suser, sizeof(suser),
2483 luser, sizeof(luser),
2484 fuser, sizeof(fuser),
2485 snode, sizeof(snode)
2489 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2490 suser[i] = tolower(suser[i]);
2491 if (!isalnum(suser[i])) suser[i]='_';
2494 if (mode == MT_RFC822) {
2495 if (!strcasecmp(snode, NODENAME)) {
2496 safestrncpy(snode, FQDN, sizeof snode);
2499 /* Construct a fun message id */
2500 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2501 if (strchr(mid, '@')==NULL) {
2502 cprintf("@%s", snode);
2506 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2507 cprintf("From: \"----\" <x@x.org>%s", nl);
2509 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2510 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2512 else if (!IsEmptyStr(fuser)) {
2513 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2516 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2519 /* Blank line signifying RFC822 end-of-headers */
2520 if (TheMessage->cm_format_type != FMT_RFC822) {
2525 /* end header processing loop ... at this point, we're in the text */
2527 if (headers_only == HEADERS_FAST) goto DONE;
2529 /* Tell the client about the MIME parts in this message */
2530 if (TheMessage->cm_format_type == FMT_RFC822) {
2531 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2532 mptr = TheMessage->cm_fields['M'];
2533 memset(&ma, 0, sizeof(struct ma_info));
2534 mime_parser(mptr, NULL,
2535 (do_proto ? *list_this_part : NULL),
2536 (do_proto ? *list_this_pref : NULL),
2537 (do_proto ? *list_this_suff : NULL),
2540 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2541 Dump_RFC822HeadersBody(
2550 if (headers_only == HEADERS_ONLY) {
2554 /* signify start of msg text */
2555 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2556 if (do_proto) cprintf("text\n");
2559 if (TheMessage->cm_format_type == FMT_FIXED)
2562 mode, /* how would you like that message? */
2565 /* If the message on disk is format 0 (Citadel vari-format), we
2566 * output using the formatter at 80 columns. This is the final output
2567 * form if the transfer format is RFC822, but if the transfer format
2568 * is Citadel proprietary, it'll still work, because the indentation
2569 * for new paragraphs is correct and the client will reformat the
2570 * message to the reader's screen width.
2572 if (TheMessage->cm_format_type == FMT_CITADEL) {
2573 mptr = TheMessage->cm_fields['M'];
2575 if (mode == MT_MIME) {
2576 cprintf("Content-type: text/x-citadel-variformat\n\n");
2581 /* If the message on disk is format 4 (MIME), we've gotta hand it
2582 * off to the MIME parser. The client has already been told that
2583 * this message is format 1 (fixed format), so the callback function
2584 * we use will display those parts as-is.
2586 if (TheMessage->cm_format_type == FMT_RFC822) {
2587 memset(&ma, 0, sizeof(struct ma_info));
2589 if (mode == MT_MIME) {
2590 ma.use_fo_hooks = 0;
2591 strcpy(ma.chosen_part, "1");
2592 ma.chosen_pref = 9999;
2593 ma.dont_decode = CCC->msg4_dont_decode;
2594 mime_parser(mptr, NULL,
2595 *choose_preferred, *fixed_output_pre,
2596 *fixed_output_post, (void *)&ma, 1);
2597 mime_parser(mptr, NULL,
2598 *output_preferred, NULL, NULL, (void *)&ma, 1);
2601 ma.use_fo_hooks = 1;
2602 mime_parser(mptr, NULL,
2603 *fixed_output, *fixed_output_pre,
2604 *fixed_output_post, (void *)&ma, 0);
2609 DONE: /* now we're done */
2610 if (do_proto) cprintf("000\n");
2616 * display a message (mode 0 - Citadel proprietary)
2618 void cmd_msg0(char *cmdbuf)
2621 int headers_only = HEADERS_ALL;
2623 msgid = extract_long(cmdbuf, 0);
2624 headers_only = extract_int(cmdbuf, 1);
2626 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
2632 * display a message (mode 2 - RFC822)
2634 void cmd_msg2(char *cmdbuf)
2637 int headers_only = HEADERS_ALL;
2639 msgid = extract_long(cmdbuf, 0);
2640 headers_only = extract_int(cmdbuf, 1);
2642 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
2648 * display a message (mode 3 - IGnet raw format - internal programs only)
2650 void cmd_msg3(char *cmdbuf)
2653 struct CtdlMessage *msg = NULL;
2656 if (CC->internal_pgm == 0) {
2657 cprintf("%d This command is for internal programs only.\n",
2658 ERROR + HIGHER_ACCESS_REQUIRED);
2662 msgnum = extract_long(cmdbuf, 0);
2663 msg = CtdlFetchMessage(msgnum, 1);
2665 cprintf("%d Message %ld not found.\n",
2666 ERROR + MESSAGE_NOT_FOUND, msgnum);
2670 serialize_message(&smr, msg);
2671 CtdlFreeMessage(msg);
2674 cprintf("%d Unable to serialize message\n",
2675 ERROR + INTERNAL_ERROR);
2679 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2680 client_write((char *)smr.ser, (int)smr.len);
2687 * Display a message using MIME content types
2689 void cmd_msg4(char *cmdbuf)
2694 msgid = extract_long(cmdbuf, 0);
2695 extract_token(section, cmdbuf, 1, '|', sizeof section);
2696 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
2702 * Client tells us its preferred message format(s)
2704 void cmd_msgp(char *cmdbuf)
2706 if (!strcasecmp(cmdbuf, "dont_decode")) {
2707 CC->msg4_dont_decode = 1;
2708 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2711 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2712 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2718 * Open a component of a MIME message as a download file
2720 void cmd_opna(char *cmdbuf)
2723 char desired_section[128];
2725 msgid = extract_long(cmdbuf, 0);
2726 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2727 safestrncpy(CC->download_desired_section, desired_section,
2728 sizeof CC->download_desired_section);
2729 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
2734 * Open a component of a MIME message and transmit it all at once
2736 void cmd_dlat(char *cmdbuf)
2739 char desired_section[128];
2741 msgid = extract_long(cmdbuf, 0);
2742 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2743 safestrncpy(CC->download_desired_section, desired_section,
2744 sizeof CC->download_desired_section);
2745 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
2750 * Save one or more message pointers into a specified room
2751 * (Returns 0 for success, nonzero for failure)
2752 * roomname may be NULL to use the current room
2754 * Note that the 'supplied_msg' field may be set to NULL, in which case
2755 * the message will be fetched from disk, by number, if we need to perform
2756 * replication checks. This adds an additional database read, so if the
2757 * caller already has the message in memory then it should be supplied. (Obviously
2758 * this mode of operation only works if we're saving a single message.)
2760 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2761 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2763 struct CitContext *CCC = CC;
2765 char hold_rm[ROOMNAMELEN];
2766 struct cdbdata *cdbfr;
2769 long highest_msg = 0L;
2772 struct CtdlMessage *msg = NULL;
2774 long *msgs_to_be_merged = NULL;
2775 int num_msgs_to_be_merged = 0;
2777 MSG_syslog(LOG_DEBUG,
2778 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2779 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2782 strcpy(hold_rm, CCC->room.QRname);
2785 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2786 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2787 if (num_newmsgs > 1) supplied_msg = NULL;
2789 /* Now the regular stuff */
2790 if (CtdlGetRoomLock(&CCC->room,
2791 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2793 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2794 return(ERROR + ROOM_NOT_FOUND);
2798 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2799 num_msgs_to_be_merged = 0;
2802 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2803 if (cdbfr == NULL) {
2807 msglist = (long *) cdbfr->ptr;
2808 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2809 num_msgs = cdbfr->len / sizeof(long);
2814 /* Create a list of msgid's which were supplied by the caller, but do
2815 * not already exist in the target room. It is absolutely taboo to
2816 * have more than one reference to the same message in a room.
2818 for (i=0; i<num_newmsgs; ++i) {
2820 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2821 if (msglist[j] == newmsgidlist[i]) {
2826 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2830 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2833 * Now merge the new messages
2835 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2836 if (msglist == NULL) {
2837 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2838 free(msgs_to_be_merged);
2839 return (ERROR + INTERNAL_ERROR);
2841 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2842 num_msgs += num_msgs_to_be_merged;
2844 /* Sort the message list, so all the msgid's are in order */
2845 num_msgs = sort_msglist(msglist, num_msgs);
2847 /* Determine the highest message number */
2848 highest_msg = msglist[num_msgs - 1];
2850 /* Write it back to disk. */
2851 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2852 msglist, (int)(num_msgs * sizeof(long)));
2854 /* Free up the memory we used. */
2857 /* Update the highest-message pointer and unlock the room. */
2858 CCC->room.QRhighest = highest_msg;
2859 CtdlPutRoomLock(&CCC->room);
2861 /* Perform replication checks if necessary */
2862 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
2863 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2865 for (i=0; i<num_msgs_to_be_merged; ++i) {
2866 msgid = msgs_to_be_merged[i];
2868 if (supplied_msg != NULL) {
2872 msg = CtdlFetchMessage(msgid, 0);
2876 ReplicationChecks(msg);
2878 /* If the message has an Exclusive ID, index that... */
2879 if (msg->cm_fields['E'] != NULL) {
2880 index_message_by_euid(msg->cm_fields['E'], &CCC->room, msgid);
2883 /* Free up the memory we may have allocated */
2884 if (msg != supplied_msg) {
2885 CtdlFreeMessage(msg);
2893 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2896 /* Submit this room for processing by hooks */
2897 PerformRoomHooks(&CCC->room);
2899 /* Go back to the room we were in before we wandered here... */
2900 CtdlGetRoom(&CCC->room, hold_rm);
2902 /* Bump the reference count for all messages which were merged */
2903 if (!suppress_refcount_adj) {
2904 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2907 /* Free up memory... */
2908 if (msgs_to_be_merged != NULL) {
2909 free(msgs_to_be_merged);
2912 /* Return success. */
2918 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2921 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2922 int do_repl_check, struct CtdlMessage *supplied_msg)
2924 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2931 * Message base operation to save a new message to the message store
2932 * (returns new message number)
2934 * This is the back end for CtdlSubmitMsg() and should not be directly
2935 * called by server-side modules.
2938 long send_message(struct CtdlMessage *msg) {
2939 struct CitContext *CCC = CC;
2947 /* Get a new message number */
2948 newmsgid = get_new_message_number();
2949 snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2950 (long unsigned int) time(NULL),
2951 (long unsigned int) newmsgid,
2955 /* Generate an ID if we don't have one already */
2956 if (msg->cm_fields['I']==NULL) {
2957 msg->cm_fields['I'] = strdup(msgidbuf);
2960 /* If the message is big, set its body aside for storage elsewhere */
2961 if (msg->cm_fields['M'] != NULL) {
2962 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2964 holdM = msg->cm_fields['M'];
2965 msg->cm_fields['M'] = NULL;
2969 /* Serialize our data structure for storage in the database */
2970 serialize_message(&smr, msg);
2973 msg->cm_fields['M'] = holdM;
2977 cprintf("%d Unable to serialize message\n",
2978 ERROR + INTERNAL_ERROR);
2982 /* Write our little bundle of joy into the message base */
2983 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2984 smr.ser, smr.len) < 0) {
2985 MSGM_syslog(LOG_ERR, "Can't store message\n");
2989 cdb_store(CDB_BIGMSGS,
2999 /* Free the memory we used for the serialized message */
3002 /* Return the *local* message ID to the caller
3003 * (even if we're storing an incoming network message)
3011 * Serialize a struct CtdlMessage into the format used on disk and network.
3013 * This function loads up a "struct ser_ret" (defined in server.h) which
3014 * contains the length of the serialized message and a pointer to the
3015 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3017 void serialize_message(struct ser_ret *ret, /* return values */
3018 struct CtdlMessage *msg) /* unserialized msg */
3020 struct CitContext *CCC = CC;
3021 size_t wlen, fieldlen;
3023 static char *forder = FORDER;
3026 * Check for valid message format
3028 if (is_valid_message(msg) == 0) {
3029 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
3036 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
3037 ret->len = ret->len +
3038 strlen(msg->cm_fields[(int)forder[i]]) + 2;
3040 ret->ser = malloc(ret->len);
3041 if (ret->ser == NULL) {
3042 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3043 (long)ret->len, strerror(errno));
3050 ret->ser[1] = msg->cm_anon_type;
3051 ret->ser[2] = msg->cm_format_type;
3054 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
3055 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
3056 ret->ser[wlen++] = (char)forder[i];
3057 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
3058 wlen = wlen + fieldlen + 1;
3060 if (ret->len != wlen) {
3061 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
3062 (long)ret->len, (long)wlen);
3070 * Check to see if any messages already exist in the current room which
3071 * carry the same Exclusive ID as this one. If any are found, delete them.
3073 void ReplicationChecks(struct CtdlMessage *msg) {
3074 struct CitContext *CCC = CC;
3075 long old_msgnum = (-1L);
3077 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3079 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3082 /* No exclusive id? Don't do anything. */
3083 if (msg == NULL) return;
3084 if (msg->cm_fields['E'] == NULL) return;
3085 if (IsEmptyStr(msg->cm_fields['E'])) return;
3086 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3087 msg->cm_fields['E'], CCC->room.QRname);*/
3089 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CCC->room);
3090 if (old_msgnum > 0L) {
3091 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3092 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3099 * Save a message to disk and submit it into the delivery system.
3101 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3102 struct recptypes *recps, /* recipients (if mail) */
3103 const char *force, /* force a particular room? */
3104 int flags /* should the message be exported clean? */
3107 char submit_filename[128];
3108 char generated_timestamp[32];
3109 char hold_rm[ROOMNAMELEN];
3110 char actual_rm[ROOMNAMELEN];
3111 char force_room[ROOMNAMELEN];
3112 char content_type[SIZ]; /* We have to learn this */
3113 char recipient[SIZ];
3116 const char *mptr = NULL;
3117 struct ctdluser userbuf;
3119 struct MetaData smi;
3120 FILE *network_fp = NULL;
3121 static int seqnum = 1;
3122 struct CtdlMessage *imsg = NULL;
3124 size_t instr_alloc = 0;
3126 char *hold_R, *hold_D;
3127 char *collected_addresses = NULL;
3128 struct addresses_to_be_filed *aptr = NULL;
3129 StrBuf *saved_rfc822_version = NULL;
3130 int qualified_for_journaling = 0;
3131 CitContext *CCC = MyContext();
3132 char bounce_to[1024] = "";
3135 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3136 if (is_valid_message(msg) == 0) return(-1); /* self check */
3138 /* If this message has no timestamp, we take the liberty of
3139 * giving it one, right now.
3141 if (msg->cm_fields['T'] == NULL) {
3142 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3143 msg->cm_fields['T'] = strdup(generated_timestamp);
3146 /* If this message has no path, we generate one.
3148 if (msg->cm_fields['P'] == NULL) {
3149 if (msg->cm_fields['A'] != NULL) {
3150 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3151 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3152 if (isspace(msg->cm_fields['P'][a])) {
3153 msg->cm_fields['P'][a] = ' ';
3158 msg->cm_fields['P'] = strdup("unknown");
3162 if (force == NULL) {
3163 strcpy(force_room, "");
3166 strcpy(force_room, force);
3169 /* Learn about what's inside, because it's what's inside that counts */
3170 if (msg->cm_fields['M'] == NULL) {
3171 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3175 switch (msg->cm_format_type) {
3177 strcpy(content_type, "text/x-citadel-variformat");
3180 strcpy(content_type, "text/plain");
3183 strcpy(content_type, "text/plain");
3184 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3187 safestrncpy(content_type, &mptr[13], sizeof content_type);
3188 striplt(content_type);
3189 aptr = content_type;
3190 while (!IsEmptyStr(aptr)) {
3202 /* Goto the correct room */
3203 room = (recps) ? CCC->room.QRname : SENTITEMS;
3204 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3205 strcpy(hold_rm, CCC->room.QRname);
3206 strcpy(actual_rm, CCC->room.QRname);
3207 if (recps != NULL) {
3208 strcpy(actual_rm, SENTITEMS);
3211 /* If the user is a twit, move to the twit room for posting */
3213 if (CCC->user.axlevel == AxProbU) {
3214 strcpy(hold_rm, actual_rm);
3215 strcpy(actual_rm, config.c_twitroom);
3216 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3220 /* ...or if this message is destined for Aide> then go there. */
3221 if (!IsEmptyStr(force_room)) {
3222 strcpy(actual_rm, force_room);
3225 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3226 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3227 /* CtdlGetRoom(&CCC->room, actual_rm); */
3228 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3232 * If this message has no O (room) field, generate one.
3234 if (msg->cm_fields['O'] == NULL) {
3235 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3238 /* Perform "before save" hooks (aborting if any return nonzero) */
3239 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3240 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3243 * If this message has an Exclusive ID, and the room is replication
3244 * checking enabled, then do replication checks.
3246 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3247 ReplicationChecks(msg);
3250 /* Save it to disk */
3251 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3252 newmsgid = send_message(msg);
3253 if (newmsgid <= 0L) return(-5);
3255 /* Write a supplemental message info record. This doesn't have to
3256 * be a critical section because nobody else knows about this message
3259 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3260 memset(&smi, 0, sizeof(struct MetaData));
3261 smi.meta_msgnum = newmsgid;
3262 smi.meta_refcount = 0;
3263 safestrncpy(smi.meta_content_type, content_type,
3264 sizeof smi.meta_content_type);
3267 * Measure how big this message will be when rendered as RFC822.
3268 * We do this for two reasons:
3269 * 1. We need the RFC822 length for the new metadata record, so the
3270 * POP and IMAP services don't have to calculate message lengths
3271 * while the user is waiting (multiplied by potentially hundreds
3272 * or thousands of messages).
3273 * 2. If journaling is enabled, we will need an RFC822 version of the
3274 * message to attach to the journalized copy.
3276 if (CCC->redirect_buffer != NULL) {
3277 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3280 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3281 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3282 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3283 saved_rfc822_version = CCC->redirect_buffer;
3284 CCC->redirect_buffer = NULL;
3288 /* Now figure out where to store the pointers */
3289 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3291 /* If this is being done by the networker delivering a private
3292 * message, we want to BYPASS saving the sender's copy (because there
3293 * is no local sender; it would otherwise go to the Trashcan).
3295 if ((!CCC->internal_pgm) || (recps == NULL)) {
3296 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3297 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3298 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3302 /* For internet mail, drop a copy in the outbound queue room */
3303 if ((recps != NULL) && (recps->num_internet > 0)) {
3304 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3307 /* If other rooms are specified, drop them there too. */
3308 if ((recps != NULL) && (recps->num_room > 0))
3309 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3310 extract_token(recipient, recps->recp_room, i,
3311 '|', sizeof recipient);
3312 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3313 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3316 /* Bump this user's messages posted counter. */
3317 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3318 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3319 CCC->user.posted = CCC->user.posted + 1;
3320 CtdlPutUserLock(&CCC->user);
3322 /* Decide where bounces need to be delivered */
3323 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3324 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3326 else if (CCC->logged_in) {
3327 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3330 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3333 /* If this is private, local mail, make a copy in the
3334 * recipient's mailbox and bump the reference count.
3336 if ((recps != NULL) && (recps->num_local > 0))
3337 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3338 extract_token(recipient, recps->recp_local, i,
3339 '|', sizeof recipient);
3340 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3342 if (CtdlGetUser(&userbuf, recipient) == 0) {
3343 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3344 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3345 CtdlBumpNewMailCounter(userbuf.usernum);
3346 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3347 /* Generate a instruction message for the Funambol notification
3348 * server, in the same style as the SMTP queue
3351 instr = malloc(instr_alloc);
3352 snprintf(instr, instr_alloc,
3353 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3355 SPOOLMIME, newmsgid, (long)time(NULL),
3359 imsg = malloc(sizeof(struct CtdlMessage));
3360 memset(imsg, 0, sizeof(struct CtdlMessage));
3361 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3362 imsg->cm_anon_type = MES_NORMAL;
3363 imsg->cm_format_type = FMT_RFC822;
3364 imsg->cm_fields['U'] = strdup("QMSG");
3365 imsg->cm_fields['A'] = strdup("Citadel");
3366 imsg->cm_fields['J'] = strdup("do not journal");
3367 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3368 imsg->cm_fields['2'] = strdup(recipient);
3369 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3370 CtdlFreeMessage(imsg);
3374 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3375 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3379 /* Perform "after save" hooks */
3380 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3381 if (msg->cm_fields['3'] != NULL) free(msg->cm_fields['3']);
3382 msg->cm_fields['3'] = malloc(20);
3383 snprintf(msg->cm_fields['3'], 20, "%ld", newmsgid);
3384 PerformMessageHooks(msg, EVT_AFTERSAVE);
3385 free(msg->cm_fields['3']);
3386 msg->cm_fields['3'] = NULL;
3388 /* For IGnet mail, we have to save a new copy into the spooler for
3389 * each recipient, with the R and D fields set to the recipient and
3390 * destination-node. This has two ugly side effects: all other
3391 * recipients end up being unlisted in this recipient's copy of the
3392 * message, and it has to deliver multiple messages to the same
3393 * node. We'll revisit this again in a year or so when everyone has
3394 * a network spool receiver that can handle the new style messages.
3396 if ((recps != NULL) && (recps->num_ignet > 0))
3397 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3398 extract_token(recipient, recps->recp_ignet, i,
3399 '|', sizeof recipient);
3401 hold_R = msg->cm_fields['R'];
3402 hold_D = msg->cm_fields['D'];
3403 msg->cm_fields['R'] = malloc(SIZ);
3404 msg->cm_fields['D'] = malloc(128);
3405 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3406 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3408 serialize_message(&smr, msg);
3410 snprintf(submit_filename, sizeof submit_filename,
3411 "%s/netmail.%04lx.%04x.%04x",
3413 (long) getpid(), CCC->cs_pid, ++seqnum);
3414 network_fp = fopen(submit_filename, "wb+");
3415 if (network_fp != NULL) {
3416 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3418 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3426 free(msg->cm_fields['R']);
3427 free(msg->cm_fields['D']);
3428 msg->cm_fields['R'] = hold_R;
3429 msg->cm_fields['D'] = hold_D;
3432 /* Go back to the room we started from */
3433 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3434 if (strcasecmp(hold_rm, CCC->room.QRname))
3435 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3437 /* For internet mail, generate delivery instructions.
3438 * Yes, this is recursive. Deal with it. Infinite recursion does
3439 * not happen because the delivery instructions message does not
3440 * contain a recipient.
3442 if ((recps != NULL) && (recps->num_internet > 0)) {
3443 StrBuf *SpoolMsg = NewStrBuf();
3446 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3448 StrBufPrintf(SpoolMsg,
3449 "Content-type: "SPOOLMIME"\n"
3458 if (recps->envelope_from != NULL) {
3459 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3460 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3461 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3463 if (recps->sending_room != NULL) {
3464 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3465 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3466 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3469 nTokens = num_tokens(recps->recp_internet, '|');
3470 for (i = 0; i < nTokens; i++) {
3472 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3474 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3475 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3476 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3480 imsg = malloc(sizeof(struct CtdlMessage));
3481 memset(imsg, 0, sizeof(struct CtdlMessage));
3482 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3483 imsg->cm_anon_type = MES_NORMAL;
3484 imsg->cm_format_type = FMT_RFC822;
3485 imsg->cm_fields['U'] = strdup("QMSG");
3486 imsg->cm_fields['A'] = strdup("Citadel");
3487 imsg->cm_fields['J'] = strdup("do not journal");
3488 imsg->cm_fields['M'] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3489 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3490 CtdlFreeMessage(imsg);
3494 * Any addresses to harvest for someone's address book?
3496 if ( (CCC->logged_in) && (recps != NULL) ) {
3497 collected_addresses = harvest_collected_addresses(msg);
3500 if (collected_addresses != NULL) {
3501 aptr = (struct addresses_to_be_filed *)
3502 malloc(sizeof(struct addresses_to_be_filed));
3503 CtdlMailboxName(actual_rm, sizeof actual_rm,
3504 &CCC->user, USERCONTACTSROOM);
3505 aptr->roomname = strdup(actual_rm);
3506 aptr->collected_addresses = collected_addresses;
3507 begin_critical_section(S_ATBF);
3510 end_critical_section(S_ATBF);
3514 * Determine whether this message qualifies for journaling.
3516 if (msg->cm_fields['J'] != NULL) {
3517 qualified_for_journaling = 0;
3520 if (recps == NULL) {
3521 qualified_for_journaling = config.c_journal_pubmsgs;
3523 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3524 qualified_for_journaling = config.c_journal_email;
3527 qualified_for_journaling = config.c_journal_pubmsgs;
3532 * Do we have to perform journaling? If so, hand off the saved
3533 * RFC822 version will be handed off to the journaler for background
3534 * submit. Otherwise, we have to free the memory ourselves.
3536 if (saved_rfc822_version != NULL) {
3537 if (qualified_for_journaling) {
3538 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3541 FreeStrBuf(&saved_rfc822_version);
3551 * Convenience function for generating small administrative messages.
3553 void quickie_message(const char *from,
3554 const char *fromaddr,
3559 const char *subject)
3561 struct CtdlMessage *msg;
3562 struct recptypes *recp = NULL;
3564 msg = malloc(sizeof(struct CtdlMessage));
3565 memset(msg, 0, sizeof(struct CtdlMessage));
3566 msg->cm_magic = CTDLMESSAGE_MAGIC;
3567 msg->cm_anon_type = MES_NORMAL;
3568 msg->cm_format_type = format_type;
3571 msg->cm_fields['A'] = strdup(from);
3573 else if (fromaddr != NULL) {
3574 msg->cm_fields['A'] = strdup(fromaddr);
3575 if (strchr(msg->cm_fields['A'], '@')) {
3576 *strchr(msg->cm_fields['A'], '@') = 0;
3580 msg->cm_fields['A'] = strdup("Citadel");
3583 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3584 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3585 msg->cm_fields['N'] = strdup(NODENAME);
3587 msg->cm_fields['R'] = strdup(to);
3588 recp = validate_recipients(to, NULL, 0);
3590 if (subject != NULL) {
3591 msg->cm_fields['U'] = strdup(subject);
3593 msg->cm_fields['M'] = strdup(text);
3595 CtdlSubmitMsg(msg, recp, room, 0);
3596 CtdlFreeMessage(msg);
3597 if (recp != NULL) free_recipients(recp);
3600 void flood_protect_quickie_message(const char *from,
3601 const char *fromaddr,
3606 const char *subject,
3608 const char **CritStr,
3613 u_char rawdigest[MD5_DIGEST_LEN];
3614 struct MD5Context md5context;
3616 struct cdbdata *cdbut;
3619 time_t ts = time(NULL);
3620 time_t tsday = ts / (8*60*60); /* just care for a day... */
3622 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3623 MD5Init(&md5context);
3625 for (i = 0; i < nCriterions; i++)
3626 MD5Update(&md5context,
3627 (const unsigned char*)CritStr[i], CritStrLen[i]);
3628 MD5Update(&md5context,
3629 (const unsigned char*)timestamp, tslen);
3630 MD5Final(rawdigest, &md5context);
3632 guid = NewStrBufPlain(NULL,
3633 MD5_DIGEST_LEN * 2 + 12);
3634 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3635 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3636 if (StrLength(guid) > 40)
3637 StrBufCutAt(guid, 40, NULL);
3638 /* Find out if we've already sent a similar message */
3639 memcpy(ut.ut_msgid, SKEY(guid));
3640 ut.ut_timestamp = ts;
3642 cdbut = cdb_fetch(CDB_USETABLE, SKEY(guid));
3644 if (cdbut != NULL) {
3645 /* yes, we did. flood protection kicks in. */
3647 "not sending message again\n");
3651 /* rewrite the record anyway, to update the timestamp */
3652 cdb_store(CDB_USETABLE,
3654 &ut, sizeof(struct UseTable) );
3658 if (cdbut != NULL) return;
3659 /* no, this message isn't sent recently; go ahead. */
3660 quickie_message(from,
3671 * Back end function used by CtdlMakeMessage() and similar functions
3673 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3675 size_t maxlen, /* maximum message length */
3676 StrBuf *exist, /* if non-null, append to it;
3677 exist is ALWAYS freed */
3678 int crlf, /* CRLF newlines instead of LF */
3679 int *sock /* socket handle or 0 for this session's client socket */
3688 LineBuf = NewStrBufPlain(NULL, SIZ);
3689 if (exist == NULL) {
3690 Message = NewStrBufPlain(NULL, 4 * SIZ);
3693 Message = NewStrBufDup(exist);
3696 /* Do we need to change leading ".." to "." for SMTP escaping? */
3697 if ((tlen == 1) && (*terminator == '.')) {
3701 /* read in the lines of message text one by one */
3704 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3709 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3711 if ((StrLength(LineBuf) == tlen) &&
3712 (!strcmp(ChrPtr(LineBuf), terminator)))
3715 if ( (!flushing) && (!finished) ) {
3717 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3720 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3723 /* Unescape SMTP-style input of two dots at the beginning of the line */
3725 (StrLength(LineBuf) == 2) &&
3726 (!strcmp(ChrPtr(LineBuf), "..")))
3728 StrBufCutLeft(LineBuf, 1);
3731 StrBufAppendBuf(Message, LineBuf, 0);
3734 /* if we've hit the max msg length, flush the rest */
3735 if (StrLength(Message) >= maxlen) flushing = 1;
3737 } while (!finished);
3738 FreeStrBuf(&LineBuf);
3742 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3746 FreeStrBuf(&(*Msg)->MsgBuf);
3752 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3754 size_t maxlen, /* maximum message length */
3755 size_t expectlen, /* if we expect a message, how long should it be? */
3756 StrBuf *exist, /* if non-null, append to it;
3757 exist is ALWAYS freed */
3758 long eLen, /* length of exist */
3759 int crlf /* CRLF newlines instead of LF */
3762 ReadAsyncMsg *NewMsg;
3764 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3765 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3767 if (exist == NULL) {
3770 if (expectlen == 0) {
3774 len = expectlen + 10;
3776 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3779 NewMsg->MsgBuf = NewStrBufDup(exist);
3781 /* Do we need to change leading ".." to "." for SMTP escaping? */
3782 if ((tlen == 1) && (*terminator == '.')) {
3786 NewMsg->terminator = terminator;
3787 NewMsg->tlen = tlen;
3789 NewMsg->maxlen = maxlen;
3791 NewMsg->crlf = crlf;
3797 * Back end function used by CtdlMakeMessage() and similar functions
3799 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3801 ReadAsyncMsg *ReadMsg;
3802 int MsgFinished = 0;
3803 eReadState Finished = eMustReadMore;
3808 const char *pch = ChrPtr(IO->SendBuf.Buf);
3809 const char *pchh = IO->SendBuf.ReadWritePointer;
3815 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3816 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3817 ((CitContext*)(IO->CitContext))->ServiceName,
3820 fd = fopen(fn, "a+");
3823 ReadMsg = IO->ReadMsg;
3825 /* read in the lines of message text one by one */
3827 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3830 case eMustReadMore: /// read new from socket...
3832 if (IO->RecvBuf.ReadWritePointer != NULL) {
3833 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3834 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3836 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3840 fprintf(fd, "BufferEmpty! \n");
3846 case eBufferNotEmpty: /* shouldn't happen... */
3847 case eReadSuccess: /// done for now...
3849 case eReadFail: /// WHUT?
3855 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3856 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3859 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3862 else if (!ReadMsg->flushing) {
3865 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3868 /* Unescape SMTP-style input of two dots at the beginning of the line */
3869 if ((ReadMsg->dodot) &&
3870 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3871 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3874 fprintf(fd, "UnEscaped!\n");
3876 StrBufCutLeft(IO->IOBuf, 1);
3879 if (ReadMsg->crlf) {
3880 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3883 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3886 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3889 /* if we've hit the max msg length, flush the rest */
3890 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3892 } while (!MsgFinished);
3895 fprintf(fd, "Done with reading; %s.\n, ",
3896 (MsgFinished)?"Message Finished": "FAILED");
3900 return eReadSuccess;
3907 * Back end function used by CtdlMakeMessage() and similar functions
3909 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3911 size_t maxlen, /* maximum message length */
3912 StrBuf *exist, /* if non-null, append to it;
3913 exist is ALWAYS freed */
3914 int crlf, /* CRLF newlines instead of LF */
3915 int *sock /* socket handle or 0 for this session's client socket */
3920 Message = CtdlReadMessageBodyBuf(terminator,
3926 if (Message == NULL)
3929 return SmashStrBuf(&Message);
3934 * Build a binary message to be saved on disk.
3935 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3936 * will become part of the message. This means you are no longer
3937 * responsible for managing that memory -- it will be freed along with
3938 * the rest of the fields when CtdlFreeMessage() is called.)
3941 struct CtdlMessage *CtdlMakeMessage(
3942 struct ctdluser *author, /* author's user structure */
3943 char *recipient, /* NULL if it's not mail */
3944 char *recp_cc, /* NULL if it's not mail */
3945 char *room, /* room where it's going */
3946 int type, /* see MES_ types in header file */
3947 int format_type, /* variformat, plain text, MIME... */
3948 char *fake_name, /* who we're masquerading as */
3949 char *my_email, /* which of my email addresses to use (empty is ok) */
3950 char *subject, /* Subject (optional) */
3951 char *supplied_euid, /* ...or NULL if this is irrelevant */
3952 char *preformatted_text, /* ...or NULL to read text from client */
3953 char *references /* Thread references */
3955 char dest_node[256];
3957 struct CtdlMessage *msg;
3959 StrBuf *FakeEncAuthor = NULL;
3961 msg = malloc(sizeof(struct CtdlMessage));
3962 memset(msg, 0, sizeof(struct CtdlMessage));
3963 msg->cm_magic = CTDLMESSAGE_MAGIC;
3964 msg->cm_anon_type = type;
3965 msg->cm_format_type = format_type;
3967 /* Don't confuse the poor folks if it's not routed mail. */
3968 strcpy(dest_node, "");
3970 if (recipient != NULL) striplt(recipient);
3971 if (recp_cc != NULL) striplt(recp_cc);
3973 /* Path or Return-Path */
3974 if (my_email == NULL) my_email = "";
3976 if (!IsEmptyStr(my_email)) {
3977 msg->cm_fields['P'] = strdup(my_email);
3980 snprintf(buf, sizeof buf, "%s", author->fullname);
3981 msg->cm_fields['P'] = strdup(buf);
3983 convert_spaces_to_underscores(msg->cm_fields['P']);
3985 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3986 msg->cm_fields['T'] = strdup(buf);
3988 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3989 FakeAuthor = NewStrBufPlain (fake_name, -1);
3992 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3994 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3995 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3996 FreeStrBuf(&FakeAuthor);
3998 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3999 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
4002 msg->cm_fields['O'] = strdup(CC->room.QRname);
4005 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
4006 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
4008 if ((recipient != NULL) && (recipient[0] != 0)) {
4009 msg->cm_fields['R'] = strdup(recipient);
4011 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
4012 msg->cm_fields['Y'] = strdup(recp_cc);
4014 if (dest_node[0] != 0) {
4015 msg->cm_fields['D'] = strdup(dest_node);
4018 if (!IsEmptyStr(my_email)) {
4019 msg->cm_fields['F'] = strdup(my_email);
4021 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
4022 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
4025 if (subject != NULL) {
4028 length = strlen(subject);
4034 while ((subject[i] != '\0') &&
4035 (IsAscii = isascii(subject[i]) != 0 ))
4038 msg->cm_fields['U'] = strdup(subject);
4039 else /* ok, we've got utf8 in the string. */
4041 msg->cm_fields['U'] = rfc2047encode(subject, length);
4047 if (supplied_euid != NULL) {
4048 msg->cm_fields['E'] = strdup(supplied_euid);
4051 if ((references != NULL) && (!IsEmptyStr(references))) {
4052 if (msg->cm_fields['W'] != NULL)
4053 free(msg->cm_fields['W']);
4054 msg->cm_fields['W'] = strdup(references);
4057 if (preformatted_text != NULL) {
4058 msg->cm_fields['M'] = preformatted_text;
4061 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4068 * Check to see whether we have permission to post a message in the current
4069 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
4070 * returns 0 on success.
4072 int CtdlDoIHavePermissionToPostInThisRoom(
4075 const char* RemoteIdentifier,
4081 if (!(CC->logged_in) &&
4082 (PostPublic == POST_LOGGED_IN)) {
4083 snprintf(errmsgbuf, n, "Not logged in.");
4084 return (ERROR + NOT_LOGGED_IN);
4086 else if (PostPublic == CHECK_EXISTANCE) {
4087 return (0); // We're Evaling whether a recipient exists
4089 else if (!(CC->logged_in)) {
4091 if ((CC->room.QRflags & QR_READONLY)) {
4092 snprintf(errmsgbuf, n, "Not logged in.");
4093 return (ERROR + NOT_LOGGED_IN);
4095 if (CC->room.QRflags2 & QR2_MODERATED) {
4096 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4097 return (ERROR + NOT_LOGGED_IN);
4099 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4101 return CtdlNetconfigCheckRoomaccess(errmsgbuf, n, RemoteIdentifier);
4107 if ((CC->user.axlevel < AxProbU)
4108 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4109 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4110 return (ERROR + HIGHER_ACCESS_REQUIRED);
4113 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4115 if (ra & UA_POSTALLOWED) {
4116 strcpy(errmsgbuf, "OK to post or reply here");
4120 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4122 * To be thorough, we ought to check to see if the message they are
4123 * replying to is actually a valid one in this room, but unless this
4124 * actually becomes a problem we'll go with high performance instead.
4126 strcpy(errmsgbuf, "OK to reply here");
4130 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4131 /* Clarify what happened with a better error message */
4132 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4133 return (ERROR + HIGHER_ACCESS_REQUIRED);
4136 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4137 return (ERROR + HIGHER_ACCESS_REQUIRED);
4143 * Check to see if the specified user has Internet mail permission
4144 * (returns nonzero if permission is granted)
4146 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4148 /* Do not allow twits to send Internet mail */
4149 if (who->axlevel <= AxProbU) return(0);
4151 /* Globally enabled? */
4152 if (config.c_restrict == 0) return(1);
4154 /* User flagged ok? */
4155 if (who->flags & US_INTERNET) return(2);
4157 /* Admin level access? */
4158 if (who->axlevel >= AxAideU) return(3);
4160 /* No mail for you! */
4166 * Validate recipients, count delivery types and errors, and handle aliasing
4167 * FIXME check for dupes!!!!!
4169 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4170 * were specified, or the number of addresses found invalid.
4172 * Caller needs to free the result using free_recipients()
4174 struct recptypes *validate_recipients(const char *supplied_recipients,
4175 const char *RemoteIdentifier,
4177 struct CitContext *CCC = CC;
4178 struct recptypes *ret;
4179 char *recipients = NULL;
4181 char this_recp[256];
4182 char this_recp_cooked[256];
4189 struct ctdluser tempUS;
4190 struct ctdlroom tempQR;
4191 struct ctdlroom tempQR2;
4197 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4198 if (ret == NULL) return(NULL);
4200 /* Set all strings to null and numeric values to zero */
4201 memset(ret, 0, sizeof(struct recptypes));
4203 if (supplied_recipients == NULL) {
4204 recipients = strdup("");
4207 recipients = strdup(supplied_recipients);
4210 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4211 * actually need, but it's healthier for the heap than doing lots of tiny
4212 * realloc() calls instead.
4214 len = strlen(recipients) + 1024;
4215 ret->errormsg = malloc(len);
4216 ret->recp_local = malloc(len);
4217 ret->recp_internet = malloc(len);
4218 ret->recp_ignet = malloc(len);
4219 ret->recp_room = malloc(len);
4220 ret->display_recp = malloc(len);
4221 ret->recp_orgroom = malloc(len);
4222 org_recp = malloc(len);
4224 ret->errormsg[0] = 0;
4225 ret->recp_local[0] = 0;
4226 ret->recp_internet[0] = 0;
4227 ret->recp_ignet[0] = 0;
4228 ret->recp_room[0] = 0;
4229 ret->recp_orgroom[0] = 0;
4230 ret->display_recp[0] = 0;
4232 ret->recptypes_magic = RECPTYPES_MAGIC;
4234 /* Change all valid separator characters to commas */
4235 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4236 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4237 recipients[i] = ',';
4241 /* Now start extracting recipients... */
4243 while (!IsEmptyStr(recipients)) {
4244 for (i=0; i<=strlen(recipients); ++i) {
4245 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4246 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4247 safestrncpy(this_recp, recipients, i+1);
4249 if (recipients[i] == ',') {
4250 strcpy(recipients, &recipients[i+1]);
4253 strcpy(recipients, "");
4260 if (IsEmptyStr(this_recp))
4262 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4265 strcpy(org_recp, this_recp);
4268 mailtype = alias(this_recp);
4270 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4271 if (this_recp[j]=='_') {
4272 this_recp_cooked[j] = ' ';
4275 this_recp_cooked[j] = this_recp[j];
4278 this_recp_cooked[j] = '\0';
4283 if (!strcasecmp(this_recp, "sysop")) {
4285 strcpy(this_recp, config.c_aideroom);
4286 if (!IsEmptyStr(ret->recp_room)) {
4287 strcat(ret->recp_room, "|");
4289 strcat(ret->recp_room, this_recp);
4291 else if ( (!strncasecmp(this_recp, "room_", 5))
4292 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4294 /* Save room so we can restore it later */
4295 tempQR2 = CCC->room;
4298 /* Check permissions to send mail to this room */
4299 err = CtdlDoIHavePermissionToPostInThisRoom(
4304 0 /* 0 = not a reply */
4313 if (!IsEmptyStr(ret->recp_room)) {
4314 strcat(ret->recp_room, "|");
4316 strcat(ret->recp_room, &this_recp_cooked[5]);
4318 if (!IsEmptyStr(ret->recp_orgroom)) {
4319 strcat(ret->recp_orgroom, "|");
4321 strcat(ret->recp_orgroom, org_recp);
4325 /* Restore room in case something needs it */
4326 CCC->room = tempQR2;
4329 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4331 strcpy(this_recp, tempUS.fullname);
4332 if (!IsEmptyStr(ret->recp_local)) {
4333 strcat(ret->recp_local, "|");
4335 strcat(ret->recp_local, this_recp);
4337 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4339 strcpy(this_recp, tempUS.fullname);
4340 if (!IsEmptyStr(ret->recp_local)) {
4341 strcat(ret->recp_local, "|");
4343 strcat(ret->recp_local, this_recp);
4351 /* Yes, you're reading this correctly: if the target
4352 * domain points back to the local system or an attached
4353 * Citadel directory, the address is invalid. That's
4354 * because if the address were valid, we would have
4355 * already translated it to a local address by now.
4357 if (IsDirectory(this_recp, 0)) {
4362 ++ret->num_internet;
4363 if (!IsEmptyStr(ret->recp_internet)) {
4364 strcat(ret->recp_internet, "|");
4366 strcat(ret->recp_internet, this_recp);
4371 if (!IsEmptyStr(ret->recp_ignet)) {
4372 strcat(ret->recp_ignet, "|");
4374 strcat(ret->recp_ignet, this_recp);
4382 if (IsEmptyStr(errmsg)) {
4383 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4386 snprintf(append, sizeof append, "%s", errmsg);
4388 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4389 if (!IsEmptyStr(ret->errormsg)) {
4390 strcat(ret->errormsg, "; ");
4392 strcat(ret->errormsg, append);
4396 if (IsEmptyStr(ret->display_recp)) {
4397 strcpy(append, this_recp);
4400 snprintf(append, sizeof append, ", %s", this_recp);
4402 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4403 strcat(ret->display_recp, append);
4409 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4410 ret->num_room + ret->num_error) == 0) {
4411 ret->num_error = (-1);
4412 strcpy(ret->errormsg, "No recipients specified.");
4415 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4416 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4417 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4418 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4419 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4420 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4428 * Destructor for struct recptypes
4430 void free_recipients(struct recptypes *valid) {
4432 if (valid == NULL) {
4436 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4437 struct CitContext *CCC = CC;
4438 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4442 if (valid->errormsg != NULL) free(valid->errormsg);
4443 if (valid->recp_local != NULL) free(valid->recp_local);
4444 if (valid->recp_internet != NULL) free(valid->recp_internet);
4445 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4446 if (valid->recp_room != NULL) free(valid->recp_room);
4447 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4448 if (valid->display_recp != NULL) free(valid->display_recp);
4449 if (valid->bounce_to != NULL) free(valid->bounce_to);
4450 if (valid->envelope_from != NULL) free(valid->envelope_from);
4451 if (valid->sending_room != NULL) free(valid->sending_room);
4458 * message entry - mode 0 (normal)
4460 void cmd_ent0(char *entargs)
4462 struct CitContext *CCC = CC;
4467 char supplied_euid[128];
4469 int format_type = 0;
4470 char newusername[256];
4471 char newuseremail[256];
4472 struct CtdlMessage *msg;
4476 struct recptypes *valid = NULL;
4477 struct recptypes *valid_to = NULL;
4478 struct recptypes *valid_cc = NULL;
4479 struct recptypes *valid_bcc = NULL;
4481 int subject_required = 0;
4486 int newuseremail_ok = 0;
4487 char references[SIZ];
4492 post = extract_int(entargs, 0);
4493 extract_token(recp, entargs, 1, '|', sizeof recp);
4494 anon_flag = extract_int(entargs, 2);
4495 format_type = extract_int(entargs, 3);
4496 extract_token(subject, entargs, 4, '|', sizeof subject);
4497 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4498 do_confirm = extract_int(entargs, 6);
4499 extract_token(cc, entargs, 7, '|', sizeof cc);
4500 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4501 switch(CC->room.QRdefaultview) {
4504 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4507 supplied_euid[0] = 0;
4510 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4511 extract_token(references, entargs, 11, '|', sizeof references);
4512 for (ptr=references; *ptr != 0; ++ptr) {
4513 if (*ptr == '!') *ptr = '|';
4516 /* first check to make sure the request is valid. */
4518 err = CtdlDoIHavePermissionToPostInThisRoom(
4523 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4527 cprintf("%d %s\n", err, errmsg);
4531 /* Check some other permission type things. */
4533 if (IsEmptyStr(newusername)) {
4534 strcpy(newusername, CCC->user.fullname);
4536 if ( (CCC->user.axlevel < AxAideU)
4537 && (strcasecmp(newusername, CCC->user.fullname))
4538 && (strcasecmp(newusername, CCC->cs_inet_fn))
4540 cprintf("%d You don't have permission to author messages as '%s'.\n",
4541 ERROR + HIGHER_ACCESS_REQUIRED,
4548 if (IsEmptyStr(newuseremail)) {
4549 newuseremail_ok = 1;
4552 if (!IsEmptyStr(newuseremail)) {
4553 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4554 newuseremail_ok = 1;
4556 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4557 j = num_tokens(CCC->cs_inet_other_emails, '|');
4558 for (i=0; i<j; ++i) {
4559 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4560 if (!strcasecmp(newuseremail, buf)) {
4561 newuseremail_ok = 1;
4567 if (!newuseremail_ok) {
4568 cprintf("%d You don't have permission to author messages as '%s'.\n",
4569 ERROR + HIGHER_ACCESS_REQUIRED,
4575 CCC->cs_flags |= CS_POSTING;
4577 /* In mailbox rooms we have to behave a little differently --
4578 * make sure the user has specified at least one recipient. Then
4579 * validate the recipient(s). We do this for the Mail> room, as
4580 * well as any room which has the "Mailbox" view set - unless it
4581 * is the DRAFTS room which does not require recipients
4584 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4585 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4586 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4587 if (CCC->user.axlevel < AxProbU) {
4588 strcpy(recp, "sysop");
4593 valid_to = validate_recipients(recp, NULL, 0);
4594 if (valid_to->num_error > 0) {
4595 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4596 free_recipients(valid_to);
4600 valid_cc = validate_recipients(cc, NULL, 0);
4601 if (valid_cc->num_error > 0) {
4602 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4603 free_recipients(valid_to);
4604 free_recipients(valid_cc);
4608 valid_bcc = validate_recipients(bcc, NULL, 0);
4609 if (valid_bcc->num_error > 0) {
4610 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4611 free_recipients(valid_to);
4612 free_recipients(valid_cc);
4613 free_recipients(valid_bcc);
4617 /* Recipient required, but none were specified */
4618 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4619 free_recipients(valid_to);
4620 free_recipients(valid_cc);
4621 free_recipients(valid_bcc);
4622 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4626 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4627 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4628 cprintf("%d You do not have permission "
4629 "to send Internet mail.\n",
4630 ERROR + HIGHER_ACCESS_REQUIRED);
4631 free_recipients(valid_to);
4632 free_recipients(valid_cc);
4633 free_recipients(valid_bcc);
4638 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)
4639 && (CCC->user.axlevel < AxNetU) ) {
4640 cprintf("%d Higher access required for network mail.\n",
4641 ERROR + HIGHER_ACCESS_REQUIRED);
4642 free_recipients(valid_to);
4643 free_recipients(valid_cc);
4644 free_recipients(valid_bcc);
4648 if ((RESTRICT_INTERNET == 1)
4649 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4650 && ((CCC->user.flags & US_INTERNET) == 0)
4651 && (!CCC->internal_pgm)) {
4652 cprintf("%d You don't have access to Internet mail.\n",
4653 ERROR + HIGHER_ACCESS_REQUIRED);
4654 free_recipients(valid_to);
4655 free_recipients(valid_cc);
4656 free_recipients(valid_bcc);
4662 /* Is this a room which has anonymous-only or anonymous-option? */
4663 anonymous = MES_NORMAL;
4664 if (CCC->room.QRflags & QR_ANONONLY) {
4665 anonymous = MES_ANONONLY;
4667 if (CCC->room.QRflags & QR_ANONOPT) {
4668 if (anon_flag == 1) { /* only if the user requested it */
4669 anonymous = MES_ANONOPT;
4673 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4677 /* Recommend to the client that the use of a message subject is
4678 * strongly recommended in this room, if either the SUBJECTREQ flag
4679 * is set, or if there is one or more Internet email recipients.
4681 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4682 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4683 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4684 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4686 /* If we're only checking the validity of the request, return
4687 * success without creating the message.
4690 cprintf("%d %s|%d\n", CIT_OK,
4691 ((valid_to != NULL) ? valid_to->display_recp : ""),
4693 free_recipients(valid_to);
4694 free_recipients(valid_cc);
4695 free_recipients(valid_bcc);
4699 /* We don't need these anymore because we'll do it differently below */
4700 free_recipients(valid_to);
4701 free_recipients(valid_cc);
4702 free_recipients(valid_bcc);
4704 /* Read in the message from the client. */
4706 cprintf("%d send message\n", START_CHAT_MODE);
4708 cprintf("%d send message\n", SEND_LISTING);
4711 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4712 CCC->room.QRname, anonymous, format_type,
4713 newusername, newuseremail, subject,
4714 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4717 /* Put together one big recipients struct containing to/cc/bcc all in
4718 * one. This is for the envelope.
4720 char *all_recps = malloc(SIZ * 3);
4721 strcpy(all_recps, recp);
4722 if (!IsEmptyStr(cc)) {
4723 if (!IsEmptyStr(all_recps)) {
4724 strcat(all_recps, ",");
4726 strcat(all_recps, cc);
4728 if (!IsEmptyStr(bcc)) {
4729 if (!IsEmptyStr(all_recps)) {
4730 strcat(all_recps, ",");
4732 strcat(all_recps, bcc);
4734 if (!IsEmptyStr(all_recps)) {
4735 valid = validate_recipients(all_recps, NULL, 0);
4742 if ((valid != NULL) && (valid->num_room == 1))
4744 /* posting into an ML room? set the envelope from
4745 * to the actual mail address so others get a valid
4748 msg->cm_fields['V'] = strdup(valid->recp_orgroom);
4752 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4754 cprintf("%ld\n", msgnum);
4756 if (StrLength(CCC->StatusMessage) > 0) {
4757 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4759 else if (msgnum >= 0L) {
4760 client_write(HKEY("Message accepted.\n"));
4763 client_write(HKEY("Internal error.\n"));
4766 if (msg->cm_fields['E'] != NULL) {
4767 cprintf("%s\n", msg->cm_fields['E']);
4774 CtdlFreeMessage(msg);
4776 if (valid != NULL) {
4777 free_recipients(valid);
4785 * API function to delete messages which match a set of criteria
4786 * (returns the actual number of messages deleted)
4788 int CtdlDeleteMessages(char *room_name, /* which room */
4789 long *dmsgnums, /* array of msg numbers to be deleted */
4790 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4791 char *content_type /* or "" for any. regular expressions expected. */
4794 struct CitContext *CCC = CC;
4795 struct ctdlroom qrbuf;
4796 struct cdbdata *cdbfr;
4797 long *msglist = NULL;
4798 long *dellist = NULL;
4801 int num_deleted = 0;
4803 struct MetaData smi;
4806 int need_to_free_re = 0;
4808 if (content_type) if (!IsEmptyStr(content_type)) {
4809 regcomp(&re, content_type, 0);
4810 need_to_free_re = 1;
4812 MSG_syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4813 room_name, num_dmsgnums, content_type);
4815 /* get room record, obtaining a lock... */
4816 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4817 MSG_syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4819 if (need_to_free_re) regfree(&re);
4820 return (0); /* room not found */
4822 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4824 if (cdbfr != NULL) {
4825 dellist = malloc(cdbfr->len);
4826 msglist = (long *) cdbfr->ptr;
4827 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4828 num_msgs = cdbfr->len / sizeof(long);
4832 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4833 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4834 int have_more_del = 1;
4836 num_msgs = sort_msglist(msglist, num_msgs);
4837 if (num_dmsgnums > 1)
4838 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4841 StrBuf *dbg = NewStrBuf();
4842 for (i = 0; i < num_dmsgnums; i++)
4843 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4844 MSG_syslog(LOG_DEBUG, "Deleting before: %s", ChrPtr(dbg));
4849 while ((i < num_msgs) && (have_more_del)) {
4853 /* Set/clear a bit for each criterion */
4855 /* 0 messages in the list or a null list means that we are
4856 * interested in deleting any messages which meet the other criteria.
4859 delete_this |= 0x01;
4862 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
4863 if (msglist[i] == dmsgnums[j]) {
4864 delete_this |= 0x01;
4867 have_more_del = (j < num_dmsgnums);
4870 if (have_contenttype) {
4871 GetMetaData(&smi, msglist[i]);
4872 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4873 delete_this |= 0x02;
4876 delete_this |= 0x02;
4879 /* Delete message only if all bits are set */
4880 if (delete_this == 0x03) {
4881 dellist[num_deleted++] = msglist[i];
4888 StrBuf *dbg = NewStrBuf();
4889 for (i = 0; i < num_deleted; i++)
4890 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
4891 MSG_syslog(LOG_DEBUG, "Deleting: %s", ChrPtr(dbg));
4895 num_msgs = sort_msglist(msglist, num_msgs);
4896 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4897 msglist, (int)(num_msgs * sizeof(long)));
4900 qrbuf.QRhighest = msglist[num_msgs - 1];
4902 qrbuf.QRhighest = 0;
4904 CtdlPutRoomLock(&qrbuf);
4906 /* Go through the messages we pulled out of the index, and decrement
4907 * their reference counts by 1. If this is the only room the message
4908 * was in, the reference count will reach zero and the message will
4909 * automatically be deleted from the database. We do this in a
4910 * separate pass because there might be plug-in hooks getting called,
4911 * and we don't want that happening during an S_ROOMS critical
4915 for (i=0; i<num_deleted; ++i) {
4916 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4918 AdjRefCountList(dellist, num_deleted, -1);
4920 /* Now free the memory we used, and go away. */
4921 if (msglist != NULL) free(msglist);
4922 if (dellist != NULL) free(dellist);
4923 MSG_syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4924 if (need_to_free_re) regfree(&re);
4925 return (num_deleted);
4931 * Check whether the current user has permission to delete messages from
4932 * the current room (returns 1 for yes, 0 for no)
4934 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4936 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4937 if (ra & UA_DELETEALLOWED) return(1);
4945 * Delete message from current room
4947 void cmd_dele(char *args)
4956 extract_token(msgset, args, 0, '|', sizeof msgset);
4957 num_msgs = num_tokens(msgset, ',');
4959 cprintf("%d Nothing to do.\n", CIT_OK);
4963 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4964 cprintf("%d Higher access required.\n",
4965 ERROR + HIGHER_ACCESS_REQUIRED);
4970 * Build our message set to be moved/copied
4972 msgs = malloc(num_msgs * sizeof(long));
4973 for (i=0; i<num_msgs; ++i) {
4974 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4975 msgs[i] = atol(msgtok);
4978 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4982 cprintf("%d %d message%s deleted.\n", CIT_OK,
4983 num_deleted, ((num_deleted != 1) ? "s" : ""));
4985 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4993 * move or copy a message to another room
4995 void cmd_move(char *args)
5002 char targ[ROOMNAMELEN];
5003 struct ctdlroom qtemp;
5010 extract_token(msgset, args, 0, '|', sizeof msgset);
5011 num_msgs = num_tokens(msgset, ',');
5013 cprintf("%d Nothing to do.\n", CIT_OK);
5017 extract_token(targ, args, 1, '|', sizeof targ);
5018 convert_room_name_macros(targ, sizeof targ);
5019 targ[ROOMNAMELEN - 1] = 0;
5020 is_copy = extract_int(args, 2);
5022 if (CtdlGetRoom(&qtemp, targ) != 0) {
5023 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
5027 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
5028 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
5032 CtdlGetUser(&CC->user, CC->curr_user);
5033 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
5035 /* Check for permission to perform this operation.
5036 * Remember: "CC->room" is source, "qtemp" is target.
5040 /* Admins can move/copy */
5041 if (CC->user.axlevel >= AxAideU) permit = 1;
5043 /* Room aides can move/copy */
5044 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
5046 /* Permit move/copy from personal rooms */
5047 if ((CC->room.QRflags & QR_MAILBOX)
5048 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5050 /* Permit only copy from public to personal room */
5052 && (!(CC->room.QRflags & QR_MAILBOX))
5053 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5055 /* Permit message removal from collaborative delete rooms */
5056 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5058 /* Users allowed to post into the target room may move into it too. */
5059 if ((CC->room.QRflags & QR_MAILBOX) &&
5060 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
5062 /* User must have access to target room */
5063 if (!(ra & UA_KNOWN)) permit = 0;
5066 cprintf("%d Higher access required.\n",
5067 ERROR + HIGHER_ACCESS_REQUIRED);
5072 * Build our message set to be moved/copied
5074 msgs = malloc(num_msgs * sizeof(long));
5075 for (i=0; i<num_msgs; ++i) {
5076 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5077 msgs[i] = atol(msgtok);
5083 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5085 cprintf("%d Cannot store message(s) in %s: error %d\n",
5091 /* Now delete the message from the source room,
5092 * if this is a 'move' rather than a 'copy' operation.
5095 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5099 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5105 * GetMetaData() - Get the supplementary record for a message
5107 void GetMetaData(struct MetaData *smibuf, long msgnum)
5110 struct cdbdata *cdbsmi;
5113 memset(smibuf, 0, sizeof(struct MetaData));
5114 smibuf->meta_msgnum = msgnum;
5115 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5117 /* Use the negative of the message number for its supp record index */
5118 TheIndex = (0L - msgnum);
5120 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5121 if (cdbsmi == NULL) {
5122 return; /* record not found; go with defaults */
5124 memcpy(smibuf, cdbsmi->ptr,
5125 ((cdbsmi->len > sizeof(struct MetaData)) ?
5126 sizeof(struct MetaData) : cdbsmi->len));
5133 * PutMetaData() - (re)write supplementary record for a message
5135 void PutMetaData(struct MetaData *smibuf)
5139 /* Use the negative of the message number for the metadata db index */
5140 TheIndex = (0L - smibuf->meta_msgnum);
5142 cdb_store(CDB_MSGMAIN,
5143 &TheIndex, (int)sizeof(long),
5144 smibuf, (int)sizeof(struct MetaData));
5149 * AdjRefCount - submit an adjustment to the reference count for a message.
5150 * (These are just queued -- we actually process them later.)
5152 void AdjRefCount(long msgnum, int incr)
5154 struct CitContext *CCC = CC;
5155 struct arcq new_arcq;
5158 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5160 begin_critical_section(S_SUPPMSGMAIN);
5161 if (arcfp == NULL) {
5162 arcfp = fopen(file_arcq, "ab+");
5163 chown(file_arcq, CTDLUID, (-1));
5164 chmod(file_arcq, 0600);
5166 end_critical_section(S_SUPPMSGMAIN);
5168 /* msgnum < 0 means that we're trying to close the file */
5170 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5171 begin_critical_section(S_SUPPMSGMAIN);
5172 if (arcfp != NULL) {
5176 end_critical_section(S_SUPPMSGMAIN);
5181 * If we can't open the queue, perform the operation synchronously.
5183 if (arcfp == NULL) {
5184 TDAP_AdjRefCount(msgnum, incr);
5188 new_arcq.arcq_msgnum = msgnum;
5189 new_arcq.arcq_delta = incr;
5190 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5192 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5201 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5203 struct CitContext *CCC = CC;
5204 long i, the_size, offset;
5205 struct arcq *new_arcq;
5208 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5210 begin_critical_section(S_SUPPMSGMAIN);
5211 if (arcfp == NULL) {
5212 arcfp = fopen(file_arcq, "ab+");
5213 chown(file_arcq, CTDLUID, (-1));
5214 chmod(file_arcq, 0600);
5216 end_critical_section(S_SUPPMSGMAIN);
5219 * If we can't open the queue, perform the operation synchronously.
5221 if (arcfp == NULL) {
5222 for (i = 0; i < nmsg; i++)
5223 TDAP_AdjRefCount(msgnum[i], incr);
5227 the_size = sizeof(struct arcq) * nmsg;
5228 new_arcq = malloc(the_size);
5229 for (i = 0; i < nmsg; i++) {
5230 new_arcq[i].arcq_msgnum = msgnum[i];
5231 new_arcq[i].arcq_delta = incr;
5235 while ((rv >= 0) && (offset < the_size))
5237 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5239 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5255 * TDAP_ProcessAdjRefCountQueue()
5257 * Process the queue of message count adjustments that was created by calls
5258 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5259 * for each one. This should be an "off hours" operation.
5261 int TDAP_ProcessAdjRefCountQueue(void)
5263 struct CitContext *CCC = CC;
5264 char file_arcq_temp[PATH_MAX];
5267 struct arcq arcq_rec;
5268 int num_records_processed = 0;
5270 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5272 begin_critical_section(S_SUPPMSGMAIN);
5273 if (arcfp != NULL) {
5278 r = link(file_arcq, file_arcq_temp);
5280 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5281 end_critical_section(S_SUPPMSGMAIN);
5282 return(num_records_processed);
5286 end_critical_section(S_SUPPMSGMAIN);
5288 fp = fopen(file_arcq_temp, "rb");
5290 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5291 return(num_records_processed);
5294 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5295 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5296 ++num_records_processed;
5300 r = unlink(file_arcq_temp);
5302 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5305 return(num_records_processed);
5311 * TDAP_AdjRefCount - adjust the reference count for a message.
5312 * This one does it "for real" because it's called by
5313 * the autopurger function that processes the queue
5314 * created by AdjRefCount(). If a message's reference
5315 * count becomes zero, we also delete the message from
5316 * disk and de-index it.
5318 void TDAP_AdjRefCount(long msgnum, int incr)
5320 struct CitContext *CCC = CC;
5322 struct MetaData smi;
5325 /* This is a *tight* critical section; please keep it that way, as
5326 * it may get called while nested in other critical sections.
5327 * Complicating this any further will surely cause deadlock!
5329 begin_critical_section(S_SUPPMSGMAIN);
5330 GetMetaData(&smi, msgnum);
5331 smi.meta_refcount += incr;
5333 end_critical_section(S_SUPPMSGMAIN);
5334 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5335 msgnum, incr, smi.meta_refcount
5338 /* If the reference count is now zero, delete the message
5339 * (and its supplementary record as well).
5341 if (smi.meta_refcount == 0) {
5342 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5344 /* Call delete hooks with NULL room to show it has gone altogether */
5345 PerformDeleteHooks(NULL, msgnum);
5347 /* Remove from message base */
5349 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5350 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5352 /* Remove metadata record */
5353 delnum = (0L - msgnum);
5354 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5360 * Write a generic object to this room
5362 * Note: this could be much more efficient. Right now we use two temporary
5363 * files, and still pull the message into memory as with all others.
5365 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5366 char *content_type, /* MIME type of this object */
5367 char *raw_message, /* Data to be written */
5368 off_t raw_length, /* Size of raw_message */
5369 struct ctdluser *is_mailbox, /* Mailbox room? */
5370 int is_binary, /* Is encoding necessary? */
5371 int is_unique, /* Del others of this type? */
5372 unsigned int flags /* Internal save flags */
5375 struct CitContext *CCC = CC;
5376 struct ctdlroom qrbuf;
5377 char roomname[ROOMNAMELEN];
5378 struct CtdlMessage *msg;
5379 char *encoded_message = NULL;
5381 if (is_mailbox != NULL) {
5382 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5385 safestrncpy(roomname, req_room, sizeof(roomname));
5388 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5391 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5394 encoded_message = malloc((size_t)(raw_length + 4096));
5397 sprintf(encoded_message, "Content-type: %s\n", content_type);
5400 sprintf(&encoded_message[strlen(encoded_message)],
5401 "Content-transfer-encoding: base64\n\n"
5405 sprintf(&encoded_message[strlen(encoded_message)],
5406 "Content-transfer-encoding: 7bit\n\n"
5412 &encoded_message[strlen(encoded_message)],
5420 &encoded_message[strlen(encoded_message)],
5426 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5427 msg = malloc(sizeof(struct CtdlMessage));
5428 memset(msg, 0, sizeof(struct CtdlMessage));
5429 msg->cm_magic = CTDLMESSAGE_MAGIC;
5430 msg->cm_anon_type = MES_NORMAL;
5431 msg->cm_format_type = 4;
5432 msg->cm_fields['A'] = strdup(CCC->user.fullname);
5433 msg->cm_fields['O'] = strdup(req_room);
5434 msg->cm_fields['N'] = strdup(config.c_nodename);
5435 msg->cm_fields['H'] = strdup(config.c_humannode);
5436 msg->cm_flags = flags;
5438 msg->cm_fields['M'] = encoded_message;
5440 /* Create the requested room if we have to. */
5441 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5442 CtdlCreateRoom(roomname,
5443 ( (is_mailbox != NULL) ? 5 : 3 ),
5444 "", 0, 1, 0, VIEW_BBS);
5446 /* If the caller specified this object as unique, delete all
5447 * other objects of this type that are currently in the room.
5450 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5451 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5454 /* Now write the data */
5455 CtdlSubmitMsg(msg, NULL, roomname, 0);
5456 CtdlFreeMessage(msg);
5464 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5465 config_msgnum = msgnum;
5469 char *CtdlGetSysConfig(char *sysconfname) {
5470 char hold_rm[ROOMNAMELEN];
5473 struct CtdlMessage *msg;
5476 strcpy(hold_rm, CC->room.QRname);
5477 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5478 CtdlGetRoom(&CC->room, hold_rm);
5483 /* We want the last (and probably only) config in this room */
5484 begin_critical_section(S_CONFIG);
5485 config_msgnum = (-1L);
5486 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5487 CtdlGetSysConfigBackend, NULL);
5488 msgnum = config_msgnum;
5489 end_critical_section(S_CONFIG);
5495 msg = CtdlFetchMessage(msgnum, 1);
5497 conf = strdup(msg->cm_fields['M']);
5498 CtdlFreeMessage(msg);
5505 CtdlGetRoom(&CC->room, hold_rm);
5507 if (conf != NULL) do {
5508 extract_token(buf, conf, 0, '\n', sizeof buf);
5509 strcpy(conf, &conf[strlen(buf)+1]);
5510 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5516 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5517 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5522 * Determine whether a given Internet address belongs to the current user
5524 int CtdlIsMe(char *addr, int addr_buf_len)
5526 struct recptypes *recp;
5529 recp = validate_recipients(addr, NULL, 0);
5530 if (recp == NULL) return(0);
5532 if (recp->num_local == 0) {
5533 free_recipients(recp);
5537 for (i=0; i<recp->num_local; ++i) {
5538 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5539 if (!strcasecmp(addr, CC->user.fullname)) {
5540 free_recipients(recp);
5545 free_recipients(recp);
5551 * Citadel protocol command to do the same
5553 void cmd_isme(char *argbuf) {
5556 if (CtdlAccessCheck(ac_logged_in)) return;
5557 extract_token(addr, argbuf, 0, '|', sizeof addr);
5559 if (CtdlIsMe(addr, sizeof addr)) {
5560 cprintf("%d %s\n", CIT_OK, addr);
5563 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5569 /*****************************************************************************/
5570 /* MODULE INITIALIZATION STUFF */
5571 /*****************************************************************************/
5572 void SetMessageDebugEnabled(const int n)
5574 MessageDebugEnabled = n;
5576 CTDL_MODULE_INIT(msgbase)
5579 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5581 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5582 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5583 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5584 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5585 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5586 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5587 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5588 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5589 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5590 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5591 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5592 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5595 /* return our Subversion id for the Log */