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;
3024 int n = sizeof(FORDER) - 1;
3025 long lengths[sizeof(FORDER)];
3027 memset(lengths, 0, sizeof(lengths));
3030 * Check for valid message format
3032 if (is_valid_message(msg) == 0) {
3033 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
3041 if (msg->cm_fields[(int)forder[i]] != NULL)
3043 lengths[i] = strlen(msg->cm_fields[(int)forder[i]]);
3044 ret->len += lengths[i] + 2;
3047 ret->ser = malloc(ret->len);
3048 if (ret->ser == NULL) {
3049 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3050 (long)ret->len, strerror(errno));
3057 ret->ser[1] = msg->cm_anon_type;
3058 ret->ser[2] = msg->cm_format_type;
3062 if (msg->cm_fields[(int)forder[i]] != NULL)
3064 fieldlen = lengths[i];
3065 ret->ser[wlen++] = (char)forder[i];
3067 memcpy(&ret->ser[wlen],
3068 msg->cm_fields[(int)forder[i]],
3071 wlen = wlen + fieldlen + 1;
3074 if (ret->len != wlen) {
3075 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
3076 (long)ret->len, (long)wlen);
3084 * Check to see if any messages already exist in the current room which
3085 * carry the same Exclusive ID as this one. If any are found, delete them.
3087 void ReplicationChecks(struct CtdlMessage *msg) {
3088 struct CitContext *CCC = CC;
3089 long old_msgnum = (-1L);
3091 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3093 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3096 /* No exclusive id? Don't do anything. */
3097 if (msg == NULL) return;
3098 if (msg->cm_fields['E'] == NULL) return;
3099 if (IsEmptyStr(msg->cm_fields['E'])) return;
3100 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3101 msg->cm_fields['E'], CCC->room.QRname);*/
3103 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CCC->room);
3104 if (old_msgnum > 0L) {
3105 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3106 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3113 * Save a message to disk and submit it into the delivery system.
3115 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3116 struct recptypes *recps, /* recipients (if mail) */
3117 const char *force, /* force a particular room? */
3118 int flags /* should the message be exported clean? */
3121 char submit_filename[128];
3122 char generated_timestamp[32];
3123 char hold_rm[ROOMNAMELEN];
3124 char actual_rm[ROOMNAMELEN];
3125 char force_room[ROOMNAMELEN];
3126 char content_type[SIZ]; /* We have to learn this */
3127 char recipient[SIZ];
3130 const char *mptr = NULL;
3131 struct ctdluser userbuf;
3133 struct MetaData smi;
3134 FILE *network_fp = NULL;
3135 static int seqnum = 1;
3136 struct CtdlMessage *imsg = NULL;
3138 size_t instr_alloc = 0;
3140 char *hold_R, *hold_D;
3141 char *collected_addresses = NULL;
3142 struct addresses_to_be_filed *aptr = NULL;
3143 StrBuf *saved_rfc822_version = NULL;
3144 int qualified_for_journaling = 0;
3145 CitContext *CCC = MyContext();
3146 char bounce_to[1024] = "";
3149 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3150 if (is_valid_message(msg) == 0) return(-1); /* self check */
3152 /* If this message has no timestamp, we take the liberty of
3153 * giving it one, right now.
3155 if (msg->cm_fields['T'] == NULL) {
3156 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3157 msg->cm_fields['T'] = strdup(generated_timestamp);
3160 /* If this message has no path, we generate one.
3162 if (msg->cm_fields['P'] == NULL) {
3163 if (msg->cm_fields['A'] != NULL) {
3164 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3165 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3166 if (isspace(msg->cm_fields['P'][a])) {
3167 msg->cm_fields['P'][a] = ' ';
3172 msg->cm_fields['P'] = strdup("unknown");
3176 if (force == NULL) {
3177 strcpy(force_room, "");
3180 strcpy(force_room, force);
3183 /* Learn about what's inside, because it's what's inside that counts */
3184 if (msg->cm_fields['M'] == NULL) {
3185 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3189 switch (msg->cm_format_type) {
3191 strcpy(content_type, "text/x-citadel-variformat");
3194 strcpy(content_type, "text/plain");
3197 strcpy(content_type, "text/plain");
3198 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3201 safestrncpy(content_type, &mptr[13], sizeof content_type);
3202 striplt(content_type);
3203 aptr = content_type;
3204 while (!IsEmptyStr(aptr)) {
3216 /* Goto the correct room */
3217 room = (recps) ? CCC->room.QRname : SENTITEMS;
3218 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3219 strcpy(hold_rm, CCC->room.QRname);
3220 strcpy(actual_rm, CCC->room.QRname);
3221 if (recps != NULL) {
3222 strcpy(actual_rm, SENTITEMS);
3225 /* If the user is a twit, move to the twit room for posting */
3227 if (CCC->user.axlevel == AxProbU) {
3228 strcpy(hold_rm, actual_rm);
3229 strcpy(actual_rm, config.c_twitroom);
3230 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3234 /* ...or if this message is destined for Aide> then go there. */
3235 if (!IsEmptyStr(force_room)) {
3236 strcpy(actual_rm, force_room);
3239 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3240 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3241 /* CtdlGetRoom(&CCC->room, actual_rm); */
3242 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3246 * If this message has no O (room) field, generate one.
3248 if (msg->cm_fields['O'] == NULL) {
3249 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3252 /* Perform "before save" hooks (aborting if any return nonzero) */
3253 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3254 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3257 * If this message has an Exclusive ID, and the room is replication
3258 * checking enabled, then do replication checks.
3260 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3261 ReplicationChecks(msg);
3264 /* Save it to disk */
3265 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3266 newmsgid = send_message(msg);
3267 if (newmsgid <= 0L) return(-5);
3269 /* Write a supplemental message info record. This doesn't have to
3270 * be a critical section because nobody else knows about this message
3273 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3274 memset(&smi, 0, sizeof(struct MetaData));
3275 smi.meta_msgnum = newmsgid;
3276 smi.meta_refcount = 0;
3277 safestrncpy(smi.meta_content_type, content_type,
3278 sizeof smi.meta_content_type);
3281 * Measure how big this message will be when rendered as RFC822.
3282 * We do this for two reasons:
3283 * 1. We need the RFC822 length for the new metadata record, so the
3284 * POP and IMAP services don't have to calculate message lengths
3285 * while the user is waiting (multiplied by potentially hundreds
3286 * or thousands of messages).
3287 * 2. If journaling is enabled, we will need an RFC822 version of the
3288 * message to attach to the journalized copy.
3290 if (CCC->redirect_buffer != NULL) {
3291 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3294 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3295 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3296 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3297 saved_rfc822_version = CCC->redirect_buffer;
3298 CCC->redirect_buffer = NULL;
3302 /* Now figure out where to store the pointers */
3303 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3305 /* If this is being done by the networker delivering a private
3306 * message, we want to BYPASS saving the sender's copy (because there
3307 * is no local sender; it would otherwise go to the Trashcan).
3309 if ((!CCC->internal_pgm) || (recps == NULL)) {
3310 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3311 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3312 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3316 /* For internet mail, drop a copy in the outbound queue room */
3317 if ((recps != NULL) && (recps->num_internet > 0)) {
3318 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3321 /* If other rooms are specified, drop them there too. */
3322 if ((recps != NULL) && (recps->num_room > 0))
3323 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3324 extract_token(recipient, recps->recp_room, i,
3325 '|', sizeof recipient);
3326 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3327 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3330 /* Bump this user's messages posted counter. */
3331 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3332 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3333 CCC->user.posted = CCC->user.posted + 1;
3334 CtdlPutUserLock(&CCC->user);
3336 /* Decide where bounces need to be delivered */
3337 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3338 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3340 else if (CCC->logged_in) {
3341 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3344 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3347 /* If this is private, local mail, make a copy in the
3348 * recipient's mailbox and bump the reference count.
3350 if ((recps != NULL) && (recps->num_local > 0))
3351 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3352 extract_token(recipient, recps->recp_local, i,
3353 '|', sizeof recipient);
3354 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3356 if (CtdlGetUser(&userbuf, recipient) == 0) {
3357 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3358 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3359 CtdlBumpNewMailCounter(userbuf.usernum);
3360 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3361 /* Generate a instruction message for the Funambol notification
3362 * server, in the same style as the SMTP queue
3365 instr = malloc(instr_alloc);
3366 snprintf(instr, instr_alloc,
3367 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3369 SPOOLMIME, newmsgid, (long)time(NULL),
3373 imsg = malloc(sizeof(struct CtdlMessage));
3374 memset(imsg, 0, sizeof(struct CtdlMessage));
3375 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3376 imsg->cm_anon_type = MES_NORMAL;
3377 imsg->cm_format_type = FMT_RFC822;
3378 imsg->cm_fields['U'] = strdup("QMSG");
3379 imsg->cm_fields['A'] = strdup("Citadel");
3380 imsg->cm_fields['J'] = strdup("do not journal");
3381 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3382 imsg->cm_fields['2'] = strdup(recipient);
3383 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3384 CtdlFreeMessage(imsg);
3388 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3389 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3393 /* Perform "after save" hooks */
3394 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3395 if (msg->cm_fields['3'] != NULL) free(msg->cm_fields['3']);
3396 msg->cm_fields['3'] = malloc(20);
3397 snprintf(msg->cm_fields['3'], 20, "%ld", newmsgid);
3398 PerformMessageHooks(msg, EVT_AFTERSAVE);
3399 free(msg->cm_fields['3']);
3400 msg->cm_fields['3'] = NULL;
3402 /* For IGnet mail, we have to save a new copy into the spooler for
3403 * each recipient, with the R and D fields set to the recipient and
3404 * destination-node. This has two ugly side effects: all other
3405 * recipients end up being unlisted in this recipient's copy of the
3406 * message, and it has to deliver multiple messages to the same
3407 * node. We'll revisit this again in a year or so when everyone has
3408 * a network spool receiver that can handle the new style messages.
3410 if ((recps != NULL) && (recps->num_ignet > 0))
3411 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3412 extract_token(recipient, recps->recp_ignet, i,
3413 '|', sizeof recipient);
3415 hold_R = msg->cm_fields['R'];
3416 hold_D = msg->cm_fields['D'];
3417 msg->cm_fields['R'] = malloc(SIZ);
3418 msg->cm_fields['D'] = malloc(128);
3419 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3420 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3422 serialize_message(&smr, msg);
3424 snprintf(submit_filename, sizeof submit_filename,
3425 "%s/netmail.%04lx.%04x.%04x",
3427 (long) getpid(), CCC->cs_pid, ++seqnum);
3428 network_fp = fopen(submit_filename, "wb+");
3429 if (network_fp != NULL) {
3430 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3432 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3440 free(msg->cm_fields['R']);
3441 free(msg->cm_fields['D']);
3442 msg->cm_fields['R'] = hold_R;
3443 msg->cm_fields['D'] = hold_D;
3446 /* Go back to the room we started from */
3447 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3448 if (strcasecmp(hold_rm, CCC->room.QRname))
3449 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3451 /* For internet mail, generate delivery instructions.
3452 * Yes, this is recursive. Deal with it. Infinite recursion does
3453 * not happen because the delivery instructions message does not
3454 * contain a recipient.
3456 if ((recps != NULL) && (recps->num_internet > 0)) {
3457 StrBuf *SpoolMsg = NewStrBuf();
3460 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3462 StrBufPrintf(SpoolMsg,
3463 "Content-type: "SPOOLMIME"\n"
3472 if (recps->envelope_from != NULL) {
3473 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3474 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3475 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3477 if (recps->sending_room != NULL) {
3478 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3479 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3480 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3483 nTokens = num_tokens(recps->recp_internet, '|');
3484 for (i = 0; i < nTokens; i++) {
3486 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3488 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3489 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3490 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3494 imsg = malloc(sizeof(struct CtdlMessage));
3495 memset(imsg, 0, sizeof(struct CtdlMessage));
3496 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3497 imsg->cm_anon_type = MES_NORMAL;
3498 imsg->cm_format_type = FMT_RFC822;
3499 imsg->cm_fields['U'] = strdup("QMSG");
3500 imsg->cm_fields['A'] = strdup("Citadel");
3501 imsg->cm_fields['J'] = strdup("do not journal");
3502 imsg->cm_fields['M'] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3503 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3504 CtdlFreeMessage(imsg);
3508 * Any addresses to harvest for someone's address book?
3510 if ( (CCC->logged_in) && (recps != NULL) ) {
3511 collected_addresses = harvest_collected_addresses(msg);
3514 if (collected_addresses != NULL) {
3515 aptr = (struct addresses_to_be_filed *)
3516 malloc(sizeof(struct addresses_to_be_filed));
3517 CtdlMailboxName(actual_rm, sizeof actual_rm,
3518 &CCC->user, USERCONTACTSROOM);
3519 aptr->roomname = strdup(actual_rm);
3520 aptr->collected_addresses = collected_addresses;
3521 begin_critical_section(S_ATBF);
3524 end_critical_section(S_ATBF);
3528 * Determine whether this message qualifies for journaling.
3530 if (msg->cm_fields['J'] != NULL) {
3531 qualified_for_journaling = 0;
3534 if (recps == NULL) {
3535 qualified_for_journaling = config.c_journal_pubmsgs;
3537 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3538 qualified_for_journaling = config.c_journal_email;
3541 qualified_for_journaling = config.c_journal_pubmsgs;
3546 * Do we have to perform journaling? If so, hand off the saved
3547 * RFC822 version will be handed off to the journaler for background
3548 * submit. Otherwise, we have to free the memory ourselves.
3550 if (saved_rfc822_version != NULL) {
3551 if (qualified_for_journaling) {
3552 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3555 FreeStrBuf(&saved_rfc822_version);
3565 * Convenience function for generating small administrative messages.
3567 void quickie_message(const char *from,
3568 const char *fromaddr,
3573 const char *subject)
3575 struct CtdlMessage *msg;
3576 struct recptypes *recp = NULL;
3578 msg = malloc(sizeof(struct CtdlMessage));
3579 memset(msg, 0, sizeof(struct CtdlMessage));
3580 msg->cm_magic = CTDLMESSAGE_MAGIC;
3581 msg->cm_anon_type = MES_NORMAL;
3582 msg->cm_format_type = format_type;
3585 msg->cm_fields['A'] = strdup(from);
3587 else if (fromaddr != NULL) {
3588 msg->cm_fields['A'] = strdup(fromaddr);
3589 if (strchr(msg->cm_fields['A'], '@')) {
3590 *strchr(msg->cm_fields['A'], '@') = 0;
3594 msg->cm_fields['A'] = strdup("Citadel");
3597 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3598 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3599 msg->cm_fields['N'] = strdup(NODENAME);
3601 msg->cm_fields['R'] = strdup(to);
3602 recp = validate_recipients(to, NULL, 0);
3604 if (subject != NULL) {
3605 msg->cm_fields['U'] = strdup(subject);
3607 msg->cm_fields['M'] = strdup(text);
3609 CtdlSubmitMsg(msg, recp, room, 0);
3610 CtdlFreeMessage(msg);
3611 if (recp != NULL) free_recipients(recp);
3614 void flood_protect_quickie_message(const char *from,
3615 const char *fromaddr,
3620 const char *subject,
3622 const char **CritStr,
3629 u_char rawdigest[MD5_DIGEST_LEN];
3630 struct MD5Context md5context;
3634 time_t tsday = NOW / (8*60*60); /* just care for a day... */
3636 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3637 MD5Init(&md5context);
3639 for (i = 0; i < nCriterions; i++)
3640 MD5Update(&md5context,
3641 (const unsigned char*)CritStr[i], CritStrLen[i]);
3642 MD5Update(&md5context,
3643 (const unsigned char*)timestamp, tslen);
3644 MD5Final(rawdigest, &md5context);
3646 guid = NewStrBufPlain(NULL,
3647 MD5_DIGEST_LEN * 2 + 12);
3648 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3649 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3650 if (StrLength(guid) > 40)
3651 StrBufCutAt(guid, 40, NULL);
3653 if (CheckIfAlreadySeen("FPAideMessage",
3662 /* yes, we did. flood protection kicks in. */
3664 "not sending message again\n");
3668 /* no, this message isn't sent recently; go ahead. */
3669 quickie_message(from,
3680 * Back end function used by CtdlMakeMessage() and similar functions
3682 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3684 size_t maxlen, /* maximum message length */
3685 StrBuf *exist, /* if non-null, append to it;
3686 exist is ALWAYS freed */
3687 int crlf, /* CRLF newlines instead of LF */
3688 int *sock /* socket handle or 0 for this session's client socket */
3697 LineBuf = NewStrBufPlain(NULL, SIZ);
3698 if (exist == NULL) {
3699 Message = NewStrBufPlain(NULL, 4 * SIZ);
3702 Message = NewStrBufDup(exist);
3705 /* Do we need to change leading ".." to "." for SMTP escaping? */
3706 if ((tlen == 1) && (*terminator == '.')) {
3710 /* read in the lines of message text one by one */
3713 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3718 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3720 if ((StrLength(LineBuf) == tlen) &&
3721 (!strcmp(ChrPtr(LineBuf), terminator)))
3724 if ( (!flushing) && (!finished) ) {
3726 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3729 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3732 /* Unescape SMTP-style input of two dots at the beginning of the line */
3734 (StrLength(LineBuf) == 2) &&
3735 (!strcmp(ChrPtr(LineBuf), "..")))
3737 StrBufCutLeft(LineBuf, 1);
3740 StrBufAppendBuf(Message, LineBuf, 0);
3743 /* if we've hit the max msg length, flush the rest */
3744 if (StrLength(Message) >= maxlen) flushing = 1;
3746 } while (!finished);
3747 FreeStrBuf(&LineBuf);
3751 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3755 FreeStrBuf(&(*Msg)->MsgBuf);
3761 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3763 size_t maxlen, /* maximum message length */
3764 size_t expectlen, /* if we expect a message, how long should it be? */
3765 StrBuf *exist, /* if non-null, append to it;
3766 exist is ALWAYS freed */
3767 long eLen, /* length of exist */
3768 int crlf /* CRLF newlines instead of LF */
3771 ReadAsyncMsg *NewMsg;
3773 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3774 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3776 if (exist == NULL) {
3779 if (expectlen == 0) {
3783 len = expectlen + 10;
3785 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3788 NewMsg->MsgBuf = NewStrBufDup(exist);
3790 /* Do we need to change leading ".." to "." for SMTP escaping? */
3791 if ((tlen == 1) && (*terminator == '.')) {
3795 NewMsg->terminator = terminator;
3796 NewMsg->tlen = tlen;
3798 NewMsg->maxlen = maxlen;
3800 NewMsg->crlf = crlf;
3806 * Back end function used by CtdlMakeMessage() and similar functions
3808 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3810 ReadAsyncMsg *ReadMsg;
3811 int MsgFinished = 0;
3812 eReadState Finished = eMustReadMore;
3817 const char *pch = ChrPtr(IO->SendBuf.Buf);
3818 const char *pchh = IO->SendBuf.ReadWritePointer;
3824 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3825 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3826 ((CitContext*)(IO->CitContext))->ServiceName,
3829 fd = fopen(fn, "a+");
3832 ReadMsg = IO->ReadMsg;
3834 /* read in the lines of message text one by one */
3836 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3839 case eMustReadMore: /// read new from socket...
3841 if (IO->RecvBuf.ReadWritePointer != NULL) {
3842 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3843 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3845 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3849 fprintf(fd, "BufferEmpty! \n");
3855 case eBufferNotEmpty: /* shouldn't happen... */
3856 case eReadSuccess: /// done for now...
3858 case eReadFail: /// WHUT?
3864 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3865 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3868 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3871 else if (!ReadMsg->flushing) {
3874 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3877 /* Unescape SMTP-style input of two dots at the beginning of the line */
3878 if ((ReadMsg->dodot) &&
3879 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3880 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3883 fprintf(fd, "UnEscaped!\n");
3885 StrBufCutLeft(IO->IOBuf, 1);
3888 if (ReadMsg->crlf) {
3889 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3892 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3895 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3898 /* if we've hit the max msg length, flush the rest */
3899 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3901 } while (!MsgFinished);
3904 fprintf(fd, "Done with reading; %s.\n, ",
3905 (MsgFinished)?"Message Finished": "FAILED");
3909 return eReadSuccess;
3916 * Back end function used by CtdlMakeMessage() and similar functions
3918 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3920 size_t maxlen, /* maximum message length */
3921 StrBuf *exist, /* if non-null, append to it;
3922 exist is ALWAYS freed */
3923 int crlf, /* CRLF newlines instead of LF */
3924 int *sock /* socket handle or 0 for this session's client socket */
3929 Message = CtdlReadMessageBodyBuf(terminator,
3935 if (Message == NULL)
3938 return SmashStrBuf(&Message);
3943 * Build a binary message to be saved on disk.
3944 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3945 * will become part of the message. This means you are no longer
3946 * responsible for managing that memory -- it will be freed along with
3947 * the rest of the fields when CtdlFreeMessage() is called.)
3950 struct CtdlMessage *CtdlMakeMessage(
3951 struct ctdluser *author, /* author's user structure */
3952 char *recipient, /* NULL if it's not mail */
3953 char *recp_cc, /* NULL if it's not mail */
3954 char *room, /* room where it's going */
3955 int type, /* see MES_ types in header file */
3956 int format_type, /* variformat, plain text, MIME... */
3957 char *fake_name, /* who we're masquerading as */
3958 char *my_email, /* which of my email addresses to use (empty is ok) */
3959 char *subject, /* Subject (optional) */
3960 char *supplied_euid, /* ...or NULL if this is irrelevant */
3961 char *preformatted_text, /* ...or NULL to read text from client */
3962 char *references /* Thread references */
3964 char dest_node[256];
3966 struct CtdlMessage *msg;
3968 StrBuf *FakeEncAuthor = NULL;
3970 msg = malloc(sizeof(struct CtdlMessage));
3971 memset(msg, 0, sizeof(struct CtdlMessage));
3972 msg->cm_magic = CTDLMESSAGE_MAGIC;
3973 msg->cm_anon_type = type;
3974 msg->cm_format_type = format_type;
3976 /* Don't confuse the poor folks if it's not routed mail. */
3977 strcpy(dest_node, "");
3979 if (recipient != NULL) striplt(recipient);
3980 if (recp_cc != NULL) striplt(recp_cc);
3982 /* Path or Return-Path */
3983 if (my_email == NULL) my_email = "";
3985 if (!IsEmptyStr(my_email)) {
3986 msg->cm_fields['P'] = strdup(my_email);
3989 snprintf(buf, sizeof buf, "%s", author->fullname);
3990 msg->cm_fields['P'] = strdup(buf);
3992 convert_spaces_to_underscores(msg->cm_fields['P']);
3994 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3995 msg->cm_fields['T'] = strdup(buf);
3997 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3998 FakeAuthor = NewStrBufPlain (fake_name, -1);
4001 FakeAuthor = NewStrBufPlain (author->fullname, -1);
4003 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
4004 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
4005 FreeStrBuf(&FakeAuthor);
4007 if (CC->room.QRflags & QR_MAILBOX) { /* room */
4008 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
4011 msg->cm_fields['O'] = strdup(CC->room.QRname);
4014 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
4015 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
4017 if ((recipient != NULL) && (recipient[0] != 0)) {
4018 msg->cm_fields['R'] = strdup(recipient);
4020 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
4021 msg->cm_fields['Y'] = strdup(recp_cc);
4023 if (dest_node[0] != 0) {
4024 msg->cm_fields['D'] = strdup(dest_node);
4027 if (!IsEmptyStr(my_email)) {
4028 msg->cm_fields['F'] = strdup(my_email);
4030 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
4031 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
4034 if (subject != NULL) {
4037 length = strlen(subject);
4043 while ((subject[i] != '\0') &&
4044 (IsAscii = isascii(subject[i]) != 0 ))
4047 msg->cm_fields['U'] = strdup(subject);
4048 else /* ok, we've got utf8 in the string. */
4050 msg->cm_fields['U'] = rfc2047encode(subject, length);
4056 if (supplied_euid != NULL) {
4057 msg->cm_fields['E'] = strdup(supplied_euid);
4060 if ((references != NULL) && (!IsEmptyStr(references))) {
4061 if (msg->cm_fields['W'] != NULL)
4062 free(msg->cm_fields['W']);
4063 msg->cm_fields['W'] = strdup(references);
4066 if (preformatted_text != NULL) {
4067 msg->cm_fields['M'] = preformatted_text;
4070 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4077 * Check to see whether we have permission to post a message in the current
4078 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
4079 * returns 0 on success.
4081 int CtdlDoIHavePermissionToPostInThisRoom(
4084 const char* RemoteIdentifier,
4090 if (!(CC->logged_in) &&
4091 (PostPublic == POST_LOGGED_IN)) {
4092 snprintf(errmsgbuf, n, "Not logged in.");
4093 return (ERROR + NOT_LOGGED_IN);
4095 else if (PostPublic == CHECK_EXISTANCE) {
4096 return (0); // We're Evaling whether a recipient exists
4098 else if (!(CC->logged_in)) {
4100 if ((CC->room.QRflags & QR_READONLY)) {
4101 snprintf(errmsgbuf, n, "Not logged in.");
4102 return (ERROR + NOT_LOGGED_IN);
4104 if (CC->room.QRflags2 & QR2_MODERATED) {
4105 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4106 return (ERROR + NOT_LOGGED_IN);
4108 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4110 return CtdlNetconfigCheckRoomaccess(errmsgbuf, n, RemoteIdentifier);
4116 if ((CC->user.axlevel < AxProbU)
4117 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4118 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4119 return (ERROR + HIGHER_ACCESS_REQUIRED);
4122 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4124 if (ra & UA_POSTALLOWED) {
4125 strcpy(errmsgbuf, "OK to post or reply here");
4129 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4131 * To be thorough, we ought to check to see if the message they are
4132 * replying to is actually a valid one in this room, but unless this
4133 * actually becomes a problem we'll go with high performance instead.
4135 strcpy(errmsgbuf, "OK to reply here");
4139 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4140 /* Clarify what happened with a better error message */
4141 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4142 return (ERROR + HIGHER_ACCESS_REQUIRED);
4145 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4146 return (ERROR + HIGHER_ACCESS_REQUIRED);
4152 * Check to see if the specified user has Internet mail permission
4153 * (returns nonzero if permission is granted)
4155 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4157 /* Do not allow twits to send Internet mail */
4158 if (who->axlevel <= AxProbU) return(0);
4160 /* Globally enabled? */
4161 if (config.c_restrict == 0) return(1);
4163 /* User flagged ok? */
4164 if (who->flags & US_INTERNET) return(2);
4166 /* Admin level access? */
4167 if (who->axlevel >= AxAideU) return(3);
4169 /* No mail for you! */
4175 * Validate recipients, count delivery types and errors, and handle aliasing
4176 * FIXME check for dupes!!!!!
4178 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4179 * were specified, or the number of addresses found invalid.
4181 * Caller needs to free the result using free_recipients()
4183 struct recptypes *validate_recipients(const char *supplied_recipients,
4184 const char *RemoteIdentifier,
4186 struct CitContext *CCC = CC;
4187 struct recptypes *ret;
4188 char *recipients = NULL;
4190 char this_recp[256];
4191 char this_recp_cooked[256];
4198 struct ctdluser tempUS;
4199 struct ctdlroom tempQR;
4200 struct ctdlroom tempQR2;
4206 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4207 if (ret == NULL) return(NULL);
4209 /* Set all strings to null and numeric values to zero */
4210 memset(ret, 0, sizeof(struct recptypes));
4212 if (supplied_recipients == NULL) {
4213 recipients = strdup("");
4216 recipients = strdup(supplied_recipients);
4219 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4220 * actually need, but it's healthier for the heap than doing lots of tiny
4221 * realloc() calls instead.
4223 len = strlen(recipients) + 1024;
4224 ret->errormsg = malloc(len);
4225 ret->recp_local = malloc(len);
4226 ret->recp_internet = malloc(len);
4227 ret->recp_ignet = malloc(len);
4228 ret->recp_room = malloc(len);
4229 ret->display_recp = malloc(len);
4230 ret->recp_orgroom = malloc(len);
4231 org_recp = malloc(len);
4233 ret->errormsg[0] = 0;
4234 ret->recp_local[0] = 0;
4235 ret->recp_internet[0] = 0;
4236 ret->recp_ignet[0] = 0;
4237 ret->recp_room[0] = 0;
4238 ret->recp_orgroom[0] = 0;
4239 ret->display_recp[0] = 0;
4241 ret->recptypes_magic = RECPTYPES_MAGIC;
4243 /* Change all valid separator characters to commas */
4244 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4245 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4246 recipients[i] = ',';
4250 /* Now start extracting recipients... */
4252 while (!IsEmptyStr(recipients)) {
4253 for (i=0; i<=strlen(recipients); ++i) {
4254 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4255 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4256 safestrncpy(this_recp, recipients, i+1);
4258 if (recipients[i] == ',') {
4259 strcpy(recipients, &recipients[i+1]);
4262 strcpy(recipients, "");
4269 if (IsEmptyStr(this_recp))
4271 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4274 strcpy(org_recp, this_recp);
4277 mailtype = alias(this_recp);
4279 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4280 if (this_recp[j]=='_') {
4281 this_recp_cooked[j] = ' ';
4284 this_recp_cooked[j] = this_recp[j];
4287 this_recp_cooked[j] = '\0';
4292 if (!strcasecmp(this_recp, "sysop")) {
4294 strcpy(this_recp, config.c_aideroom);
4295 if (!IsEmptyStr(ret->recp_room)) {
4296 strcat(ret->recp_room, "|");
4298 strcat(ret->recp_room, this_recp);
4300 else if ( (!strncasecmp(this_recp, "room_", 5))
4301 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4303 /* Save room so we can restore it later */
4304 tempQR2 = CCC->room;
4307 /* Check permissions to send mail to this room */
4308 err = CtdlDoIHavePermissionToPostInThisRoom(
4313 0 /* 0 = not a reply */
4322 if (!IsEmptyStr(ret->recp_room)) {
4323 strcat(ret->recp_room, "|");
4325 strcat(ret->recp_room, &this_recp_cooked[5]);
4327 if (!IsEmptyStr(ret->recp_orgroom)) {
4328 strcat(ret->recp_orgroom, "|");
4330 strcat(ret->recp_orgroom, org_recp);
4334 /* Restore room in case something needs it */
4335 CCC->room = tempQR2;
4338 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4340 strcpy(this_recp, tempUS.fullname);
4341 if (!IsEmptyStr(ret->recp_local)) {
4342 strcat(ret->recp_local, "|");
4344 strcat(ret->recp_local, this_recp);
4346 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4348 strcpy(this_recp, tempUS.fullname);
4349 if (!IsEmptyStr(ret->recp_local)) {
4350 strcat(ret->recp_local, "|");
4352 strcat(ret->recp_local, this_recp);
4360 /* Yes, you're reading this correctly: if the target
4361 * domain points back to the local system or an attached
4362 * Citadel directory, the address is invalid. That's
4363 * because if the address were valid, we would have
4364 * already translated it to a local address by now.
4366 if (IsDirectory(this_recp, 0)) {
4371 ++ret->num_internet;
4372 if (!IsEmptyStr(ret->recp_internet)) {
4373 strcat(ret->recp_internet, "|");
4375 strcat(ret->recp_internet, this_recp);
4380 if (!IsEmptyStr(ret->recp_ignet)) {
4381 strcat(ret->recp_ignet, "|");
4383 strcat(ret->recp_ignet, this_recp);
4391 if (IsEmptyStr(errmsg)) {
4392 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4395 snprintf(append, sizeof append, "%s", errmsg);
4397 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4398 if (!IsEmptyStr(ret->errormsg)) {
4399 strcat(ret->errormsg, "; ");
4401 strcat(ret->errormsg, append);
4405 if (IsEmptyStr(ret->display_recp)) {
4406 strcpy(append, this_recp);
4409 snprintf(append, sizeof append, ", %s", this_recp);
4411 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4412 strcat(ret->display_recp, append);
4418 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4419 ret->num_room + ret->num_error) == 0) {
4420 ret->num_error = (-1);
4421 strcpy(ret->errormsg, "No recipients specified.");
4424 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4425 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4426 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4427 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4428 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4429 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4437 * Destructor for struct recptypes
4439 void free_recipients(struct recptypes *valid) {
4441 if (valid == NULL) {
4445 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4446 struct CitContext *CCC = CC;
4447 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4451 if (valid->errormsg != NULL) free(valid->errormsg);
4452 if (valid->recp_local != NULL) free(valid->recp_local);
4453 if (valid->recp_internet != NULL) free(valid->recp_internet);
4454 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4455 if (valid->recp_room != NULL) free(valid->recp_room);
4456 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4457 if (valid->display_recp != NULL) free(valid->display_recp);
4458 if (valid->bounce_to != NULL) free(valid->bounce_to);
4459 if (valid->envelope_from != NULL) free(valid->envelope_from);
4460 if (valid->sending_room != NULL) free(valid->sending_room);
4467 * message entry - mode 0 (normal)
4469 void cmd_ent0(char *entargs)
4471 struct CitContext *CCC = CC;
4476 char supplied_euid[128];
4478 int format_type = 0;
4479 char newusername[256];
4480 char newuseremail[256];
4481 struct CtdlMessage *msg;
4485 struct recptypes *valid = NULL;
4486 struct recptypes *valid_to = NULL;
4487 struct recptypes *valid_cc = NULL;
4488 struct recptypes *valid_bcc = NULL;
4490 int subject_required = 0;
4495 int newuseremail_ok = 0;
4496 char references[SIZ];
4501 post = extract_int(entargs, 0);
4502 extract_token(recp, entargs, 1, '|', sizeof recp);
4503 anon_flag = extract_int(entargs, 2);
4504 format_type = extract_int(entargs, 3);
4505 extract_token(subject, entargs, 4, '|', sizeof subject);
4506 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4507 do_confirm = extract_int(entargs, 6);
4508 extract_token(cc, entargs, 7, '|', sizeof cc);
4509 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4510 switch(CC->room.QRdefaultview) {
4513 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4516 supplied_euid[0] = 0;
4519 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4520 extract_token(references, entargs, 11, '|', sizeof references);
4521 for (ptr=references; *ptr != 0; ++ptr) {
4522 if (*ptr == '!') *ptr = '|';
4525 /* first check to make sure the request is valid. */
4527 err = CtdlDoIHavePermissionToPostInThisRoom(
4532 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4536 cprintf("%d %s\n", err, errmsg);
4540 /* Check some other permission type things. */
4542 if (IsEmptyStr(newusername)) {
4543 strcpy(newusername, CCC->user.fullname);
4545 if ( (CCC->user.axlevel < AxAideU)
4546 && (strcasecmp(newusername, CCC->user.fullname))
4547 && (strcasecmp(newusername, CCC->cs_inet_fn))
4549 cprintf("%d You don't have permission to author messages as '%s'.\n",
4550 ERROR + HIGHER_ACCESS_REQUIRED,
4557 if (IsEmptyStr(newuseremail)) {
4558 newuseremail_ok = 1;
4561 if (!IsEmptyStr(newuseremail)) {
4562 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4563 newuseremail_ok = 1;
4565 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4566 j = num_tokens(CCC->cs_inet_other_emails, '|');
4567 for (i=0; i<j; ++i) {
4568 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4569 if (!strcasecmp(newuseremail, buf)) {
4570 newuseremail_ok = 1;
4576 if (!newuseremail_ok) {
4577 cprintf("%d You don't have permission to author messages as '%s'.\n",
4578 ERROR + HIGHER_ACCESS_REQUIRED,
4584 CCC->cs_flags |= CS_POSTING;
4586 /* In mailbox rooms we have to behave a little differently --
4587 * make sure the user has specified at least one recipient. Then
4588 * validate the recipient(s). We do this for the Mail> room, as
4589 * well as any room which has the "Mailbox" view set - unless it
4590 * is the DRAFTS room which does not require recipients
4593 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4594 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4595 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4596 if (CCC->user.axlevel < AxProbU) {
4597 strcpy(recp, "sysop");
4602 valid_to = validate_recipients(recp, NULL, 0);
4603 if (valid_to->num_error > 0) {
4604 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4605 free_recipients(valid_to);
4609 valid_cc = validate_recipients(cc, NULL, 0);
4610 if (valid_cc->num_error > 0) {
4611 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4612 free_recipients(valid_to);
4613 free_recipients(valid_cc);
4617 valid_bcc = validate_recipients(bcc, NULL, 0);
4618 if (valid_bcc->num_error > 0) {
4619 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4620 free_recipients(valid_to);
4621 free_recipients(valid_cc);
4622 free_recipients(valid_bcc);
4626 /* Recipient required, but none were specified */
4627 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4628 free_recipients(valid_to);
4629 free_recipients(valid_cc);
4630 free_recipients(valid_bcc);
4631 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4635 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4636 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4637 cprintf("%d You do not have permission "
4638 "to send Internet mail.\n",
4639 ERROR + HIGHER_ACCESS_REQUIRED);
4640 free_recipients(valid_to);
4641 free_recipients(valid_cc);
4642 free_recipients(valid_bcc);
4647 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)
4648 && (CCC->user.axlevel < AxNetU) ) {
4649 cprintf("%d Higher access required for network mail.\n",
4650 ERROR + HIGHER_ACCESS_REQUIRED);
4651 free_recipients(valid_to);
4652 free_recipients(valid_cc);
4653 free_recipients(valid_bcc);
4657 if ((RESTRICT_INTERNET == 1)
4658 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4659 && ((CCC->user.flags & US_INTERNET) == 0)
4660 && (!CCC->internal_pgm)) {
4661 cprintf("%d You don't have access to Internet mail.\n",
4662 ERROR + HIGHER_ACCESS_REQUIRED);
4663 free_recipients(valid_to);
4664 free_recipients(valid_cc);
4665 free_recipients(valid_bcc);
4671 /* Is this a room which has anonymous-only or anonymous-option? */
4672 anonymous = MES_NORMAL;
4673 if (CCC->room.QRflags & QR_ANONONLY) {
4674 anonymous = MES_ANONONLY;
4676 if (CCC->room.QRflags & QR_ANONOPT) {
4677 if (anon_flag == 1) { /* only if the user requested it */
4678 anonymous = MES_ANONOPT;
4682 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4686 /* Recommend to the client that the use of a message subject is
4687 * strongly recommended in this room, if either the SUBJECTREQ flag
4688 * is set, or if there is one or more Internet email recipients.
4690 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4691 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4692 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4693 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4695 /* If we're only checking the validity of the request, return
4696 * success without creating the message.
4699 cprintf("%d %s|%d\n", CIT_OK,
4700 ((valid_to != NULL) ? valid_to->display_recp : ""),
4702 free_recipients(valid_to);
4703 free_recipients(valid_cc);
4704 free_recipients(valid_bcc);
4708 /* We don't need these anymore because we'll do it differently below */
4709 free_recipients(valid_to);
4710 free_recipients(valid_cc);
4711 free_recipients(valid_bcc);
4713 /* Read in the message from the client. */
4715 cprintf("%d send message\n", START_CHAT_MODE);
4717 cprintf("%d send message\n", SEND_LISTING);
4720 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4721 CCC->room.QRname, anonymous, format_type,
4722 newusername, newuseremail, subject,
4723 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4726 /* Put together one big recipients struct containing to/cc/bcc all in
4727 * one. This is for the envelope.
4729 char *all_recps = malloc(SIZ * 3);
4730 strcpy(all_recps, recp);
4731 if (!IsEmptyStr(cc)) {
4732 if (!IsEmptyStr(all_recps)) {
4733 strcat(all_recps, ",");
4735 strcat(all_recps, cc);
4737 if (!IsEmptyStr(bcc)) {
4738 if (!IsEmptyStr(all_recps)) {
4739 strcat(all_recps, ",");
4741 strcat(all_recps, bcc);
4743 if (!IsEmptyStr(all_recps)) {
4744 valid = validate_recipients(all_recps, NULL, 0);
4751 if ((valid != NULL) && (valid->num_room == 1))
4753 /* posting into an ML room? set the envelope from
4754 * to the actual mail address so others get a valid
4757 msg->cm_fields['V'] = strdup(valid->recp_orgroom);
4761 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4763 cprintf("%ld\n", msgnum);
4765 if (StrLength(CCC->StatusMessage) > 0) {
4766 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4768 else if (msgnum >= 0L) {
4769 client_write(HKEY("Message accepted.\n"));
4772 client_write(HKEY("Internal error.\n"));
4775 if (msg->cm_fields['E'] != NULL) {
4776 cprintf("%s\n", msg->cm_fields['E']);
4783 CtdlFreeMessage(msg);
4785 if (valid != NULL) {
4786 free_recipients(valid);
4794 * API function to delete messages which match a set of criteria
4795 * (returns the actual number of messages deleted)
4797 int CtdlDeleteMessages(char *room_name, /* which room */
4798 long *dmsgnums, /* array of msg numbers to be deleted */
4799 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4800 char *content_type /* or "" for any. regular expressions expected. */
4803 struct CitContext *CCC = CC;
4804 struct ctdlroom qrbuf;
4805 struct cdbdata *cdbfr;
4806 long *msglist = NULL;
4807 long *dellist = NULL;
4810 int num_deleted = 0;
4812 struct MetaData smi;
4815 int need_to_free_re = 0;
4817 if (content_type) if (!IsEmptyStr(content_type)) {
4818 regcomp(&re, content_type, 0);
4819 need_to_free_re = 1;
4821 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
4822 room_name, num_dmsgnums, content_type);
4824 /* get room record, obtaining a lock... */
4825 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4826 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
4828 if (need_to_free_re) regfree(&re);
4829 return (0); /* room not found */
4831 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4833 if (cdbfr != NULL) {
4834 dellist = malloc(cdbfr->len);
4835 msglist = (long *) cdbfr->ptr;
4836 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4837 num_msgs = cdbfr->len / sizeof(long);
4841 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4842 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4843 int have_more_del = 1;
4845 num_msgs = sort_msglist(msglist, num_msgs);
4846 if (num_dmsgnums > 1)
4847 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4850 StrBuf *dbg = NewStrBuf();
4851 for (i = 0; i < num_dmsgnums; i++)
4852 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4853 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
4858 while ((i < num_msgs) && (have_more_del)) {
4861 /* Set/clear a bit for each criterion */
4863 /* 0 messages in the list or a null list means that we are
4864 * interested in deleting any messages which meet the other criteria.
4867 delete_this |= 0x01;
4870 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
4875 if (msglist[i] == dmsgnums[j]) {
4876 delete_this |= 0x01;
4879 have_more_del = (j < num_dmsgnums);
4882 if (have_contenttype) {
4883 GetMetaData(&smi, msglist[i]);
4884 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4885 delete_this |= 0x02;
4888 delete_this |= 0x02;
4891 /* Delete message only if all bits are set */
4892 if (delete_this == 0x03) {
4893 dellist[num_deleted++] = msglist[i];
4900 StrBuf *dbg = NewStrBuf();
4901 for (i = 0; i < num_deleted; i++)
4902 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
4903 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
4907 num_msgs = sort_msglist(msglist, num_msgs);
4908 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4909 msglist, (int)(num_msgs * sizeof(long)));
4912 qrbuf.QRhighest = msglist[num_msgs - 1];
4914 qrbuf.QRhighest = 0;
4916 CtdlPutRoomLock(&qrbuf);
4918 /* Go through the messages we pulled out of the index, and decrement
4919 * their reference counts by 1. If this is the only room the message
4920 * was in, the reference count will reach zero and the message will
4921 * automatically be deleted from the database. We do this in a
4922 * separate pass because there might be plug-in hooks getting called,
4923 * and we don't want that happening during an S_ROOMS critical
4927 for (i=0; i<num_deleted; ++i) {
4928 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4930 AdjRefCountList(dellist, num_deleted, -1);
4932 /* Now free the memory we used, and go away. */
4933 if (msglist != NULL) free(msglist);
4934 if (dellist != NULL) free(dellist);
4935 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
4936 if (need_to_free_re) regfree(&re);
4937 return (num_deleted);
4943 * Check whether the current user has permission to delete messages from
4944 * the current room (returns 1 for yes, 0 for no)
4946 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4948 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4949 if (ra & UA_DELETEALLOWED) return(1);
4957 * Delete message from current room
4959 void cmd_dele(char *args)
4968 extract_token(msgset, args, 0, '|', sizeof msgset);
4969 num_msgs = num_tokens(msgset, ',');
4971 cprintf("%d Nothing to do.\n", CIT_OK);
4975 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4976 cprintf("%d Higher access required.\n",
4977 ERROR + HIGHER_ACCESS_REQUIRED);
4982 * Build our message set to be moved/copied
4984 msgs = malloc(num_msgs * sizeof(long));
4985 for (i=0; i<num_msgs; ++i) {
4986 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4987 msgs[i] = atol(msgtok);
4990 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4994 cprintf("%d %d message%s deleted.\n", CIT_OK,
4995 num_deleted, ((num_deleted != 1) ? "s" : ""));
4997 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
5005 * move or copy a message to another room
5007 void cmd_move(char *args)
5014 char targ[ROOMNAMELEN];
5015 struct ctdlroom qtemp;
5022 extract_token(msgset, args, 0, '|', sizeof msgset);
5023 num_msgs = num_tokens(msgset, ',');
5025 cprintf("%d Nothing to do.\n", CIT_OK);
5029 extract_token(targ, args, 1, '|', sizeof targ);
5030 convert_room_name_macros(targ, sizeof targ);
5031 targ[ROOMNAMELEN - 1] = 0;
5032 is_copy = extract_int(args, 2);
5034 if (CtdlGetRoom(&qtemp, targ) != 0) {
5035 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
5039 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
5040 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
5044 CtdlGetUser(&CC->user, CC->curr_user);
5045 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
5047 /* Check for permission to perform this operation.
5048 * Remember: "CC->room" is source, "qtemp" is target.
5052 /* Admins can move/copy */
5053 if (CC->user.axlevel >= AxAideU) permit = 1;
5055 /* Room aides can move/copy */
5056 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
5058 /* Permit move/copy from personal rooms */
5059 if ((CC->room.QRflags & QR_MAILBOX)
5060 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5062 /* Permit only copy from public to personal room */
5064 && (!(CC->room.QRflags & QR_MAILBOX))
5065 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5067 /* Permit message removal from collaborative delete rooms */
5068 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5070 /* Users allowed to post into the target room may move into it too. */
5071 if ((CC->room.QRflags & QR_MAILBOX) &&
5072 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
5074 /* User must have access to target room */
5075 if (!(ra & UA_KNOWN)) permit = 0;
5078 cprintf("%d Higher access required.\n",
5079 ERROR + HIGHER_ACCESS_REQUIRED);
5084 * Build our message set to be moved/copied
5086 msgs = malloc(num_msgs * sizeof(long));
5087 for (i=0; i<num_msgs; ++i) {
5088 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5089 msgs[i] = atol(msgtok);
5095 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5097 cprintf("%d Cannot store message(s) in %s: error %d\n",
5103 /* Now delete the message from the source room,
5104 * if this is a 'move' rather than a 'copy' operation.
5107 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5111 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5117 * GetMetaData() - Get the supplementary record for a message
5119 void GetMetaData(struct MetaData *smibuf, long msgnum)
5122 struct cdbdata *cdbsmi;
5125 memset(smibuf, 0, sizeof(struct MetaData));
5126 smibuf->meta_msgnum = msgnum;
5127 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5129 /* Use the negative of the message number for its supp record index */
5130 TheIndex = (0L - msgnum);
5132 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5133 if (cdbsmi == NULL) {
5134 return; /* record not found; go with defaults */
5136 memcpy(smibuf, cdbsmi->ptr,
5137 ((cdbsmi->len > sizeof(struct MetaData)) ?
5138 sizeof(struct MetaData) : cdbsmi->len));
5145 * PutMetaData() - (re)write supplementary record for a message
5147 void PutMetaData(struct MetaData *smibuf)
5151 /* Use the negative of the message number for the metadata db index */
5152 TheIndex = (0L - smibuf->meta_msgnum);
5154 cdb_store(CDB_MSGMAIN,
5155 &TheIndex, (int)sizeof(long),
5156 smibuf, (int)sizeof(struct MetaData));
5161 * AdjRefCount - submit an adjustment to the reference count for a message.
5162 * (These are just queued -- we actually process them later.)
5164 void AdjRefCount(long msgnum, int incr)
5166 struct CitContext *CCC = CC;
5167 struct arcq new_arcq;
5170 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5172 begin_critical_section(S_SUPPMSGMAIN);
5173 if (arcfp == NULL) {
5174 arcfp = fopen(file_arcq, "ab+");
5175 chown(file_arcq, CTDLUID, (-1));
5176 chmod(file_arcq, 0600);
5178 end_critical_section(S_SUPPMSGMAIN);
5180 /* msgnum < 0 means that we're trying to close the file */
5182 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5183 begin_critical_section(S_SUPPMSGMAIN);
5184 if (arcfp != NULL) {
5188 end_critical_section(S_SUPPMSGMAIN);
5193 * If we can't open the queue, perform the operation synchronously.
5195 if (arcfp == NULL) {
5196 TDAP_AdjRefCount(msgnum, incr);
5200 new_arcq.arcq_msgnum = msgnum;
5201 new_arcq.arcq_delta = incr;
5202 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5204 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5213 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5215 struct CitContext *CCC = CC;
5216 long i, the_size, offset;
5217 struct arcq *new_arcq;
5220 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5222 begin_critical_section(S_SUPPMSGMAIN);
5223 if (arcfp == NULL) {
5224 arcfp = fopen(file_arcq, "ab+");
5225 chown(file_arcq, CTDLUID, (-1));
5226 chmod(file_arcq, 0600);
5228 end_critical_section(S_SUPPMSGMAIN);
5231 * If we can't open the queue, perform the operation synchronously.
5233 if (arcfp == NULL) {
5234 for (i = 0; i < nmsg; i++)
5235 TDAP_AdjRefCount(msgnum[i], incr);
5239 the_size = sizeof(struct arcq) * nmsg;
5240 new_arcq = malloc(the_size);
5241 for (i = 0; i < nmsg; i++) {
5242 new_arcq[i].arcq_msgnum = msgnum[i];
5243 new_arcq[i].arcq_delta = incr;
5247 while ((rv >= 0) && (offset < the_size))
5249 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5251 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5267 * TDAP_ProcessAdjRefCountQueue()
5269 * Process the queue of message count adjustments that was created by calls
5270 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5271 * for each one. This should be an "off hours" operation.
5273 int TDAP_ProcessAdjRefCountQueue(void)
5275 struct CitContext *CCC = CC;
5276 char file_arcq_temp[PATH_MAX];
5279 struct arcq arcq_rec;
5280 int num_records_processed = 0;
5282 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5284 begin_critical_section(S_SUPPMSGMAIN);
5285 if (arcfp != NULL) {
5290 r = link(file_arcq, file_arcq_temp);
5292 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5293 end_critical_section(S_SUPPMSGMAIN);
5294 return(num_records_processed);
5298 end_critical_section(S_SUPPMSGMAIN);
5300 fp = fopen(file_arcq_temp, "rb");
5302 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5303 return(num_records_processed);
5306 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5307 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5308 ++num_records_processed;
5312 r = unlink(file_arcq_temp);
5314 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5317 return(num_records_processed);
5323 * TDAP_AdjRefCount - adjust the reference count for a message.
5324 * This one does it "for real" because it's called by
5325 * the autopurger function that processes the queue
5326 * created by AdjRefCount(). If a message's reference
5327 * count becomes zero, we also delete the message from
5328 * disk and de-index it.
5330 void TDAP_AdjRefCount(long msgnum, int incr)
5332 struct CitContext *CCC = CC;
5334 struct MetaData smi;
5337 /* This is a *tight* critical section; please keep it that way, as
5338 * it may get called while nested in other critical sections.
5339 * Complicating this any further will surely cause deadlock!
5341 begin_critical_section(S_SUPPMSGMAIN);
5342 GetMetaData(&smi, msgnum);
5343 smi.meta_refcount += incr;
5345 end_critical_section(S_SUPPMSGMAIN);
5346 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5347 msgnum, incr, smi.meta_refcount
5350 /* If the reference count is now zero, delete the message
5351 * (and its supplementary record as well).
5353 if (smi.meta_refcount == 0) {
5354 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5356 /* Call delete hooks with NULL room to show it has gone altogether */
5357 PerformDeleteHooks(NULL, msgnum);
5359 /* Remove from message base */
5361 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5362 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5364 /* Remove metadata record */
5365 delnum = (0L - msgnum);
5366 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5372 * Write a generic object to this room
5374 * Note: this could be much more efficient. Right now we use two temporary
5375 * files, and still pull the message into memory as with all others.
5377 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5378 char *content_type, /* MIME type of this object */
5379 char *raw_message, /* Data to be written */
5380 off_t raw_length, /* Size of raw_message */
5381 struct ctdluser *is_mailbox, /* Mailbox room? */
5382 int is_binary, /* Is encoding necessary? */
5383 int is_unique, /* Del others of this type? */
5384 unsigned int flags /* Internal save flags */
5387 struct CitContext *CCC = CC;
5388 struct ctdlroom qrbuf;
5389 char roomname[ROOMNAMELEN];
5390 struct CtdlMessage *msg;
5391 char *encoded_message = NULL;
5393 if (is_mailbox != NULL) {
5394 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5397 safestrncpy(roomname, req_room, sizeof(roomname));
5400 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5403 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5406 encoded_message = malloc((size_t)(raw_length + 4096));
5409 sprintf(encoded_message, "Content-type: %s\n", content_type);
5412 sprintf(&encoded_message[strlen(encoded_message)],
5413 "Content-transfer-encoding: base64\n\n"
5417 sprintf(&encoded_message[strlen(encoded_message)],
5418 "Content-transfer-encoding: 7bit\n\n"
5424 &encoded_message[strlen(encoded_message)],
5432 &encoded_message[strlen(encoded_message)],
5438 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5439 msg = malloc(sizeof(struct CtdlMessage));
5440 memset(msg, 0, sizeof(struct CtdlMessage));
5441 msg->cm_magic = CTDLMESSAGE_MAGIC;
5442 msg->cm_anon_type = MES_NORMAL;
5443 msg->cm_format_type = 4;
5444 msg->cm_fields['A'] = strdup(CCC->user.fullname);
5445 msg->cm_fields['O'] = strdup(req_room);
5446 msg->cm_fields['N'] = strdup(config.c_nodename);
5447 msg->cm_fields['H'] = strdup(config.c_humannode);
5448 msg->cm_flags = flags;
5450 msg->cm_fields['M'] = encoded_message;
5452 /* Create the requested room if we have to. */
5453 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5454 CtdlCreateRoom(roomname,
5455 ( (is_mailbox != NULL) ? 5 : 3 ),
5456 "", 0, 1, 0, VIEW_BBS);
5458 /* If the caller specified this object as unique, delete all
5459 * other objects of this type that are currently in the room.
5462 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5463 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5466 /* Now write the data */
5467 CtdlSubmitMsg(msg, NULL, roomname, 0);
5468 CtdlFreeMessage(msg);
5476 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5477 config_msgnum = msgnum;
5481 char *CtdlGetSysConfig(char *sysconfname) {
5482 char hold_rm[ROOMNAMELEN];
5485 struct CtdlMessage *msg;
5488 strcpy(hold_rm, CC->room.QRname);
5489 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5490 CtdlGetRoom(&CC->room, hold_rm);
5495 /* We want the last (and probably only) config in this room */
5496 begin_critical_section(S_CONFIG);
5497 config_msgnum = (-1L);
5498 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5499 CtdlGetSysConfigBackend, NULL);
5500 msgnum = config_msgnum;
5501 end_critical_section(S_CONFIG);
5507 msg = CtdlFetchMessage(msgnum, 1);
5509 conf = strdup(msg->cm_fields['M']);
5510 CtdlFreeMessage(msg);
5517 CtdlGetRoom(&CC->room, hold_rm);
5519 if (conf != NULL) do {
5520 extract_token(buf, conf, 0, '\n', sizeof buf);
5521 strcpy(conf, &conf[strlen(buf)+1]);
5522 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5528 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5529 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5534 * Determine whether a given Internet address belongs to the current user
5536 int CtdlIsMe(char *addr, int addr_buf_len)
5538 struct recptypes *recp;
5541 recp = validate_recipients(addr, NULL, 0);
5542 if (recp == NULL) return(0);
5544 if (recp->num_local == 0) {
5545 free_recipients(recp);
5549 for (i=0; i<recp->num_local; ++i) {
5550 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5551 if (!strcasecmp(addr, CC->user.fullname)) {
5552 free_recipients(recp);
5557 free_recipients(recp);
5563 * Citadel protocol command to do the same
5565 void cmd_isme(char *argbuf) {
5568 if (CtdlAccessCheck(ac_logged_in)) return;
5569 extract_token(addr, argbuf, 0, '|', sizeof addr);
5571 if (CtdlIsMe(addr, sizeof addr)) {
5572 cprintf("%d %s\n", CIT_OK, addr);
5575 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5581 /*****************************************************************************/
5582 /* MODULE INITIALIZATION STUFF */
5583 /*****************************************************************************/
5584 void SetMessageDebugEnabled(const int n)
5586 MessageDebugEnabled = n;
5588 CTDL_MODULE_INIT(msgbase)
5591 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5593 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5594 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5595 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5596 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5597 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5598 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5599 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5600 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5601 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5602 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5603 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5604 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5607 /* return our Subversion id for the Log */