2 * Implements the message store.
4 * Copyright (c) 1987-2011 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 as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #if TIME_WITH_SYS_TIME
28 # include <sys/time.h>
32 # include <sys/time.h>
45 #include <sys/types.h>
47 #include <libcitadel.h>
50 #include "serv_extensions.h"
54 #include "sysdep_decls.h"
55 #include "citserver.h"
62 #include "internet_addressing.h"
63 #include "euidindex.h"
64 #include "journaling.h"
65 #include "citadel_dirs.h"
66 #include "clientsocket.h"
67 #include "serv_network.h"
70 #include "ctdl_module.h"
73 struct addresses_to_be_filed *atbf = NULL;
75 /* This temp file holds the queue of operations for AdjRefCount() */
76 static FILE *arcfp = NULL;
79 * This really belongs in serv_network.c, but I don't know how to export
80 * symbols between modules.
82 struct FilterList *filterlist = NULL;
86 * These are the four-character field headers we use when outputting
87 * messages in Citadel format (as opposed to RFC822 format).
90 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
91 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
92 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
93 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
94 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
95 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
96 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
97 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
126 * This function is self explanatory.
127 * (What can I say, I'm in a weird mood today...)
129 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
133 for (i = 0; i < strlen(name); ++i) {
134 if (name[i] == '@') {
135 while (isspace(name[i - 1]) && i > 0) {
136 strcpy(&name[i - 1], &name[i]);
139 while (isspace(name[i + 1])) {
140 strcpy(&name[i + 1], &name[i + 2]);
148 * Aliasing for network mail.
149 * (Error messages have been commented out, because this is a server.)
151 int alias(char *name)
152 { /* process alias and routing info for mail */
155 char aaa[SIZ], bbb[SIZ];
156 char *ignetcfg = NULL;
157 char *ignetmap = NULL;
163 char original_name[256];
164 safestrncpy(original_name, name, sizeof original_name);
167 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
168 stripallbut(name, '<', '>');
170 fp = fopen(file_mail_aliases, "r");
172 fp = fopen("/dev/null", "r");
179 while (fgets(aaa, sizeof aaa, fp) != NULL) {
180 while (isspace(name[0]))
181 strcpy(name, &name[1]);
182 aaa[strlen(aaa) - 1] = 0;
184 for (a = 0; a < strlen(aaa); ++a) {
186 strcpy(bbb, &aaa[a + 1]);
190 if (!strcasecmp(name, aaa))
195 /* Hit the Global Address Book */
196 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
200 if (strcasecmp(original_name, name)) {
201 syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
204 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
205 for (a=0; a<strlen(name); ++a) {
206 if (name[a] == '@') {
207 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
209 syslog(LOG_INFO, "Changed to <%s>\n", name);
214 /* determine local or remote type, see citadel.h */
215 at = haschar(name, '@');
216 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
217 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
218 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
220 /* figure out the delivery mode */
221 extract_token(node, name, 1, '@', sizeof node);
223 /* If there are one or more dots in the nodename, we assume that it
224 * is an FQDN and will attempt SMTP delivery to the Internet.
226 if (haschar(node, '.') > 0) {
227 return(MES_INTERNET);
230 /* Otherwise we look in the IGnet maps for a valid Citadel node.
231 * Try directly-connected nodes first...
233 ignetcfg = CtdlGetSysConfig(IGNETCFG);
234 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
235 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
236 extract_token(testnode, buf, 0, '|', sizeof testnode);
237 if (!strcasecmp(node, testnode)) {
245 * Then try nodes that are two or more hops away.
247 ignetmap = CtdlGetSysConfig(IGNETMAP);
248 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
249 extract_token(buf, ignetmap, i, '\n', sizeof buf);
250 extract_token(testnode, buf, 0, '|', sizeof testnode);
251 if (!strcasecmp(node, testnode)) {
258 /* If we get to this point it's an invalid node name */
264 * Back end for the MSGS command: output message number only.
266 void simple_listing(long msgnum, void *userdata)
268 cprintf("%ld\n", msgnum);
274 * Back end for the MSGS command: output header summary.
276 void headers_listing(long msgnum, void *userdata)
278 struct CtdlMessage *msg;
280 msg = CtdlFetchMessage(msgnum, 0);
282 cprintf("%ld|0|||||\n", msgnum);
286 cprintf("%ld|%s|%s|%s|%s|%s|\n",
288 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
289 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
290 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
291 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
292 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
294 CtdlFreeMessage(msg);
298 * Back end for the MSGS command: output EUID header.
300 void headers_euid(long msgnum, void *userdata)
302 struct CtdlMessage *msg;
304 msg = CtdlFetchMessage(msgnum, 0);
306 cprintf("%ld||\n", msgnum);
310 cprintf("%ld|%s|%s\n",
312 (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""),
313 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"));
314 CtdlFreeMessage(msg);
321 /* Determine if a given message matches the fields in a message template.
322 * Return 0 for a successful match.
324 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
327 /* If there aren't any fields in the template, all messages will
330 if (template == NULL) return(0);
332 /* Null messages are bogus. */
333 if (msg == NULL) return(1);
335 for (i='A'; i<='Z'; ++i) {
336 if (template->cm_fields[i] != NULL) {
337 if (msg->cm_fields[i] == NULL) {
338 /* Considered equal if temmplate is empty string */
339 if (IsEmptyStr(template->cm_fields[i])) continue;
342 if (strcasecmp(msg->cm_fields[i],
343 template->cm_fields[i])) return 1;
347 /* All compares succeeded: we have a match! */
354 * Retrieve the "seen" message list for the current room.
356 void CtdlGetSeen(char *buf, int which_set) {
359 /* Learn about the user and room in question */
360 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
362 if (which_set == ctdlsetseen_seen)
363 safestrncpy(buf, vbuf.v_seen, SIZ);
364 if (which_set == ctdlsetseen_answered)
365 safestrncpy(buf, vbuf.v_answered, SIZ);
371 * Manipulate the "seen msgs" string (or other message set strings)
373 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
374 int target_setting, int which_set,
375 struct ctdluser *which_user, struct ctdlroom *which_room) {
376 struct cdbdata *cdbfr;
390 char *is_set; /* actually an array of booleans */
392 /* Don't bother doing *anything* if we were passed a list of zero messages */
393 if (num_target_msgnums < 1) {
397 /* If no room was specified, we go with the current room. */
399 which_room = &CC->room;
402 /* If no user was specified, we go with the current user. */
404 which_user = &CC->user;
407 syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
408 num_target_msgnums, target_msgnums[0],
409 (target_setting ? "SET" : "CLEAR"),
413 /* Learn about the user and room in question */
414 CtdlGetRelationship(&vbuf, which_user, which_room);
416 /* Load the message list */
417 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
419 msglist = (long *) cdbfr->ptr;
420 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
421 num_msgs = cdbfr->len / sizeof(long);
424 return; /* No messages at all? No further action. */
427 is_set = malloc(num_msgs * sizeof(char));
428 memset(is_set, 0, (num_msgs * sizeof(char)) );
430 /* Decide which message set we're manipulating */
432 case ctdlsetseen_seen:
433 vset = NewStrBufPlain(vbuf.v_seen, -1);
435 case ctdlsetseen_answered:
436 vset = NewStrBufPlain(vbuf.v_answered, -1);
443 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
444 syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
445 for (i=0; i<num_msgs; ++i) {
446 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
448 syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
449 for (k=0; k<num_target_msgnums; ++k) {
450 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
454 syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
456 /* Translate the existing sequence set into an array of booleans */
457 setstr = NewStrBuf();
461 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
463 StrBufExtract_token(lostr, setstr, 0, ':');
464 if (StrBufNum_tokens(setstr, ':') >= 2) {
465 StrBufExtract_token(histr, setstr, 1, ':');
469 StrBufAppendBuf(histr, lostr, 0);
472 if (!strcmp(ChrPtr(histr), "*")) {
479 for (i = 0; i < num_msgs; ++i) {
480 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
490 /* Now translate the array of booleans back into a sequence set */
496 for (i=0; i<num_msgs; ++i) {
500 for (k=0; k<num_target_msgnums; ++k) {
501 if (msglist[i] == target_msgnums[k]) {
502 is_seen = target_setting;
506 if ((was_seen == 0) && (is_seen == 1)) {
509 else if ((was_seen == 1) && (is_seen == 0)) {
512 if (StrLength(vset) > 0) {
513 StrBufAppendBufPlain(vset, HKEY(","), 0);
516 StrBufAppendPrintf(vset, "%ld", hi);
519 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
523 if ((is_seen) && (i == num_msgs - 1)) {
524 if (StrLength(vset) > 0) {
525 StrBufAppendBufPlain(vset, HKEY(","), 0);
527 if ((i==0) || (was_seen == 0)) {
528 StrBufAppendPrintf(vset, "%ld", msglist[i]);
531 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
539 * We will have to stuff this string back into a 4096 byte buffer, so if it's
540 * larger than that now, truncate it by removing tokens from the beginning.
541 * The limit of 100 iterations is there to prevent an infinite loop in case
542 * something unexpected happens.
544 int number_of_truncations = 0;
545 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
546 StrBufRemove_token(vset, 0, ',');
547 ++number_of_truncations;
551 * If we're truncating the sequence set of messages marked with the 'seen' flag,
552 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
553 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
555 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
557 first_tok = NewStrBuf();
558 StrBufExtract_token(first_tok, vset, 0, ',');
559 StrBufRemove_token(vset, 0, ',');
561 if (StrBufNum_tokens(first_tok, ':') > 1) {
562 StrBufRemove_token(first_tok, 0, ':');
566 new_set = NewStrBuf();
567 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
568 StrBufAppendBuf(new_set, first_tok, 0);
569 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
570 StrBufAppendBuf(new_set, vset, 0);
573 FreeStrBuf(&first_tok);
577 syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
579 /* Decide which message set we're manipulating */
581 case ctdlsetseen_seen:
582 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
584 case ctdlsetseen_answered:
585 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
591 CtdlSetRelationship(&vbuf, which_user, which_room);
597 * API function to perform an operation for each qualifying message in the
598 * current room. (Returns the number of messages processed.)
600 int CtdlForEachMessage(int mode, long ref, char *search_string,
602 struct CtdlMessage *compare,
603 ForEachMsgCallback CallBack,
609 struct cdbdata *cdbfr;
610 long *msglist = NULL;
612 int num_processed = 0;
615 struct CtdlMessage *msg = NULL;
618 int printed_lastold = 0;
619 int num_search_msgs = 0;
620 long *search_msgs = NULL;
622 int need_to_free_re = 0;
625 if ((content_type) && (!IsEmptyStr(content_type))) {
626 regcomp(&re, content_type, 0);
630 /* Learn about the user and room in question */
631 CtdlGetUser(&CC->user, CC->curr_user);
632 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
634 /* Load the message list */
635 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
637 if (need_to_free_re) regfree(&re);
638 return 0; /* No messages at all? No further action. */
641 msglist = (long *) cdbfr->ptr;
642 num_msgs = cdbfr->len / sizeof(long);
644 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
645 cdb_free(cdbfr); /* we own this memory now */
648 * We cache the most recent msglist in order to do security checks later
650 if (CC->client_socket > 0) {
651 if (CC->cached_msglist != NULL) {
652 free(CC->cached_msglist);
655 CC->cached_msglist = msglist;
659 * Now begin the traversal.
661 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
663 /* If the caller is looking for a specific MIME type, filter
664 * out all messages which are not of the type requested.
666 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
668 /* This call to GetMetaData() sits inside this loop
669 * so that we only do the extra database read per msg
670 * if we need to. Doing the extra read all the time
671 * really kills the server. If we ever need to use
672 * metadata for another search criterion, we need to
673 * move the read somewhere else -- but still be smart
674 * enough to only do the read if the caller has
675 * specified something that will need it.
677 GetMetaData(&smi, msglist[a]);
679 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
680 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
686 num_msgs = sort_msglist(msglist, num_msgs);
688 /* If a template was supplied, filter out the messages which
689 * don't match. (This could induce some delays!)
692 if (compare != NULL) {
693 for (a = 0; a < num_msgs; ++a) {
694 msg = CtdlFetchMessage(msglist[a], 1);
696 if (CtdlMsgCmp(msg, compare)) {
699 CtdlFreeMessage(msg);
705 /* If a search string was specified, get a message list from
706 * the full text index and remove messages which aren't on both
710 * Since the lists are sorted and strictly ascending, and the
711 * output list is guaranteed to be shorter than or equal to the
712 * input list, we overwrite the bottom of the input list. This
713 * eliminates the need to memmove big chunks of the list over and
716 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
718 /* Call search module via hook mechanism.
719 * NULL means use any search function available.
720 * otherwise replace with a char * to name of search routine
722 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
724 if (num_search_msgs > 0) {
728 orig_num_msgs = num_msgs;
730 for (i=0; i<orig_num_msgs; ++i) {
731 for (j=0; j<num_search_msgs; ++j) {
732 if (msglist[i] == search_msgs[j]) {
733 msglist[num_msgs++] = msglist[i];
739 num_msgs = 0; /* No messages qualify */
741 if (search_msgs != NULL) free(search_msgs);
743 /* Now that we've purged messages which don't contain the search
744 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
751 * Now iterate through the message list, according to the
752 * criteria supplied by the caller.
755 for (a = 0; a < num_msgs; ++a) {
756 thismsg = msglist[a];
757 if (mode == MSGS_ALL) {
761 is_seen = is_msg_in_sequence_set(
762 vbuf.v_seen, thismsg);
763 if (is_seen) lastold = thismsg;
769 || ((mode == MSGS_OLD) && (is_seen))
770 || ((mode == MSGS_NEW) && (!is_seen))
771 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
772 || ((mode == MSGS_FIRST) && (a < ref))
773 || ((mode == MSGS_GT) && (thismsg > ref))
774 || ((mode == MSGS_LT) && (thismsg < ref))
775 || ((mode == MSGS_EQ) && (thismsg == ref))
778 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
780 CallBack(lastold, userdata);
784 if (CallBack) CallBack(thismsg, userdata);
788 if (need_to_free_re) regfree(&re);
789 if (CC->client_socket <= 0) free(msglist);
790 return num_processed;
796 * cmd_msgs() - get list of message #'s in this room
797 * implements the MSGS server command using CtdlForEachMessage()
799 void cmd_msgs(char *cmdbuf)
808 int with_template = 0;
809 struct CtdlMessage *template = NULL;
810 char search_string[1024];
811 ForEachMsgCallback CallBack;
813 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
815 extract_token(which, cmdbuf, 0, '|', sizeof which);
816 cm_ref = extract_int(cmdbuf, 1);
817 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
818 with_template = extract_int(cmdbuf, 2);
819 switch (extract_int(cmdbuf, 3))
823 CallBack = simple_listing;
826 CallBack = headers_listing;
829 CallBack = headers_euid;
834 if (!strncasecmp(which, "OLD", 3))
836 else if (!strncasecmp(which, "NEW", 3))
838 else if (!strncasecmp(which, "FIRST", 5))
840 else if (!strncasecmp(which, "LAST", 4))
842 else if (!strncasecmp(which, "GT", 2))
844 else if (!strncasecmp(which, "LT", 2))
846 else if (!strncasecmp(which, "SEARCH", 6))
851 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
852 cprintf("%d Full text index is not enabled on this server.\n",
853 ERROR + CMD_NOT_SUPPORTED);
859 cprintf("%d Send template then receive message list\n",
861 template = (struct CtdlMessage *)
862 malloc(sizeof(struct CtdlMessage));
863 memset(template, 0, sizeof(struct CtdlMessage));
864 template->cm_magic = CTDLMESSAGE_MAGIC;
865 template->cm_anon_type = MES_NORMAL;
867 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
868 extract_token(tfield, buf, 0, '|', sizeof tfield);
869 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
870 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
871 if (!strcasecmp(tfield, msgkeys[i])) {
872 template->cm_fields[i] =
880 cprintf("%d \n", LISTING_FOLLOWS);
883 CtdlForEachMessage(mode,
884 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
885 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
890 if (template != NULL) CtdlFreeMessage(template);
898 * help_subst() - support routine for help file viewer
900 void help_subst(char *strbuf, char *source, char *dest)
905 while (p = pattern2(strbuf, source), (p >= 0)) {
906 strcpy(workbuf, &strbuf[p + strlen(source)]);
907 strcpy(&strbuf[p], dest);
908 strcat(strbuf, workbuf);
913 void do_help_subst(char *buffer)
917 help_subst(buffer, "^nodename", config.c_nodename);
918 help_subst(buffer, "^humannode", config.c_humannode);
919 help_subst(buffer, "^fqdn", config.c_fqdn);
920 help_subst(buffer, "^username", CC->user.fullname);
921 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
922 help_subst(buffer, "^usernum", buf2);
923 help_subst(buffer, "^sysadm", config.c_sysadm);
924 help_subst(buffer, "^variantname", CITADEL);
925 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
926 help_subst(buffer, "^maxsessions", buf2);
927 help_subst(buffer, "^bbsdir", ctdl_message_dir);
933 * memfmout() - Citadel text formatter and paginator.
934 * Although the original purpose of this routine was to format
935 * text to the reader's screen width, all we're really using it
936 * for here is to format text out to 80 columns before sending it
937 * to the client. The client software may reformat it again.
940 char *mptr, /* where are we going to get our text from? */
941 const char *nl /* string to terminate lines with */
944 unsigned char ch = 0;
951 while (ch=*(mptr++), ch != 0) {
954 client_write(outbuf, len);
956 client_write(nl, nllen);
959 else if (ch == '\r') {
960 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
962 else if (isspace(ch)) {
963 if (column > 72) { /* Beyond 72 columns, break on the next space */
964 client_write(outbuf, len);
966 client_write(nl, nllen);
977 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
978 client_write(outbuf, len);
980 client_write(nl, nllen);
986 client_write(outbuf, len);
988 client_write(nl, nllen);
996 * Callback function for mime parser that simply lists the part
998 void list_this_part(char *name, char *filename, char *partnum, char *disp,
999 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1000 char *cbid, void *cbuserdata)
1004 ma = (struct ma_info *)cbuserdata;
1005 if (ma->is_ma == 0) {
1006 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1019 * Callback function for multipart prefix
1021 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1022 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1023 char *cbid, void *cbuserdata)
1027 ma = (struct ma_info *)cbuserdata;
1028 if (!strcasecmp(cbtype, "multipart/alternative")) {
1032 if (ma->is_ma == 0) {
1033 cprintf("pref=%s|%s\n", partnum, cbtype);
1038 * Callback function for multipart sufffix
1040 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1041 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1042 char *cbid, void *cbuserdata)
1046 ma = (struct ma_info *)cbuserdata;
1047 if (ma->is_ma == 0) {
1048 cprintf("suff=%s|%s\n", partnum, cbtype);
1050 if (!strcasecmp(cbtype, "multipart/alternative")) {
1057 * Callback function for mime parser that opens a section for downloading
1059 void mime_download(char *name, char *filename, char *partnum, char *disp,
1060 void *content, char *cbtype, char *cbcharset, size_t length,
1061 char *encoding, char *cbid, void *cbuserdata)
1065 /* Silently go away if there's already a download open. */
1066 if (CC->download_fp != NULL)
1070 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1071 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1073 CC->download_fp = tmpfile();
1074 if (CC->download_fp == NULL)
1077 rv = fwrite(content, length, 1, CC->download_fp);
1078 fflush(CC->download_fp);
1079 rewind(CC->download_fp);
1081 OpenCmdResult(filename, cbtype);
1088 * Callback function for mime parser that outputs a section all at once.
1089 * We can specify the desired section by part number *or* content-id.
1091 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1092 void *content, char *cbtype, char *cbcharset, size_t length,
1093 char *encoding, char *cbid, void *cbuserdata)
1095 int *found_it = (int *)cbuserdata;
1098 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1099 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1102 cprintf("%d %d|-1|%s|%s|%s\n",
1109 client_write(content, length);
1115 * Load a message from disk into memory.
1116 * This is used by CtdlOutputMsg() and other fetch functions.
1118 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1119 * using the CtdlMessageFree() function.
1121 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1123 struct cdbdata *dmsgtext;
1124 struct CtdlMessage *ret = NULL;
1128 cit_uint8_t field_header;
1130 syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1131 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1132 if (dmsgtext == NULL) {
1135 mptr = dmsgtext->ptr;
1136 upper_bound = mptr + dmsgtext->len;
1138 /* Parse the three bytes that begin EVERY message on disk.
1139 * The first is always 0xFF, the on-disk magic number.
1140 * The second is the anonymous/public type byte.
1141 * The third is the format type byte (vari, fixed, or MIME).
1145 syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1149 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1150 memset(ret, 0, sizeof(struct CtdlMessage));
1152 ret->cm_magic = CTDLMESSAGE_MAGIC;
1153 ret->cm_anon_type = *mptr++; /* Anon type byte */
1154 ret->cm_format_type = *mptr++; /* Format type byte */
1157 * The rest is zero or more arbitrary fields. Load them in.
1158 * We're done when we encounter either a zero-length field or
1159 * have just processed the 'M' (message text) field.
1162 if (mptr >= upper_bound) {
1165 field_header = *mptr++;
1166 ret->cm_fields[field_header] = strdup(mptr);
1168 while (*mptr++ != 0); /* advance to next field */
1170 } while ((mptr < upper_bound) && (field_header != 'M'));
1174 /* Always make sure there's something in the msg text field. If
1175 * it's NULL, the message text is most likely stored separately,
1176 * so go ahead and fetch that. Failing that, just set a dummy
1177 * body so other code doesn't barf.
1179 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1180 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1181 if (dmsgtext != NULL) {
1182 ret->cm_fields['M'] = dmsgtext->ptr;
1183 dmsgtext->ptr = NULL;
1187 if (ret->cm_fields['M'] == NULL) {
1188 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1191 /* Perform "before read" hooks (aborting if any return nonzero) */
1192 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1193 CtdlFreeMessage(ret);
1202 * Returns 1 if the supplied pointer points to a valid Citadel message.
1203 * If the pointer is NULL or the magic number check fails, returns 0.
1205 int is_valid_message(struct CtdlMessage *msg) {
1208 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1209 syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1217 * 'Destructor' for struct CtdlMessage
1219 void CtdlFreeMessage(struct CtdlMessage *msg)
1223 if (is_valid_message(msg) == 0)
1225 if (msg != NULL) free (msg);
1229 for (i = 0; i < 256; ++i)
1230 if (msg->cm_fields[i] != NULL) {
1231 free(msg->cm_fields[i]);
1234 msg->cm_magic = 0; /* just in case */
1240 * Pre callback function for multipart/alternative
1242 * NOTE: this differs from the standard behavior for a reason. Normally when
1243 * displaying multipart/alternative you want to show the _last_ usable
1244 * format in the message. Here we show the _first_ one, because it's
1245 * usually text/plain. Since this set of functions is designed for text
1246 * output to non-MIME-aware clients, this is the desired behavior.
1249 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1250 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1251 char *cbid, void *cbuserdata)
1255 ma = (struct ma_info *)cbuserdata;
1256 syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1257 if (!strcasecmp(cbtype, "multipart/alternative")) {
1261 if (!strcasecmp(cbtype, "message/rfc822")) {
1267 * Post callback function for multipart/alternative
1269 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1270 void *content, char *cbtype, char *cbcharset, size_t length,
1271 char *encoding, char *cbid, void *cbuserdata)
1275 ma = (struct ma_info *)cbuserdata;
1276 syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1277 if (!strcasecmp(cbtype, "multipart/alternative")) {
1281 if (!strcasecmp(cbtype, "message/rfc822")) {
1287 * Inline callback function for mime parser that wants to display text
1289 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1290 void *content, char *cbtype, char *cbcharset, size_t length,
1291 char *encoding, char *cbid, void *cbuserdata)
1298 ma = (struct ma_info *)cbuserdata;
1301 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1302 partnum, filename, cbtype, (long)length);
1305 * If we're in the middle of a multipart/alternative scope and
1306 * we've already printed another section, skip this one.
1308 if ( (ma->is_ma) && (ma->did_print) ) {
1309 syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1314 if ( (!strcasecmp(cbtype, "text/plain"))
1315 || (IsEmptyStr(cbtype)) ) {
1318 client_write(wptr, length);
1319 if (wptr[length-1] != '\n') {
1326 if (!strcasecmp(cbtype, "text/html")) {
1327 ptr = html_to_ascii(content, length, 80, 0);
1329 client_write(ptr, wlen);
1330 if (ptr[wlen-1] != '\n') {
1337 if (ma->use_fo_hooks) {
1338 if (PerformFixedOutputHooks(cbtype, content, length)) {
1339 /* above function returns nonzero if it handled the part */
1344 if (strncasecmp(cbtype, "multipart/", 10)) {
1345 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1346 partnum, filename, cbtype, (long)length);
1352 * The client is elegant and sophisticated and wants to be choosy about
1353 * MIME content types, so figure out which multipart/alternative part
1354 * we're going to send.
1356 * We use a system of weights. When we find a part that matches one of the
1357 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1358 * and then set ma->chosen_pref to that MIME type's position in our preference
1359 * list. If we then hit another match, we only replace the first match if
1360 * the preference value is lower.
1362 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1363 void *content, char *cbtype, char *cbcharset, size_t length,
1364 char *encoding, char *cbid, void *cbuserdata)
1370 ma = (struct ma_info *)cbuserdata;
1372 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1373 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1374 // I don't know if there are any side effects! Please TEST TEST TEST
1375 //if (ma->is_ma > 0) {
1377 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1378 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1379 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1380 if (i < ma->chosen_pref) {
1381 syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1382 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1383 ma->chosen_pref = i;
1390 * Now that we've chosen our preferred part, output it.
1392 void output_preferred(char *name,
1406 int add_newline = 0;
1409 char *decoded = NULL;
1410 size_t bytes_decoded;
1413 ma = (struct ma_info *)cbuserdata;
1415 /* This is not the MIME part you're looking for... */
1416 if (strcasecmp(partnum, ma->chosen_part)) return;
1418 /* If the content-type of this part is in our preferred formats
1419 * list, we can simply output it verbatim.
1421 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1422 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1423 if (!strcasecmp(buf, cbtype)) {
1424 /* Yeah! Go! W00t!! */
1425 if (ma->dont_decode == 0)
1426 rc = mime_decode_now (content,
1432 break; /* Give us the chance, maybe theres another one. */
1434 if (rc == 0) text_content = (char *)content;
1436 text_content = decoded;
1437 length = bytes_decoded;
1440 if (text_content[length-1] != '\n') {
1443 cprintf("Content-type: %s", cbtype);
1444 if (!IsEmptyStr(cbcharset)) {
1445 cprintf("; charset=%s", cbcharset);
1447 cprintf("\nContent-length: %d\n",
1448 (int)(length + add_newline) );
1449 if (!IsEmptyStr(encoding)) {
1450 cprintf("Content-transfer-encoding: %s\n", encoding);
1453 cprintf("Content-transfer-encoding: 7bit\n");
1455 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1457 client_write(text_content, length);
1458 if (add_newline) cprintf("\n");
1459 if (decoded != NULL) free(decoded);
1464 /* No translations required or possible: output as text/plain */
1465 cprintf("Content-type: text/plain\n\n");
1467 if (ma->dont_decode == 0)
1468 rc = mime_decode_now (content,
1474 return; /* Give us the chance, maybe theres another one. */
1476 if (rc == 0) text_content = (char *)content;
1478 text_content = decoded;
1479 length = bytes_decoded;
1482 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1483 length, encoding, cbid, cbuserdata);
1484 if (decoded != NULL) free(decoded);
1489 char desired_section[64];
1496 * Callback function for
1498 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1499 void *content, char *cbtype, char *cbcharset, size_t length,
1500 char *encoding, char *cbid, void *cbuserdata)
1502 struct encapmsg *encap;
1504 encap = (struct encapmsg *)cbuserdata;
1506 /* Only proceed if this is the desired section... */
1507 if (!strcasecmp(encap->desired_section, partnum)) {
1508 encap->msglen = length;
1509 encap->msg = malloc(length + 2);
1510 memcpy(encap->msg, content, length);
1517 * Determine whether the currently logged in session has permission to read
1518 * messages in the current room.
1520 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1521 if ( (!(CC->logged_in))
1522 && (!(CC->internal_pgm))
1523 && (!config.c_guest_logins)
1525 return(om_not_logged_in);
1532 * Get a message off disk. (returns om_* values found in msgbase.h)
1535 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1536 int mode, /* how would you like that message? */
1537 int headers_only, /* eschew the message body? */
1538 int do_proto, /* do Citadel protocol responses? */
1539 int crlf, /* Use CRLF newlines instead of LF? */
1540 char *section, /* NULL or a message/rfc822 section */
1541 int flags /* various flags; see msgbase.h */
1543 struct CtdlMessage *TheMessage = NULL;
1544 int retcode = om_no_such_msg;
1545 struct encapmsg encap;
1548 syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1550 (section ? section : "<>")
1553 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1556 if (r == om_not_logged_in) {
1557 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1560 cprintf("%d An unknown error has occurred.\n", ERROR);
1567 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1568 * request that we don't even bother loading the body into memory.
1570 if (headers_only == HEADERS_FAST) {
1571 TheMessage = CtdlFetchMessage(msg_num, 0);
1574 TheMessage = CtdlFetchMessage(msg_num, 1);
1577 if (TheMessage == NULL) {
1578 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1579 ERROR + MESSAGE_NOT_FOUND, msg_num);
1580 return(om_no_such_msg);
1583 /* Here is the weird form of this command, to process only an
1584 * encapsulated message/rfc822 section.
1586 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1587 memset(&encap, 0, sizeof encap);
1588 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1589 mime_parser(TheMessage->cm_fields['M'],
1591 *extract_encapsulated_message,
1592 NULL, NULL, (void *)&encap, 0
1594 CtdlFreeMessage(TheMessage);
1598 encap.msg[encap.msglen] = 0;
1599 TheMessage = convert_internet_message(encap.msg);
1600 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1602 /* Now we let it fall through to the bottom of this
1603 * function, because TheMessage now contains the
1604 * encapsulated message instead of the top-level
1605 * message. Isn't that neat?
1610 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1611 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1612 retcode = om_no_such_msg;
1617 /* Ok, output the message now */
1618 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1619 CtdlFreeMessage(TheMessage);
1625 char *qp_encode_email_addrs(char *source)
1627 char *user, *node, *name;
1628 const char headerStr[] = "=?UTF-8?Q?";
1632 int need_to_encode = 0;
1638 long nAddrPtrMax = 50;
1643 if (source == NULL) return source;
1644 if (IsEmptyStr(source)) return source;
1646 syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1648 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1649 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1650 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1653 while (!IsEmptyStr (&source[i])) {
1654 if (nColons >= nAddrPtrMax){
1657 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1658 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1659 free (AddrPtr), AddrPtr = ptr;
1661 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1662 memset(&ptr[nAddrPtrMax], 0,
1663 sizeof (long) * nAddrPtrMax);
1665 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1666 free (AddrUtf8), AddrUtf8 = ptr;
1669 if (((unsigned char) source[i] < 32) ||
1670 ((unsigned char) source[i] > 126)) {
1672 AddrUtf8[nColons] = 1;
1674 if (source[i] == '"')
1675 InQuotes = !InQuotes;
1676 if (!InQuotes && source[i] == ',') {
1677 AddrPtr[nColons] = i;
1682 if (need_to_encode == 0) {
1689 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1690 Encoded = (char*) malloc (EncodedMaxLen);
1692 for (i = 0; i < nColons; i++)
1693 source[AddrPtr[i]++] = '\0';
1694 /* TODO: if libidn, this might get larger*/
1695 user = malloc(SourceLen + 1);
1696 node = malloc(SourceLen + 1);
1697 name = malloc(SourceLen + 1);
1701 for (i = 0; i < nColons && nPtr != NULL; i++) {
1702 nmax = EncodedMaxLen - (nPtr - Encoded);
1704 process_rfc822_addr(&source[AddrPtr[i]],
1708 /* TODO: libIDN here ! */
1709 if (IsEmptyStr(name)) {
1710 n = snprintf(nPtr, nmax,
1711 (i==0)?"%s@%s" : ",%s@%s",
1715 EncodedName = rfc2047encode(name, strlen(name));
1716 n = snprintf(nPtr, nmax,
1717 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1718 EncodedName, user, node);
1723 n = snprintf(nPtr, nmax,
1724 (i==0)?"%s" : ",%s",
1725 &source[AddrPtr[i]]);
1731 ptr = (char*) malloc(EncodedMaxLen * 2);
1732 memcpy(ptr, Encoded, EncodedMaxLen);
1733 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1734 free(Encoded), Encoded = ptr;
1736 i--; /* do it once more with properly lengthened buffer */
1739 for (i = 0; i < nColons; i++)
1740 source[--AddrPtr[i]] = ',';
1751 /* If the last item in a list of recipients was truncated to a partial address,
1752 * remove it completely in order to avoid choking libSieve
1754 void sanitize_truncated_recipient(char *str)
1757 if (num_tokens(str, ',') < 2) return;
1759 int len = strlen(str);
1760 if (len < 900) return;
1761 if (len > 998) str[998] = 0;
1763 char *cptr = strrchr(str, ',');
1766 char *lptr = strchr(cptr, '<');
1767 char *rptr = strchr(cptr, '>');
1769 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1775 void OutputCtdlMsgHeaders(
1776 struct CtdlMessage *TheMessage,
1777 int do_proto) /* do Citadel protocol responses? */
1783 char display_name[256];
1785 /* begin header processing loop for Citadel message format */
1786 safestrncpy(display_name, "<unknown>", sizeof display_name);
1787 if (TheMessage->cm_fields['A']) {
1788 strcpy(buf, TheMessage->cm_fields['A']);
1789 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1790 safestrncpy(display_name, "****", sizeof display_name);
1792 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1793 safestrncpy(display_name, "anonymous", sizeof display_name);
1796 safestrncpy(display_name, buf, sizeof display_name);
1798 if ((is_room_aide())
1799 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1800 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1801 size_t tmp = strlen(display_name);
1802 snprintf(&display_name[tmp],
1803 sizeof display_name - tmp,
1808 /* Don't show Internet address for users on the
1809 * local Citadel network.
1812 if (TheMessage->cm_fields['N'] != NULL)
1813 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1814 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1818 /* Now spew the header fields in the order we like them. */
1819 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1820 for (i=0; i<n; ++i) {
1821 k = (int) allkeys[i];
1823 if ( (TheMessage->cm_fields[k] != NULL)
1824 && (msgkeys[k] != NULL) ) {
1825 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1826 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1829 if (do_proto) cprintf("%s=%s\n",
1833 else if ((k == 'F') && (suppress_f)) {
1836 /* Masquerade display name if needed */
1838 if (do_proto) cprintf("%s=%s\n",
1840 TheMessage->cm_fields[k]
1849 void OutputRFC822MsgHeaders(
1850 struct CtdlMessage *TheMessage,
1851 int flags, /* should the bessage be exported clean */
1853 char *mid, long sizeof_mid,
1854 char *suser, long sizeof_suser,
1855 char *luser, long sizeof_luser,
1856 char *fuser, long sizeof_fuser,
1857 char *snode, long sizeof_snode)
1859 char datestamp[100];
1860 int subject_found = 0;
1866 for (i = 0; i < 256; ++i) {
1867 if (TheMessage->cm_fields[i]) {
1868 mptr = mpptr = TheMessage->cm_fields[i];
1871 safestrncpy(luser, mptr, sizeof_luser);
1872 safestrncpy(suser, mptr, sizeof_suser);
1874 else if (i == 'Y') {
1875 if ((flags & QP_EADDR) != 0) {
1876 mptr = qp_encode_email_addrs(mptr);
1878 sanitize_truncated_recipient(mptr);
1879 cprintf("CC: %s%s", mptr, nl);
1881 else if (i == 'P') {
1882 cprintf("Return-Path: %s%s", mptr, nl);
1884 else if (i == 'L') {
1885 cprintf("List-ID: %s%s", mptr, nl);
1887 else if (i == 'V') {
1888 if ((flags & QP_EADDR) != 0)
1889 mptr = qp_encode_email_addrs(mptr);
1890 cprintf("Envelope-To: %s%s", mptr, nl);
1892 else if (i == 'U') {
1893 cprintf("Subject: %s%s", mptr, nl);
1897 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1899 safestrncpy(fuser, mptr, sizeof_fuser);
1900 /* else if (i == 'O')
1901 cprintf("X-Citadel-Room: %s%s",
1904 safestrncpy(snode, mptr, sizeof_snode);
1907 if (haschar(mptr, '@') == 0)
1909 sanitize_truncated_recipient(mptr);
1910 cprintf("To: %s@%s", mptr, config.c_fqdn);
1915 if ((flags & QP_EADDR) != 0) {
1916 mptr = qp_encode_email_addrs(mptr);
1918 sanitize_truncated_recipient(mptr);
1919 cprintf("To: %s", mptr);
1923 else if (i == 'T') {
1924 datestring(datestamp, sizeof datestamp,
1925 atol(mptr), DATESTRING_RFC822);
1926 cprintf("Date: %s%s", datestamp, nl);
1928 else if (i == 'W') {
1929 cprintf("References: ");
1930 k = num_tokens(mptr, '|');
1931 for (j=0; j<k; ++j) {
1932 extract_token(buf, mptr, j, '|', sizeof buf);
1933 cprintf("<%s>", buf);
1942 else if (i == 'K') {
1943 cprintf("Reply-To: <%s>%s", mptr, nl);
1949 if (subject_found == 0) {
1950 cprintf("Subject: (no subject)%s", nl);
1955 void Dump_RFC822HeadersBody(
1956 struct CtdlMessage *TheMessage,
1957 int headers_only, /* eschew the message body? */
1958 int flags, /* should the bessage be exported clean? */
1962 cit_uint8_t prev_ch;
1964 const char *StartOfText = StrBufNOTNULL;
1967 int nllen = strlen(nl);
1970 mptr = TheMessage->cm_fields['M'];
1974 while (*mptr != '\0') {
1975 if (*mptr == '\r') {
1982 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1984 eoh = *(mptr+1) == '\n';
1988 StartOfText = strchr(StartOfText, '\n');
1989 StartOfText = strchr(StartOfText, '\n');
1992 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1993 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1994 ((headers_only != HEADERS_NONE) &&
1995 (headers_only != HEADERS_ONLY))
1997 if (*mptr == '\n') {
1998 memcpy(&outbuf[outlen], nl, nllen);
2000 outbuf[outlen] = '\0';
2003 outbuf[outlen++] = *mptr;
2007 if (flags & ESC_DOT)
2009 if ((prev_ch == '\n') &&
2011 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2013 outbuf[outlen++] = '.';
2018 if (outlen > 1000) {
2019 client_write(outbuf, outlen);
2024 client_write(outbuf, outlen);
2031 /* If the format type on disk is 1 (fixed-format), then we want
2032 * everything to be output completely literally ... regardless of
2033 * what message transfer format is in use.
2035 void DumpFormatFixed(
2036 struct CtdlMessage *TheMessage,
2037 int mode, /* how would you like that message? */
2044 int nllen = strlen (nl);
2047 mptr = TheMessage->cm_fields['M'];
2049 if (mode == MT_MIME) {
2050 cprintf("Content-type: text/plain\n\n");
2054 while (ch = *mptr++, ch > 0) {
2058 if ((buflen > 250) && (!xlline)){
2062 while ((buflen > 0) &&
2063 (!isspace(buf[buflen])))
2069 mptr -= tbuflen - buflen;
2074 /* if we reach the outer bounds of our buffer,
2075 abort without respect what whe purge. */
2078 (buflen > SIZ - nllen - 2)))
2082 memcpy (&buf[buflen], nl, nllen);
2086 client_write(buf, buflen);
2096 if (!IsEmptyStr(buf))
2097 cprintf("%s%s", buf, nl);
2101 * Get a message off disk. (returns om_* values found in msgbase.h)
2103 int CtdlOutputPreLoadedMsg(
2104 struct CtdlMessage *TheMessage,
2105 int mode, /* how would you like that message? */
2106 int headers_only, /* eschew the message body? */
2107 int do_proto, /* do Citadel protocol responses? */
2108 int crlf, /* Use CRLF newlines instead of LF? */
2109 int flags /* should the bessage be exported clean? */
2113 const char *nl; /* newline string */
2116 /* Buffers needed for RFC822 translation. These are all filled
2117 * using functions that are bounds-checked, and therefore we can
2118 * make them substantially smaller than SIZ.
2126 syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2127 ((TheMessage == NULL) ? "NULL" : "not null"),
2128 mode, headers_only, do_proto, crlf);
2130 strcpy(mid, "unknown");
2131 nl = (crlf ? "\r\n" : "\n");
2133 if (!is_valid_message(TheMessage)) {
2135 "ERROR: invalid preloaded message for output\n");
2137 return(om_no_such_msg);
2140 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2141 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2143 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2144 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2147 /* Are we downloading a MIME component? */
2148 if (mode == MT_DOWNLOAD) {
2149 if (TheMessage->cm_format_type != FMT_RFC822) {
2151 cprintf("%d This is not a MIME message.\n",
2152 ERROR + ILLEGAL_VALUE);
2153 } else if (CC->download_fp != NULL) {
2154 if (do_proto) cprintf(
2155 "%d You already have a download open.\n",
2156 ERROR + RESOURCE_BUSY);
2158 /* Parse the message text component */
2159 mptr = TheMessage->cm_fields['M'];
2160 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2161 /* If there's no file open by this time, the requested
2162 * section wasn't found, so print an error
2164 if (CC->download_fp == NULL) {
2165 if (do_proto) cprintf(
2166 "%d Section %s not found.\n",
2167 ERROR + FILE_NOT_FOUND,
2168 CC->download_desired_section);
2171 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2174 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2175 * in a single server operation instead of opening a download file.
2177 if (mode == MT_SPEW_SECTION) {
2178 if (TheMessage->cm_format_type != FMT_RFC822) {
2180 cprintf("%d This is not a MIME message.\n",
2181 ERROR + ILLEGAL_VALUE);
2183 /* Parse the message text component */
2186 mptr = TheMessage->cm_fields['M'];
2187 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2188 /* If section wasn't found, print an error
2191 if (do_proto) cprintf(
2192 "%d Section %s not found.\n",
2193 ERROR + FILE_NOT_FOUND,
2194 CC->download_desired_section);
2197 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2200 /* now for the user-mode message reading loops */
2201 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2203 /* Does the caller want to skip the headers? */
2204 if (headers_only == HEADERS_NONE) goto START_TEXT;
2206 /* Tell the client which format type we're using. */
2207 if ( (mode == MT_CITADEL) && (do_proto) ) {
2208 cprintf("type=%d\n", TheMessage->cm_format_type);
2211 /* nhdr=yes means that we're only displaying headers, no body */
2212 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2213 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2216 cprintf("nhdr=yes\n");
2219 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2220 OutputCtdlMsgHeaders(TheMessage, do_proto);
2223 /* begin header processing loop for RFC822 transfer format */
2227 strcpy(snode, NODENAME);
2228 if (mode == MT_RFC822)
2229 OutputRFC822MsgHeaders(
2234 suser, sizeof(suser),
2235 luser, sizeof(luser),
2236 fuser, sizeof(fuser),
2237 snode, sizeof(snode)
2241 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2242 suser[i] = tolower(suser[i]);
2243 if (!isalnum(suser[i])) suser[i]='_';
2246 if (mode == MT_RFC822) {
2247 if (!strcasecmp(snode, NODENAME)) {
2248 safestrncpy(snode, FQDN, sizeof snode);
2251 /* Construct a fun message id */
2252 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2253 if (strchr(mid, '@')==NULL) {
2254 cprintf("@%s", snode);
2258 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2259 cprintf("From: \"----\" <x@x.org>%s", nl);
2261 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2262 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2264 else if (!IsEmptyStr(fuser)) {
2265 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2268 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2271 /* Blank line signifying RFC822 end-of-headers */
2272 if (TheMessage->cm_format_type != FMT_RFC822) {
2277 /* end header processing loop ... at this point, we're in the text */
2279 if (headers_only == HEADERS_FAST) goto DONE;
2281 /* Tell the client about the MIME parts in this message */
2282 if (TheMessage->cm_format_type == FMT_RFC822) {
2283 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2284 mptr = TheMessage->cm_fields['M'];
2285 memset(&ma, 0, sizeof(struct ma_info));
2286 mime_parser(mptr, NULL,
2287 (do_proto ? *list_this_part : NULL),
2288 (do_proto ? *list_this_pref : NULL),
2289 (do_proto ? *list_this_suff : NULL),
2292 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2293 Dump_RFC822HeadersBody(
2302 if (headers_only == HEADERS_ONLY) {
2306 /* signify start of msg text */
2307 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2308 if (do_proto) cprintf("text\n");
2311 if (TheMessage->cm_format_type == FMT_FIXED)
2314 mode, /* how would you like that message? */
2317 /* If the message on disk is format 0 (Citadel vari-format), we
2318 * output using the formatter at 80 columns. This is the final output
2319 * form if the transfer format is RFC822, but if the transfer format
2320 * is Citadel proprietary, it'll still work, because the indentation
2321 * for new paragraphs is correct and the client will reformat the
2322 * message to the reader's screen width.
2324 if (TheMessage->cm_format_type == FMT_CITADEL) {
2325 mptr = TheMessage->cm_fields['M'];
2327 if (mode == MT_MIME) {
2328 cprintf("Content-type: text/x-citadel-variformat\n\n");
2333 /* If the message on disk is format 4 (MIME), we've gotta hand it
2334 * off to the MIME parser. The client has already been told that
2335 * this message is format 1 (fixed format), so the callback function
2336 * we use will display those parts as-is.
2338 if (TheMessage->cm_format_type == FMT_RFC822) {
2339 memset(&ma, 0, sizeof(struct ma_info));
2341 if (mode == MT_MIME) {
2342 ma.use_fo_hooks = 0;
2343 strcpy(ma.chosen_part, "1");
2344 ma.chosen_pref = 9999;
2345 ma.dont_decode = CC->msg4_dont_decode;
2346 mime_parser(mptr, NULL,
2347 *choose_preferred, *fixed_output_pre,
2348 *fixed_output_post, (void *)&ma, 1);
2349 mime_parser(mptr, NULL,
2350 *output_preferred, NULL, NULL, (void *)&ma, 1);
2353 ma.use_fo_hooks = 1;
2354 mime_parser(mptr, NULL,
2355 *fixed_output, *fixed_output_pre,
2356 *fixed_output_post, (void *)&ma, 0);
2361 DONE: /* now we're done */
2362 if (do_proto) cprintf("000\n");
2368 * display a message (mode 0 - Citadel proprietary)
2370 void cmd_msg0(char *cmdbuf)
2373 int headers_only = HEADERS_ALL;
2375 msgid = extract_long(cmdbuf, 0);
2376 headers_only = extract_int(cmdbuf, 1);
2378 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2384 * display a message (mode 2 - RFC822)
2386 void cmd_msg2(char *cmdbuf)
2389 int headers_only = HEADERS_ALL;
2391 msgid = extract_long(cmdbuf, 0);
2392 headers_only = extract_int(cmdbuf, 1);
2394 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2400 * display a message (mode 3 - IGnet raw format - internal programs only)
2402 void cmd_msg3(char *cmdbuf)
2405 struct CtdlMessage *msg = NULL;
2408 if (CC->internal_pgm == 0) {
2409 cprintf("%d This command is for internal programs only.\n",
2410 ERROR + HIGHER_ACCESS_REQUIRED);
2414 msgnum = extract_long(cmdbuf, 0);
2415 msg = CtdlFetchMessage(msgnum, 1);
2417 cprintf("%d Message %ld not found.\n",
2418 ERROR + MESSAGE_NOT_FOUND, msgnum);
2422 serialize_message(&smr, msg);
2423 CtdlFreeMessage(msg);
2426 cprintf("%d Unable to serialize message\n",
2427 ERROR + INTERNAL_ERROR);
2431 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2432 client_write((char *)smr.ser, (int)smr.len);
2439 * Display a message using MIME content types
2441 void cmd_msg4(char *cmdbuf)
2446 msgid = extract_long(cmdbuf, 0);
2447 extract_token(section, cmdbuf, 1, '|', sizeof section);
2448 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2454 * Client tells us its preferred message format(s)
2456 void cmd_msgp(char *cmdbuf)
2458 if (!strcasecmp(cmdbuf, "dont_decode")) {
2459 CC->msg4_dont_decode = 1;
2460 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2463 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2464 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2470 * Open a component of a MIME message as a download file
2472 void cmd_opna(char *cmdbuf)
2475 char desired_section[128];
2477 msgid = extract_long(cmdbuf, 0);
2478 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2479 safestrncpy(CC->download_desired_section, desired_section,
2480 sizeof CC->download_desired_section);
2481 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2486 * Open a component of a MIME message and transmit it all at once
2488 void cmd_dlat(char *cmdbuf)
2491 char desired_section[128];
2493 msgid = extract_long(cmdbuf, 0);
2494 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2495 safestrncpy(CC->download_desired_section, desired_section,
2496 sizeof CC->download_desired_section);
2497 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2502 * Save one or more message pointers into a specified room
2503 * (Returns 0 for success, nonzero for failure)
2504 * roomname may be NULL to use the current room
2506 * Note that the 'supplied_msg' field may be set to NULL, in which case
2507 * the message will be fetched from disk, by number, if we need to perform
2508 * replication checks. This adds an additional database read, so if the
2509 * caller already has the message in memory then it should be supplied. (Obviously
2510 * this mode of operation only works if we're saving a single message.)
2512 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2513 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2516 char hold_rm[ROOMNAMELEN];
2517 struct cdbdata *cdbfr;
2520 long highest_msg = 0L;
2523 struct CtdlMessage *msg = NULL;
2525 long *msgs_to_be_merged = NULL;
2526 int num_msgs_to_be_merged = 0;
2529 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2530 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2533 strcpy(hold_rm, CC->room.QRname);
2536 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2537 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2538 if (num_newmsgs > 1) supplied_msg = NULL;
2540 /* Now the regular stuff */
2541 if (CtdlGetRoomLock(&CC->room,
2542 ((roomname != NULL) ? roomname : CC->room.QRname) )
2544 syslog(LOG_ERR, "No such room <%s>\n", roomname);
2545 return(ERROR + ROOM_NOT_FOUND);
2549 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2550 num_msgs_to_be_merged = 0;
2553 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2554 if (cdbfr == NULL) {
2558 msglist = (long *) cdbfr->ptr;
2559 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2560 num_msgs = cdbfr->len / sizeof(long);
2565 /* Create a list of msgid's which were supplied by the caller, but do
2566 * not already exist in the target room. It is absolutely taboo to
2567 * have more than one reference to the same message in a room.
2569 for (i=0; i<num_newmsgs; ++i) {
2571 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2572 if (msglist[j] == newmsgidlist[i]) {
2577 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2581 syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2584 * Now merge the new messages
2586 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2587 if (msglist == NULL) {
2588 syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2590 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2591 num_msgs += num_msgs_to_be_merged;
2593 /* Sort the message list, so all the msgid's are in order */
2594 num_msgs = sort_msglist(msglist, num_msgs);
2596 /* Determine the highest message number */
2597 highest_msg = msglist[num_msgs - 1];
2599 /* Write it back to disk. */
2600 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2601 msglist, (int)(num_msgs * sizeof(long)));
2603 /* Free up the memory we used. */
2606 /* Update the highest-message pointer and unlock the room. */
2607 CC->room.QRhighest = highest_msg;
2608 CtdlPutRoomLock(&CC->room);
2610 /* Perform replication checks if necessary */
2611 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2612 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2614 for (i=0; i<num_msgs_to_be_merged; ++i) {
2615 msgid = msgs_to_be_merged[i];
2617 if (supplied_msg != NULL) {
2621 msg = CtdlFetchMessage(msgid, 0);
2625 ReplicationChecks(msg);
2627 /* If the message has an Exclusive ID, index that... */
2628 if (msg->cm_fields['E'] != NULL) {
2629 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2632 /* Free up the memory we may have allocated */
2633 if (msg != supplied_msg) {
2634 CtdlFreeMessage(msg);
2642 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2645 /* Submit this room for processing by hooks */
2646 PerformRoomHooks(&CC->room);
2648 /* Go back to the room we were in before we wandered here... */
2649 CtdlGetRoom(&CC->room, hold_rm);
2651 /* Bump the reference count for all messages which were merged */
2652 if (!suppress_refcount_adj) {
2653 for (i=0; i<num_msgs_to_be_merged; ++i) {
2654 AdjRefCount(msgs_to_be_merged[i], +1);
2658 /* Free up memory... */
2659 if (msgs_to_be_merged != NULL) {
2660 free(msgs_to_be_merged);
2663 /* Return success. */
2669 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2672 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2673 int do_repl_check, struct CtdlMessage *supplied_msg)
2675 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2682 * Message base operation to save a new message to the message store
2683 * (returns new message number)
2685 * This is the back end for CtdlSubmitMsg() and should not be directly
2686 * called by server-side modules.
2689 long send_message(struct CtdlMessage *msg) {
2697 /* Get a new message number */
2698 newmsgid = get_new_message_number();
2699 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2701 /* Generate an ID if we don't have one already */
2702 if (msg->cm_fields['I']==NULL) {
2703 msg->cm_fields['I'] = strdup(msgidbuf);
2706 /* If the message is big, set its body aside for storage elsewhere */
2707 if (msg->cm_fields['M'] != NULL) {
2708 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2710 holdM = msg->cm_fields['M'];
2711 msg->cm_fields['M'] = NULL;
2715 /* Serialize our data structure for storage in the database */
2716 serialize_message(&smr, msg);
2719 msg->cm_fields['M'] = holdM;
2723 cprintf("%d Unable to serialize message\n",
2724 ERROR + INTERNAL_ERROR);
2728 /* Write our little bundle of joy into the message base */
2729 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2730 smr.ser, smr.len) < 0) {
2731 syslog(LOG_ERR, "Can't store message\n");
2735 cdb_store(CDB_BIGMSGS,
2745 /* Free the memory we used for the serialized message */
2748 /* Return the *local* message ID to the caller
2749 * (even if we're storing an incoming network message)
2757 * Serialize a struct CtdlMessage into the format used on disk and network.
2759 * This function loads up a "struct ser_ret" (defined in server.h) which
2760 * contains the length of the serialized message and a pointer to the
2761 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2763 void serialize_message(struct ser_ret *ret, /* return values */
2764 struct CtdlMessage *msg) /* unserialized msg */
2766 size_t wlen, fieldlen;
2768 static char *forder = FORDER;
2771 * Check for valid message format
2773 if (is_valid_message(msg) == 0) {
2774 syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
2781 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2782 ret->len = ret->len +
2783 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2785 ret->ser = malloc(ret->len);
2786 if (ret->ser == NULL) {
2787 syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2788 (long)ret->len, strerror(errno));
2795 ret->ser[1] = msg->cm_anon_type;
2796 ret->ser[2] = msg->cm_format_type;
2799 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2800 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2801 ret->ser[wlen++] = (char)forder[i];
2802 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2803 wlen = wlen + fieldlen + 1;
2805 if (ret->len != wlen) syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2806 (long)ret->len, (long)wlen);
2813 * Serialize a struct CtdlMessage into the format used on disk and network.
2815 * This function loads up a "struct ser_ret" (defined in server.h) which
2816 * contains the length of the serialized message and a pointer to the
2817 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2819 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2820 long Siz) /* how many chars ? */
2824 static char *forder = FORDER;
2828 * Check for valid message format
2830 if (is_valid_message(msg) == 0) {
2831 syslog(LOG_ERR, "dump_message() aborting due to invalid message\n");
2835 buf = (char*) malloc (Siz + 1);
2839 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2840 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2841 msg->cm_fields[(int)forder[i]]);
2842 client_write (buf, strlen(buf));
2851 * Check to see if any messages already exist in the current room which
2852 * carry the same Exclusive ID as this one. If any are found, delete them.
2854 void ReplicationChecks(struct CtdlMessage *msg) {
2855 long old_msgnum = (-1L);
2857 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2859 syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2862 /* No exclusive id? Don't do anything. */
2863 if (msg == NULL) return;
2864 if (msg->cm_fields['E'] == NULL) return;
2865 if (IsEmptyStr(msg->cm_fields['E'])) return;
2866 /*syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2867 msg->cm_fields['E'], CC->room.QRname);*/
2869 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2870 if (old_msgnum > 0L) {
2871 syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2872 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2879 * Save a message to disk and submit it into the delivery system.
2881 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2882 struct recptypes *recps, /* recipients (if mail) */
2883 char *force, /* force a particular room? */
2884 int flags /* should the message be exported clean? */
2886 char submit_filename[128];
2887 char generated_timestamp[32];
2888 char hold_rm[ROOMNAMELEN];
2889 char actual_rm[ROOMNAMELEN];
2890 char force_room[ROOMNAMELEN];
2891 char content_type[SIZ]; /* We have to learn this */
2892 char recipient[SIZ];
2894 const char *mptr = NULL;
2895 struct ctdluser userbuf;
2897 struct MetaData smi;
2898 FILE *network_fp = NULL;
2899 static int seqnum = 1;
2900 struct CtdlMessage *imsg = NULL;
2902 size_t instr_alloc = 0;
2904 char *hold_R, *hold_D;
2905 char *collected_addresses = NULL;
2906 struct addresses_to_be_filed *aptr = NULL;
2907 StrBuf *saved_rfc822_version = NULL;
2908 int qualified_for_journaling = 0;
2909 CitContext *CCC = MyContext();
2910 char bounce_to[1024] = "";
2914 syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
2915 if (is_valid_message(msg) == 0) return(-1); /* self check */
2917 /* If this message has no timestamp, we take the liberty of
2918 * giving it one, right now.
2920 if (msg->cm_fields['T'] == NULL) {
2921 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2922 msg->cm_fields['T'] = strdup(generated_timestamp);
2925 /* If this message has no path, we generate one.
2927 if (msg->cm_fields['P'] == NULL) {
2928 if (msg->cm_fields['A'] != NULL) {
2929 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2930 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2931 if (isspace(msg->cm_fields['P'][a])) {
2932 msg->cm_fields['P'][a] = ' ';
2937 msg->cm_fields['P'] = strdup("unknown");
2941 if (force == NULL) {
2942 strcpy(force_room, "");
2945 strcpy(force_room, force);
2948 /* Learn about what's inside, because it's what's inside that counts */
2949 if (msg->cm_fields['M'] == NULL) {
2950 syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
2954 switch (msg->cm_format_type) {
2956 strcpy(content_type, "text/x-citadel-variformat");
2959 strcpy(content_type, "text/plain");
2962 strcpy(content_type, "text/plain");
2963 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2966 safestrncpy(content_type, &mptr[13], sizeof content_type);
2967 striplt(content_type);
2968 aptr = content_type;
2969 while (!IsEmptyStr(aptr)) {
2981 /* Goto the correct room */
2982 syslog(LOG_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2983 strcpy(hold_rm, CCC->room.QRname);
2984 strcpy(actual_rm, CCC->room.QRname);
2985 if (recps != NULL) {
2986 strcpy(actual_rm, SENTITEMS);
2989 /* If the user is a twit, move to the twit room for posting */
2991 if (CCC->user.axlevel == AxProbU) {
2992 strcpy(hold_rm, actual_rm);
2993 strcpy(actual_rm, config.c_twitroom);
2994 syslog(LOG_DEBUG, "Diverting to twit room\n");
2998 /* ...or if this message is destined for Aide> then go there. */
2999 if (!IsEmptyStr(force_room)) {
3000 strcpy(actual_rm, force_room);
3003 syslog(LOG_DEBUG, "Final selection: %s\n", actual_rm);
3004 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3005 /* CtdlGetRoom(&CCC->room, actual_rm); */
3006 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3010 * If this message has no O (room) field, generate one.
3012 if (msg->cm_fields['O'] == NULL) {
3013 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3016 /* Perform "before save" hooks (aborting if any return nonzero) */
3017 syslog(LOG_DEBUG, "Performing before-save hooks\n");
3018 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3021 * If this message has an Exclusive ID, and the room is replication
3022 * checking enabled, then do replication checks.
3024 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3025 ReplicationChecks(msg);
3028 /* Save it to disk */
3029 syslog(LOG_DEBUG, "Saving to disk\n");
3030 newmsgid = send_message(msg);
3031 if (newmsgid <= 0L) return(-5);
3033 /* Write a supplemental message info record. This doesn't have to
3034 * be a critical section because nobody else knows about this message
3037 syslog(LOG_DEBUG, "Creating MetaData record\n");
3038 memset(&smi, 0, sizeof(struct MetaData));
3039 smi.meta_msgnum = newmsgid;
3040 smi.meta_refcount = 0;
3041 safestrncpy(smi.meta_content_type, content_type,
3042 sizeof smi.meta_content_type);
3045 * Measure how big this message will be when rendered as RFC822.
3046 * We do this for two reasons:
3047 * 1. We need the RFC822 length for the new metadata record, so the
3048 * POP and IMAP services don't have to calculate message lengths
3049 * while the user is waiting (multiplied by potentially hundreds
3050 * or thousands of messages).
3051 * 2. If journaling is enabled, we will need an RFC822 version of the
3052 * message to attach to the journalized copy.
3054 if (CCC->redirect_buffer != NULL) {
3055 syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3058 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3059 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3060 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3061 saved_rfc822_version = CCC->redirect_buffer;
3062 CCC->redirect_buffer = NULL;
3066 /* Now figure out where to store the pointers */
3067 syslog(LOG_DEBUG, "Storing pointers\n");
3069 /* If this is being done by the networker delivering a private
3070 * message, we want to BYPASS saving the sender's copy (because there
3071 * is no local sender; it would otherwise go to the Trashcan).
3073 if ((!CCC->internal_pgm) || (recps == NULL)) {
3074 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3075 syslog(LOG_ERR, "ERROR saving message pointer!\n");
3076 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3080 /* For internet mail, drop a copy in the outbound queue room */
3081 if ((recps != NULL) && (recps->num_internet > 0)) {
3082 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3085 /* If other rooms are specified, drop them there too. */
3086 if ((recps != NULL) && (recps->num_room > 0))
3087 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3088 extract_token(recipient, recps->recp_room, i,
3089 '|', sizeof recipient);
3090 syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);
3091 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3094 /* Bump this user's messages posted counter. */
3095 syslog(LOG_DEBUG, "Updating user\n");
3096 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3097 CCC->user.posted = CCC->user.posted + 1;
3098 CtdlPutUserLock(&CCC->user);
3100 /* Decide where bounces need to be delivered */
3101 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3102 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3104 else if (CCC->logged_in) {
3105 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3108 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3111 /* If this is private, local mail, make a copy in the
3112 * recipient's mailbox and bump the reference count.
3114 if ((recps != NULL) && (recps->num_local > 0))
3115 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3116 extract_token(recipient, recps->recp_local, i,
3117 '|', sizeof recipient);
3118 syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3120 if (CtdlGetUser(&userbuf, recipient) == 0) {
3121 // Add a flag so the Funambol module knows its mail
3122 msg->cm_fields['W'] = strdup(recipient);
3123 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3124 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3125 CtdlBumpNewMailCounter(userbuf.usernum);
3126 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3127 /* Generate a instruction message for the Funambol notification
3128 * server, in the same style as the SMTP queue
3131 instr = malloc(instr_alloc);
3132 snprintf(instr, instr_alloc,
3133 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3135 SPOOLMIME, newmsgid, (long)time(NULL),
3139 imsg = malloc(sizeof(struct CtdlMessage));
3140 memset(imsg, 0, sizeof(struct CtdlMessage));
3141 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3142 imsg->cm_anon_type = MES_NORMAL;
3143 imsg->cm_format_type = FMT_RFC822;
3144 imsg->cm_fields['A'] = strdup("Citadel");
3145 imsg->cm_fields['J'] = strdup("do not journal");
3146 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3147 imsg->cm_fields['W'] = strdup(recipient);
3148 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3149 CtdlFreeMessage(imsg);
3153 syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3154 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3159 /* Perform "after save" hooks */
3160 syslog(LOG_DEBUG, "Performing after-save hooks\n");
3161 PerformMessageHooks(msg, EVT_AFTERSAVE);
3163 /* For IGnet mail, we have to save a new copy into the spooler for
3164 * each recipient, with the R and D fields set to the recipient and
3165 * destination-node. This has two ugly side effects: all other
3166 * recipients end up being unlisted in this recipient's copy of the
3167 * message, and it has to deliver multiple messages to the same
3168 * node. We'll revisit this again in a year or so when everyone has
3169 * a network spool receiver that can handle the new style messages.
3171 if ((recps != NULL) && (recps->num_ignet > 0))
3172 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3173 extract_token(recipient, recps->recp_ignet, i,
3174 '|', sizeof recipient);
3176 hold_R = msg->cm_fields['R'];
3177 hold_D = msg->cm_fields['D'];
3178 msg->cm_fields['R'] = malloc(SIZ);
3179 msg->cm_fields['D'] = malloc(128);
3180 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3181 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3183 serialize_message(&smr, msg);
3185 snprintf(submit_filename, sizeof submit_filename,
3186 "%s/netmail.%04lx.%04x.%04x",
3188 (long) getpid(), CCC->cs_pid, ++seqnum);
3189 network_fp = fopen(submit_filename, "wb+");
3190 if (network_fp != NULL) {
3191 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3197 free(msg->cm_fields['R']);
3198 free(msg->cm_fields['D']);
3199 msg->cm_fields['R'] = hold_R;
3200 msg->cm_fields['D'] = hold_D;
3203 /* Go back to the room we started from */
3204 syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3205 if (strcasecmp(hold_rm, CCC->room.QRname))
3206 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3208 /* For internet mail, generate delivery instructions.
3209 * Yes, this is recursive. Deal with it. Infinite recursion does
3210 * not happen because the delivery instructions message does not
3211 * contain a recipient.
3213 if ((recps != NULL) && (recps->num_internet > 0)) {
3214 syslog(LOG_DEBUG, "Generating delivery instructions\n");
3216 instr = malloc(instr_alloc);
3217 snprintf(instr, instr_alloc,
3218 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3220 SPOOLMIME, newmsgid, (long)time(NULL),
3224 if (recps->envelope_from != NULL) {
3225 tmp = strlen(instr);
3226 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3229 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3230 tmp = strlen(instr);
3231 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3232 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3233 instr_alloc = instr_alloc * 2;
3234 instr = realloc(instr, instr_alloc);
3236 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3239 imsg = malloc(sizeof(struct CtdlMessage));
3240 memset(imsg, 0, sizeof(struct CtdlMessage));
3241 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3242 imsg->cm_anon_type = MES_NORMAL;
3243 imsg->cm_format_type = FMT_RFC822;
3244 imsg->cm_fields['A'] = strdup("Citadel");
3245 imsg->cm_fields['J'] = strdup("do not journal");
3246 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3247 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3248 CtdlFreeMessage(imsg);
3252 * Any addresses to harvest for someone's address book?
3254 if ( (CCC->logged_in) && (recps != NULL) ) {
3255 collected_addresses = harvest_collected_addresses(msg);
3258 if (collected_addresses != NULL) {
3259 aptr = (struct addresses_to_be_filed *)
3260 malloc(sizeof(struct addresses_to_be_filed));
3261 CtdlMailboxName(actual_rm, sizeof actual_rm,
3262 &CCC->user, USERCONTACTSROOM);
3263 aptr->roomname = strdup(actual_rm);
3264 aptr->collected_addresses = collected_addresses;
3265 begin_critical_section(S_ATBF);
3268 end_critical_section(S_ATBF);
3272 * Determine whether this message qualifies for journaling.
3274 if (msg->cm_fields['J'] != NULL) {
3275 qualified_for_journaling = 0;
3278 if (recps == NULL) {
3279 qualified_for_journaling = config.c_journal_pubmsgs;
3281 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3282 qualified_for_journaling = config.c_journal_email;
3285 qualified_for_journaling = config.c_journal_pubmsgs;
3290 * Do we have to perform journaling? If so, hand off the saved
3291 * RFC822 version will be handed off to the journaler for background
3292 * submit. Otherwise, we have to free the memory ourselves.
3294 if (saved_rfc822_version != NULL) {
3295 if (qualified_for_journaling) {
3296 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3299 FreeStrBuf(&saved_rfc822_version);
3309 void aide_message (char *text, char *subject)
3311 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3316 * Convenience function for generating small administrative messages.
3318 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3319 int format_type, const char *subject)
3321 struct CtdlMessage *msg;
3322 struct recptypes *recp = NULL;
3324 msg = malloc(sizeof(struct CtdlMessage));
3325 memset(msg, 0, sizeof(struct CtdlMessage));
3326 msg->cm_magic = CTDLMESSAGE_MAGIC;
3327 msg->cm_anon_type = MES_NORMAL;
3328 msg->cm_format_type = format_type;
3331 msg->cm_fields['A'] = strdup(from);
3333 else if (fromaddr != NULL) {
3334 msg->cm_fields['A'] = strdup(fromaddr);
3335 if (strchr(msg->cm_fields['A'], '@')) {
3336 *strchr(msg->cm_fields['A'], '@') = 0;
3340 msg->cm_fields['A'] = strdup("Citadel");
3343 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3344 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3345 msg->cm_fields['N'] = strdup(NODENAME);
3347 msg->cm_fields['R'] = strdup(to);
3348 recp = validate_recipients(to, NULL, 0);
3350 if (subject != NULL) {
3351 msg->cm_fields['U'] = strdup(subject);
3353 msg->cm_fields['M'] = strdup(text);
3355 CtdlSubmitMsg(msg, recp, room, 0);
3356 CtdlFreeMessage(msg);
3357 if (recp != NULL) free_recipients(recp);
3363 * Back end function used by CtdlMakeMessage() and similar functions
3365 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3367 size_t maxlen, /* maximum message length */
3368 char *exist, /* if non-null, append to it;
3369 exist is ALWAYS freed */
3370 int crlf, /* CRLF newlines instead of LF */
3371 int *sock /* socket handle or 0 for this session's client socket */
3380 LineBuf = NewStrBufPlain(NULL, SIZ);
3381 if (exist == NULL) {
3382 Message = NewStrBufPlain(NULL, 4 * SIZ);
3385 Message = NewStrBufPlain(exist, -1);
3389 /* Do we need to change leading ".." to "." for SMTP escaping? */
3390 if ((tlen == 1) && (*terminator == '.')) {
3394 /* read in the lines of message text one by one */
3397 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3402 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3404 if ((StrLength(LineBuf) == tlen) &&
3405 (!strcmp(ChrPtr(LineBuf), terminator)))
3408 if ( (!flushing) && (!finished) ) {
3410 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3413 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3416 /* Unescape SMTP-style input of two dots at the beginning of the line */
3418 (StrLength(LineBuf) == 2) &&
3419 (!strcmp(ChrPtr(LineBuf), "..")))
3421 StrBufCutLeft(LineBuf, 1);
3424 StrBufAppendBuf(Message, LineBuf, 0);
3427 /* if we've hit the max msg length, flush the rest */
3428 if (StrLength(Message) >= maxlen) flushing = 1;
3430 } while (!finished);
3431 FreeStrBuf(&LineBuf);
3437 * Back end function used by CtdlMakeMessage() and similar functions
3439 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3441 size_t maxlen, /* maximum message length */
3442 char *exist, /* if non-null, append to it;
3443 exist is ALWAYS freed */
3444 int crlf, /* CRLF newlines instead of LF */
3445 int *sock /* socket handle or 0 for this session's client socket */
3450 Message = CtdlReadMessageBodyBuf(terminator,
3456 if (Message == NULL)
3459 return SmashStrBuf(&Message);
3464 * Build a binary message to be saved on disk.
3465 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3466 * will become part of the message. This means you are no longer
3467 * responsible for managing that memory -- it will be freed along with
3468 * the rest of the fields when CtdlFreeMessage() is called.)
3471 struct CtdlMessage *CtdlMakeMessage(
3472 struct ctdluser *author, /* author's user structure */
3473 char *recipient, /* NULL if it's not mail */
3474 char *recp_cc, /* NULL if it's not mail */
3475 char *room, /* room where it's going */
3476 int type, /* see MES_ types in header file */
3477 int format_type, /* variformat, plain text, MIME... */
3478 char *fake_name, /* who we're masquerading as */
3479 char *my_email, /* which of my email addresses to use (empty is ok) */
3480 char *subject, /* Subject (optional) */
3481 char *supplied_euid, /* ...or NULL if this is irrelevant */
3482 char *preformatted_text, /* ...or NULL to read text from client */
3483 char *references /* Thread references */
3485 char dest_node[256];
3487 struct CtdlMessage *msg;
3489 StrBuf *FakeEncAuthor = NULL;
3491 msg = malloc(sizeof(struct CtdlMessage));
3492 memset(msg, 0, sizeof(struct CtdlMessage));
3493 msg->cm_magic = CTDLMESSAGE_MAGIC;
3494 msg->cm_anon_type = type;
3495 msg->cm_format_type = format_type;
3497 /* Don't confuse the poor folks if it's not routed mail. */
3498 strcpy(dest_node, "");
3500 if (recipient != NULL) striplt(recipient);
3501 if (recp_cc != NULL) striplt(recp_cc);
3503 /* Path or Return-Path */
3504 if (my_email == NULL) my_email = "";
3506 if (!IsEmptyStr(my_email)) {
3507 msg->cm_fields['P'] = strdup(my_email);
3510 snprintf(buf, sizeof buf, "%s", author->fullname);
3511 msg->cm_fields['P'] = strdup(buf);
3513 convert_spaces_to_underscores(msg->cm_fields['P']);
3515 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3516 msg->cm_fields['T'] = strdup(buf);
3518 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3519 FakeAuthor = NewStrBufPlain (fake_name, -1);
3522 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3524 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3525 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3526 FreeStrBuf(&FakeAuthor);
3528 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3529 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3532 msg->cm_fields['O'] = strdup(CC->room.QRname);
3535 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3536 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3538 if ((recipient != NULL) && (recipient[0] != 0)) {
3539 msg->cm_fields['R'] = strdup(recipient);
3541 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3542 msg->cm_fields['Y'] = strdup(recp_cc);
3544 if (dest_node[0] != 0) {
3545 msg->cm_fields['D'] = strdup(dest_node);
3548 if (!IsEmptyStr(my_email)) {
3549 msg->cm_fields['F'] = strdup(my_email);
3551 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3552 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3555 if (subject != NULL) {
3558 length = strlen(subject);
3564 while ((subject[i] != '\0') &&
3565 (IsAscii = isascii(subject[i]) != 0 ))
3568 msg->cm_fields['U'] = strdup(subject);
3569 else /* ok, we've got utf8 in the string. */
3571 msg->cm_fields['U'] = rfc2047encode(subject, length);
3577 if (supplied_euid != NULL) {
3578 msg->cm_fields['E'] = strdup(supplied_euid);
3581 if (references != NULL) {
3582 if (!IsEmptyStr(references)) {
3583 msg->cm_fields['W'] = strdup(references);
3587 if (preformatted_text != NULL) {
3588 msg->cm_fields['M'] = preformatted_text;
3591 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3599 * Check to see whether we have permission to post a message in the current
3600 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3601 * returns 0 on success.
3603 int CtdlDoIHavePermissionToPostInThisRoom(
3606 const char* RemoteIdentifier,
3612 if (!(CC->logged_in) &&
3613 (PostPublic == POST_LOGGED_IN)) {
3614 snprintf(errmsgbuf, n, "Not logged in.");
3615 return (ERROR + NOT_LOGGED_IN);
3617 else if (PostPublic == CHECK_EXISTANCE) {
3618 return (0); // We're Evaling whether a recipient exists
3620 else if (!(CC->logged_in)) {
3622 if ((CC->room.QRflags & QR_READONLY)) {
3623 snprintf(errmsgbuf, n, "Not logged in.");
3624 return (ERROR + NOT_LOGGED_IN);
3626 if (CC->room.QRflags2 & QR2_MODERATED) {
3627 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3628 return (ERROR + NOT_LOGGED_IN);
3630 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3635 if (RemoteIdentifier == NULL)
3637 snprintf(errmsgbuf, n, "Need sender to permit access.");
3638 return (ERROR + USERNAME_REQUIRED);
3641 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3642 begin_critical_section(S_NETCONFIGS);
3643 if (!read_spoolcontrol_file(&sc, filename))
3645 end_critical_section(S_NETCONFIGS);
3646 snprintf(errmsgbuf, n,
3647 "This mailing list only accepts posts from subscribers.");
3648 return (ERROR + NO_SUCH_USER);
3650 end_critical_section(S_NETCONFIGS);
3651 found = is_recipient (sc, RemoteIdentifier);
3652 free_spoolcontrol_struct(&sc);
3657 snprintf(errmsgbuf, n,
3658 "This mailing list only accepts posts from subscribers.");
3659 return (ERROR + NO_SUCH_USER);
3666 if ((CC->user.axlevel < AxProbU)
3667 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3668 snprintf(errmsgbuf, n, "Need to be validated to enter "
3669 "(except in %s> to sysop)", MAILROOM);
3670 return (ERROR + HIGHER_ACCESS_REQUIRED);
3673 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3675 if ( (!(ra & UA_POSTALLOWED)) && (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3677 * To be thorough, we ought to check to see if the message they are
3678 * replying to is actually a valid one in this room, but unless this
3679 * actually becomes a problem we'll go with high performance instead.
3681 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3682 return (ERROR + HIGHER_ACCESS_REQUIRED);
3685 else if (!(ra & UA_POSTALLOWED)) {
3686 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3687 return (ERROR + HIGHER_ACCESS_REQUIRED);
3690 strcpy(errmsgbuf, "Ok");
3696 * Check to see if the specified user has Internet mail permission
3697 * (returns nonzero if permission is granted)
3699 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3701 /* Do not allow twits to send Internet mail */
3702 if (who->axlevel <= AxProbU) return(0);
3704 /* Globally enabled? */
3705 if (config.c_restrict == 0) return(1);
3707 /* User flagged ok? */
3708 if (who->flags & US_INTERNET) return(2);
3710 /* Aide level access? */
3711 if (who->axlevel >= AxAideU) return(3);
3713 /* No mail for you! */
3719 * Validate recipients, count delivery types and errors, and handle aliasing
3720 * FIXME check for dupes!!!!!
3722 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3723 * were specified, or the number of addresses found invalid.
3725 * Caller needs to free the result using free_recipients()
3727 struct recptypes *validate_recipients(const char *supplied_recipients,
3728 const char *RemoteIdentifier,
3730 struct recptypes *ret;
3731 char *recipients = NULL;
3732 char this_recp[256];
3733 char this_recp_cooked[256];
3739 struct ctdluser tempUS;
3740 struct ctdlroom tempQR;
3741 struct ctdlroom tempQR2;
3747 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3748 if (ret == NULL) return(NULL);
3750 /* Set all strings to null and numeric values to zero */
3751 memset(ret, 0, sizeof(struct recptypes));
3753 if (supplied_recipients == NULL) {
3754 recipients = strdup("");
3757 recipients = strdup(supplied_recipients);
3760 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3761 * actually need, but it's healthier for the heap than doing lots of tiny
3762 * realloc() calls instead.
3765 ret->errormsg = malloc(strlen(recipients) + 1024);
3766 ret->recp_local = malloc(strlen(recipients) + 1024);
3767 ret->recp_internet = malloc(strlen(recipients) + 1024);
3768 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3769 ret->recp_room = malloc(strlen(recipients) + 1024);
3770 ret->display_recp = malloc(strlen(recipients) + 1024);
3772 ret->errormsg[0] = 0;
3773 ret->recp_local[0] = 0;
3774 ret->recp_internet[0] = 0;
3775 ret->recp_ignet[0] = 0;
3776 ret->recp_room[0] = 0;
3777 ret->display_recp[0] = 0;
3779 ret->recptypes_magic = RECPTYPES_MAGIC;
3781 /* Change all valid separator characters to commas */
3782 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3783 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3784 recipients[i] = ',';
3788 /* Now start extracting recipients... */
3790 while (!IsEmptyStr(recipients)) {
3792 for (i=0; i<=strlen(recipients); ++i) {
3793 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3794 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3795 safestrncpy(this_recp, recipients, i+1);
3797 if (recipients[i] == ',') {
3798 strcpy(recipients, &recipients[i+1]);
3801 strcpy(recipients, "");
3808 if (IsEmptyStr(this_recp))
3810 syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3812 mailtype = alias(this_recp);
3813 mailtype = alias(this_recp);
3814 mailtype = alias(this_recp);
3816 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3817 if (this_recp[j]=='_') {
3818 this_recp_cooked[j] = ' ';
3821 this_recp_cooked[j] = this_recp[j];
3824 this_recp_cooked[j] = '\0';
3829 if (!strcasecmp(this_recp, "sysop")) {
3831 strcpy(this_recp, config.c_aideroom);
3832 if (!IsEmptyStr(ret->recp_room)) {
3833 strcat(ret->recp_room, "|");
3835 strcat(ret->recp_room, this_recp);
3837 else if ( (!strncasecmp(this_recp, "room_", 5))
3838 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3840 /* Save room so we can restore it later */
3844 /* Check permissions to send mail to this room */
3845 err = CtdlDoIHavePermissionToPostInThisRoom(
3850 0 /* 0 = not a reply */
3859 if (!IsEmptyStr(ret->recp_room)) {
3860 strcat(ret->recp_room, "|");
3862 strcat(ret->recp_room, &this_recp_cooked[5]);
3865 /* Restore room in case something needs it */
3869 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3871 strcpy(this_recp, tempUS.fullname);
3872 if (!IsEmptyStr(ret->recp_local)) {
3873 strcat(ret->recp_local, "|");
3875 strcat(ret->recp_local, this_recp);
3877 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3879 strcpy(this_recp, tempUS.fullname);
3880 if (!IsEmptyStr(ret->recp_local)) {
3881 strcat(ret->recp_local, "|");
3883 strcat(ret->recp_local, this_recp);
3891 /* Yes, you're reading this correctly: if the target
3892 * domain points back to the local system or an attached
3893 * Citadel directory, the address is invalid. That's
3894 * because if the address were valid, we would have
3895 * already translated it to a local address by now.
3897 if (IsDirectory(this_recp, 0)) {
3902 ++ret->num_internet;
3903 if (!IsEmptyStr(ret->recp_internet)) {
3904 strcat(ret->recp_internet, "|");
3906 strcat(ret->recp_internet, this_recp);
3911 if (!IsEmptyStr(ret->recp_ignet)) {
3912 strcat(ret->recp_ignet, "|");
3914 strcat(ret->recp_ignet, this_recp);
3922 if (IsEmptyStr(errmsg)) {
3923 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3926 snprintf(append, sizeof append, "%s", errmsg);
3928 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3929 if (!IsEmptyStr(ret->errormsg)) {
3930 strcat(ret->errormsg, "; ");
3932 strcat(ret->errormsg, append);
3936 if (IsEmptyStr(ret->display_recp)) {
3937 strcpy(append, this_recp);
3940 snprintf(append, sizeof append, ", %s", this_recp);
3942 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3943 strcat(ret->display_recp, append);
3948 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3949 ret->num_room + ret->num_error) == 0) {
3950 ret->num_error = (-1);
3951 strcpy(ret->errormsg, "No recipients specified.");
3954 syslog(LOG_DEBUG, "validate_recipients()\n");
3955 syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3956 syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3957 syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3958 syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3959 syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3967 * Destructor for struct recptypes
3969 void free_recipients(struct recptypes *valid) {
3971 if (valid == NULL) {
3975 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3976 syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3980 if (valid->errormsg != NULL) free(valid->errormsg);
3981 if (valid->recp_local != NULL) free(valid->recp_local);
3982 if (valid->recp_internet != NULL) free(valid->recp_internet);
3983 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3984 if (valid->recp_room != NULL) free(valid->recp_room);
3985 if (valid->display_recp != NULL) free(valid->display_recp);
3986 if (valid->bounce_to != NULL) free(valid->bounce_to);
3987 if (valid->envelope_from != NULL) free(valid->envelope_from);
3994 * message entry - mode 0 (normal)
3996 void cmd_ent0(char *entargs)
4002 char supplied_euid[128];
4004 int format_type = 0;
4005 char newusername[256];
4006 char newuseremail[256];
4007 struct CtdlMessage *msg;
4011 struct recptypes *valid = NULL;
4012 struct recptypes *valid_to = NULL;
4013 struct recptypes *valid_cc = NULL;
4014 struct recptypes *valid_bcc = NULL;
4016 int subject_required = 0;
4021 int newuseremail_ok = 0;
4022 char references[SIZ];
4027 post = extract_int(entargs, 0);
4028 extract_token(recp, entargs, 1, '|', sizeof recp);
4029 anon_flag = extract_int(entargs, 2);
4030 format_type = extract_int(entargs, 3);
4031 extract_token(subject, entargs, 4, '|', sizeof subject);
4032 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4033 do_confirm = extract_int(entargs, 6);
4034 extract_token(cc, entargs, 7, '|', sizeof cc);
4035 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4036 switch(CC->room.QRdefaultview) {
4039 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4042 supplied_euid[0] = 0;
4045 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4046 extract_token(references, entargs, 11, '|', sizeof references);
4047 for (ptr=references; *ptr != 0; ++ptr) {
4048 if (*ptr == '!') *ptr = '|';
4051 /* first check to make sure the request is valid. */
4053 err = CtdlDoIHavePermissionToPostInThisRoom(
4058 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4062 cprintf("%d %s\n", err, errmsg);
4066 /* Check some other permission type things. */
4068 if (IsEmptyStr(newusername)) {
4069 strcpy(newusername, CC->user.fullname);
4071 if ( (CC->user.axlevel < AxAideU)
4072 && (strcasecmp(newusername, CC->user.fullname))
4073 && (strcasecmp(newusername, CC->cs_inet_fn))
4075 cprintf("%d You don't have permission to author messages as '%s'.\n",
4076 ERROR + HIGHER_ACCESS_REQUIRED,
4083 if (IsEmptyStr(newuseremail)) {
4084 newuseremail_ok = 1;
4087 if (!IsEmptyStr(newuseremail)) {
4088 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4089 newuseremail_ok = 1;
4091 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4092 j = num_tokens(CC->cs_inet_other_emails, '|');
4093 for (i=0; i<j; ++i) {
4094 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4095 if (!strcasecmp(newuseremail, buf)) {
4096 newuseremail_ok = 1;
4102 if (!newuseremail_ok) {
4103 cprintf("%d You don't have permission to author messages as '%s'.\n",
4104 ERROR + HIGHER_ACCESS_REQUIRED,
4110 CC->cs_flags |= CS_POSTING;
4112 /* In mailbox rooms we have to behave a little differently --
4113 * make sure the user has specified at least one recipient. Then
4114 * validate the recipient(s). We do this for the Mail> room, as
4115 * well as any room which has the "Mailbox" view set - unless it
4116 * is the DRAFTS room which does not require recipients
4119 if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4120 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4121 ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4122 if (CC->user.axlevel < AxProbU) {
4123 strcpy(recp, "sysop");
4128 valid_to = validate_recipients(recp, NULL, 0);
4129 if (valid_to->num_error > 0) {
4130 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4131 free_recipients(valid_to);
4135 valid_cc = validate_recipients(cc, NULL, 0);
4136 if (valid_cc->num_error > 0) {
4137 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4138 free_recipients(valid_to);
4139 free_recipients(valid_cc);
4143 valid_bcc = validate_recipients(bcc, NULL, 0);
4144 if (valid_bcc->num_error > 0) {
4145 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4146 free_recipients(valid_to);
4147 free_recipients(valid_cc);
4148 free_recipients(valid_bcc);
4152 /* Recipient required, but none were specified */
4153 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4154 free_recipients(valid_to);
4155 free_recipients(valid_cc);
4156 free_recipients(valid_bcc);
4157 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4161 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4162 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4163 cprintf("%d You do not have permission "
4164 "to send Internet mail.\n",
4165 ERROR + HIGHER_ACCESS_REQUIRED);
4166 free_recipients(valid_to);
4167 free_recipients(valid_cc);
4168 free_recipients(valid_bcc);
4173 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)
4174 && (CC->user.axlevel < AxNetU) ) {
4175 cprintf("%d Higher access required for network mail.\n",
4176 ERROR + HIGHER_ACCESS_REQUIRED);
4177 free_recipients(valid_to);
4178 free_recipients(valid_cc);
4179 free_recipients(valid_bcc);
4183 if ((RESTRICT_INTERNET == 1)
4184 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4185 && ((CC->user.flags & US_INTERNET) == 0)
4186 && (!CC->internal_pgm)) {
4187 cprintf("%d You don't have access to Internet mail.\n",
4188 ERROR + HIGHER_ACCESS_REQUIRED);
4189 free_recipients(valid_to);
4190 free_recipients(valid_cc);
4191 free_recipients(valid_bcc);
4197 /* Is this a room which has anonymous-only or anonymous-option? */
4198 anonymous = MES_NORMAL;
4199 if (CC->room.QRflags & QR_ANONONLY) {
4200 anonymous = MES_ANONONLY;
4202 if (CC->room.QRflags & QR_ANONOPT) {
4203 if (anon_flag == 1) { /* only if the user requested it */
4204 anonymous = MES_ANONOPT;
4208 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4212 /* Recommend to the client that the use of a message subject is
4213 * strongly recommended in this room, if either the SUBJECTREQ flag
4214 * is set, or if there is one or more Internet email recipients.
4216 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4217 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4218 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4219 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4221 /* If we're only checking the validity of the request, return
4222 * success without creating the message.
4225 cprintf("%d %s|%d\n", CIT_OK,
4226 ((valid_to != NULL) ? valid_to->display_recp : ""),
4228 free_recipients(valid_to);
4229 free_recipients(valid_cc);
4230 free_recipients(valid_bcc);
4234 /* We don't need these anymore because we'll do it differently below */
4235 free_recipients(valid_to);
4236 free_recipients(valid_cc);
4237 free_recipients(valid_bcc);
4239 /* Read in the message from the client. */
4241 cprintf("%d send message\n", START_CHAT_MODE);
4243 cprintf("%d send message\n", SEND_LISTING);
4246 msg = CtdlMakeMessage(&CC->user, recp, cc,
4247 CC->room.QRname, anonymous, format_type,
4248 newusername, newuseremail, subject,
4249 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4252 /* Put together one big recipients struct containing to/cc/bcc all in
4253 * one. This is for the envelope.
4255 char *all_recps = malloc(SIZ * 3);
4256 strcpy(all_recps, recp);
4257 if (!IsEmptyStr(cc)) {
4258 if (!IsEmptyStr(all_recps)) {
4259 strcat(all_recps, ",");
4261 strcat(all_recps, cc);
4263 if (!IsEmptyStr(bcc)) {
4264 if (!IsEmptyStr(all_recps)) {
4265 strcat(all_recps, ",");
4267 strcat(all_recps, bcc);
4269 if (!IsEmptyStr(all_recps)) {
4270 valid = validate_recipients(all_recps, NULL, 0);
4278 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4281 cprintf("%ld\n", msgnum);
4283 cprintf("Message accepted.\n");
4286 cprintf("Internal error.\n");
4288 if (msg->cm_fields['E'] != NULL) {
4289 cprintf("%s\n", msg->cm_fields['E']);
4296 CtdlFreeMessage(msg);
4298 if (valid != NULL) {
4299 free_recipients(valid);
4307 * API function to delete messages which match a set of criteria
4308 * (returns the actual number of messages deleted)
4310 int CtdlDeleteMessages(char *room_name, /* which room */
4311 long *dmsgnums, /* array of msg numbers to be deleted */
4312 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4313 char *content_type /* or "" for any. regular expressions expected. */
4316 struct ctdlroom qrbuf;
4317 struct cdbdata *cdbfr;
4318 long *msglist = NULL;
4319 long *dellist = NULL;
4322 int num_deleted = 0;
4324 struct MetaData smi;
4327 int need_to_free_re = 0;
4329 if (content_type) if (!IsEmptyStr(content_type)) {
4330 regcomp(&re, content_type, 0);
4331 need_to_free_re = 1;
4333 syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4334 room_name, num_dmsgnums, content_type);
4336 /* get room record, obtaining a lock... */
4337 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4338 syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4340 if (need_to_free_re) regfree(&re);
4341 return (0); /* room not found */
4343 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4345 if (cdbfr != NULL) {
4346 dellist = malloc(cdbfr->len);
4347 msglist = (long *) cdbfr->ptr;
4348 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4349 num_msgs = cdbfr->len / sizeof(long);
4353 for (i = 0; i < num_msgs; ++i) {
4356 /* Set/clear a bit for each criterion */
4358 /* 0 messages in the list or a null list means that we are
4359 * interested in deleting any messages which meet the other criteria.
4361 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4362 delete_this |= 0x01;
4365 for (j=0; j<num_dmsgnums; ++j) {
4366 if (msglist[i] == dmsgnums[j]) {
4367 delete_this |= 0x01;
4372 if (IsEmptyStr(content_type)) {
4373 delete_this |= 0x02;
4375 GetMetaData(&smi, msglist[i]);
4376 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4377 delete_this |= 0x02;
4381 /* Delete message only if all bits are set */
4382 if (delete_this == 0x03) {
4383 dellist[num_deleted++] = msglist[i];
4388 num_msgs = sort_msglist(msglist, num_msgs);
4389 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4390 msglist, (int)(num_msgs * sizeof(long)));
4393 qrbuf.QRhighest = msglist[num_msgs - 1];
4395 qrbuf.QRhighest = 0;
4397 CtdlPutRoomLock(&qrbuf);
4399 /* Go through the messages we pulled out of the index, and decrement
4400 * their reference counts by 1. If this is the only room the message
4401 * was in, the reference count will reach zero and the message will
4402 * automatically be deleted from the database. We do this in a
4403 * separate pass because there might be plug-in hooks getting called,
4404 * and we don't want that happening during an S_ROOMS critical
4407 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4408 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4409 AdjRefCount(dellist[i], -1);
4412 /* Now free the memory we used, and go away. */
4413 if (msglist != NULL) free(msglist);
4414 if (dellist != NULL) free(dellist);
4415 syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4416 if (need_to_free_re) regfree(&re);
4417 return (num_deleted);
4423 * Check whether the current user has permission to delete messages from
4424 * the current room (returns 1 for yes, 0 for no)
4426 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4428 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4429 if (ra & UA_DELETEALLOWED) return(1);
4437 * Delete message from current room
4439 void cmd_dele(char *args)
4448 extract_token(msgset, args, 0, '|', sizeof msgset);
4449 num_msgs = num_tokens(msgset, ',');
4451 cprintf("%d Nothing to do.\n", CIT_OK);
4455 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4456 cprintf("%d Higher access required.\n",
4457 ERROR + HIGHER_ACCESS_REQUIRED);
4462 * Build our message set to be moved/copied
4464 msgs = malloc(num_msgs * sizeof(long));
4465 for (i=0; i<num_msgs; ++i) {
4466 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4467 msgs[i] = atol(msgtok);
4470 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4474 cprintf("%d %d message%s deleted.\n", CIT_OK,
4475 num_deleted, ((num_deleted != 1) ? "s" : ""));
4477 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4485 * move or copy a message to another room
4487 void cmd_move(char *args)
4494 char targ[ROOMNAMELEN];
4495 struct ctdlroom qtemp;
4502 extract_token(msgset, args, 0, '|', sizeof msgset);
4503 num_msgs = num_tokens(msgset, ',');
4505 cprintf("%d Nothing to do.\n", CIT_OK);
4509 extract_token(targ, args, 1, '|', sizeof targ);
4510 convert_room_name_macros(targ, sizeof targ);
4511 targ[ROOMNAMELEN - 1] = 0;
4512 is_copy = extract_int(args, 2);
4514 if (CtdlGetRoom(&qtemp, targ) != 0) {
4515 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4519 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4520 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4524 CtdlGetUser(&CC->user, CC->curr_user);
4525 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4527 /* Check for permission to perform this operation.
4528 * Remember: "CC->room" is source, "qtemp" is target.
4532 /* Aides can move/copy */
4533 if (CC->user.axlevel >= AxAideU) permit = 1;
4535 /* Room aides can move/copy */
4536 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4538 /* Permit move/copy from personal rooms */
4539 if ((CC->room.QRflags & QR_MAILBOX)
4540 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4542 /* Permit only copy from public to personal room */
4544 && (!(CC->room.QRflags & QR_MAILBOX))
4545 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4547 /* Permit message removal from collaborative delete rooms */
4548 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4550 /* Users allowed to post into the target room may move into it too. */
4551 if ((CC->room.QRflags & QR_MAILBOX) &&
4552 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4554 /* User must have access to target room */
4555 if (!(ra & UA_KNOWN)) permit = 0;
4558 cprintf("%d Higher access required.\n",
4559 ERROR + HIGHER_ACCESS_REQUIRED);
4564 * Build our message set to be moved/copied
4566 msgs = malloc(num_msgs * sizeof(long));
4567 for (i=0; i<num_msgs; ++i) {
4568 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4569 msgs[i] = atol(msgtok);
4575 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4577 cprintf("%d Cannot store message(s) in %s: error %d\n",
4583 /* Now delete the message from the source room,
4584 * if this is a 'move' rather than a 'copy' operation.
4587 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4591 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4597 * GetMetaData() - Get the supplementary record for a message
4599 void GetMetaData(struct MetaData *smibuf, long msgnum)
4602 struct cdbdata *cdbsmi;
4605 memset(smibuf, 0, sizeof(struct MetaData));
4606 smibuf->meta_msgnum = msgnum;
4607 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4609 /* Use the negative of the message number for its supp record index */
4610 TheIndex = (0L - msgnum);
4612 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4613 if (cdbsmi == NULL) {
4614 return; /* record not found; go with defaults */
4616 memcpy(smibuf, cdbsmi->ptr,
4617 ((cdbsmi->len > sizeof(struct MetaData)) ?
4618 sizeof(struct MetaData) : cdbsmi->len));
4625 * PutMetaData() - (re)write supplementary record for a message
4627 void PutMetaData(struct MetaData *smibuf)
4631 /* Use the negative of the message number for the metadata db index */
4632 TheIndex = (0L - smibuf->meta_msgnum);
4634 cdb_store(CDB_MSGMAIN,
4635 &TheIndex, (int)sizeof(long),
4636 smibuf, (int)sizeof(struct MetaData));
4641 * AdjRefCount - submit an adjustment to the reference count for a message.
4642 * (These are just queued -- we actually process them later.)
4644 void AdjRefCount(long msgnum, int incr)
4646 struct arcq new_arcq;
4649 syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4653 begin_critical_section(S_SUPPMSGMAIN);
4654 if (arcfp == NULL) {
4655 arcfp = fopen(file_arcq, "ab+");
4657 end_critical_section(S_SUPPMSGMAIN);
4659 /* msgnum < 0 means that we're trying to close the file */
4661 syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
4662 begin_critical_section(S_SUPPMSGMAIN);
4663 if (arcfp != NULL) {
4667 end_critical_section(S_SUPPMSGMAIN);
4672 * If we can't open the queue, perform the operation synchronously.
4674 if (arcfp == NULL) {
4675 TDAP_AdjRefCount(msgnum, incr);
4679 new_arcq.arcq_msgnum = msgnum;
4680 new_arcq.arcq_delta = incr;
4681 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4689 * TDAP_ProcessAdjRefCountQueue()
4691 * Process the queue of message count adjustments that was created by calls
4692 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4693 * for each one. This should be an "off hours" operation.
4695 int TDAP_ProcessAdjRefCountQueue(void)
4697 char file_arcq_temp[PATH_MAX];
4700 struct arcq arcq_rec;
4701 int num_records_processed = 0;
4703 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4705 begin_critical_section(S_SUPPMSGMAIN);
4706 if (arcfp != NULL) {
4711 r = link(file_arcq, file_arcq_temp);
4713 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4714 end_critical_section(S_SUPPMSGMAIN);
4715 return(num_records_processed);
4719 end_critical_section(S_SUPPMSGMAIN);
4721 fp = fopen(file_arcq_temp, "rb");
4723 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4724 return(num_records_processed);
4727 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4728 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4729 ++num_records_processed;
4733 r = unlink(file_arcq_temp);
4735 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4738 return(num_records_processed);
4744 * TDAP_AdjRefCount - adjust the reference count for a message.
4745 * This one does it "for real" because it's called by
4746 * the autopurger function that processes the queue
4747 * created by AdjRefCount(). If a message's reference
4748 * count becomes zero, we also delete the message from
4749 * disk and de-index it.
4751 void TDAP_AdjRefCount(long msgnum, int incr)
4754 struct MetaData smi;
4757 /* This is a *tight* critical section; please keep it that way, as
4758 * it may get called while nested in other critical sections.
4759 * Complicating this any further will surely cause deadlock!
4761 begin_critical_section(S_SUPPMSGMAIN);
4762 GetMetaData(&smi, msgnum);
4763 smi.meta_refcount += incr;
4765 end_critical_section(S_SUPPMSGMAIN);
4766 syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4767 msgnum, incr, smi.meta_refcount
4770 /* If the reference count is now zero, delete the message
4771 * (and its supplementary record as well).
4773 if (smi.meta_refcount == 0) {
4774 syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
4776 /* Call delete hooks with NULL room to show it has gone altogether */
4777 PerformDeleteHooks(NULL, msgnum);
4779 /* Remove from message base */
4781 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4782 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4784 /* Remove metadata record */
4785 delnum = (0L - msgnum);
4786 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4792 * Write a generic object to this room
4794 * Note: this could be much more efficient. Right now we use two temporary
4795 * files, and still pull the message into memory as with all others.
4797 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4798 char *content_type, /* MIME type of this object */
4799 char *raw_message, /* Data to be written */
4800 off_t raw_length, /* Size of raw_message */
4801 struct ctdluser *is_mailbox, /* Mailbox room? */
4802 int is_binary, /* Is encoding necessary? */
4803 int is_unique, /* Del others of this type? */
4804 unsigned int flags /* Internal save flags */
4808 struct ctdlroom qrbuf;
4809 char roomname[ROOMNAMELEN];
4810 struct CtdlMessage *msg;
4811 char *encoded_message = NULL;
4813 if (is_mailbox != NULL) {
4814 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4817 safestrncpy(roomname, req_room, sizeof(roomname));
4820 syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
4823 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4826 encoded_message = malloc((size_t)(raw_length + 4096));
4829 sprintf(encoded_message, "Content-type: %s\n", content_type);
4832 sprintf(&encoded_message[strlen(encoded_message)],
4833 "Content-transfer-encoding: base64\n\n"
4837 sprintf(&encoded_message[strlen(encoded_message)],
4838 "Content-transfer-encoding: 7bit\n\n"
4844 &encoded_message[strlen(encoded_message)],
4852 &encoded_message[strlen(encoded_message)],
4858 syslog(LOG_DEBUG, "Allocating\n");
4859 msg = malloc(sizeof(struct CtdlMessage));
4860 memset(msg, 0, sizeof(struct CtdlMessage));
4861 msg->cm_magic = CTDLMESSAGE_MAGIC;
4862 msg->cm_anon_type = MES_NORMAL;
4863 msg->cm_format_type = 4;
4864 msg->cm_fields['A'] = strdup(CC->user.fullname);
4865 msg->cm_fields['O'] = strdup(req_room);
4866 msg->cm_fields['N'] = strdup(config.c_nodename);
4867 msg->cm_fields['H'] = strdup(config.c_humannode);
4868 msg->cm_flags = flags;
4870 msg->cm_fields['M'] = encoded_message;
4872 /* Create the requested room if we have to. */
4873 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4874 CtdlCreateRoom(roomname,
4875 ( (is_mailbox != NULL) ? 5 : 3 ),
4876 "", 0, 1, 0, VIEW_BBS);
4878 /* If the caller specified this object as unique, delete all
4879 * other objects of this type that are currently in the room.
4882 syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
4883 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4886 /* Now write the data */
4887 CtdlSubmitMsg(msg, NULL, roomname, 0);
4888 CtdlFreeMessage(msg);
4896 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4897 config_msgnum = msgnum;
4901 char *CtdlGetSysConfig(char *sysconfname) {
4902 char hold_rm[ROOMNAMELEN];
4905 struct CtdlMessage *msg;
4908 strcpy(hold_rm, CC->room.QRname);
4909 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4910 CtdlGetRoom(&CC->room, hold_rm);
4915 /* We want the last (and probably only) config in this room */
4916 begin_critical_section(S_CONFIG);
4917 config_msgnum = (-1L);
4918 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4919 CtdlGetSysConfigBackend, NULL);
4920 msgnum = config_msgnum;
4921 end_critical_section(S_CONFIG);
4927 msg = CtdlFetchMessage(msgnum, 1);
4929 conf = strdup(msg->cm_fields['M']);
4930 CtdlFreeMessage(msg);
4937 CtdlGetRoom(&CC->room, hold_rm);
4939 if (conf != NULL) do {
4940 extract_token(buf, conf, 0, '\n', sizeof buf);
4941 strcpy(conf, &conf[strlen(buf)+1]);
4942 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4948 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4949 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4954 * Determine whether a given Internet address belongs to the current user
4956 int CtdlIsMe(char *addr, int addr_buf_len)
4958 struct recptypes *recp;
4961 recp = validate_recipients(addr, NULL, 0);
4962 if (recp == NULL) return(0);
4964 if (recp->num_local == 0) {
4965 free_recipients(recp);
4969 for (i=0; i<recp->num_local; ++i) {
4970 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4971 if (!strcasecmp(addr, CC->user.fullname)) {
4972 free_recipients(recp);
4977 free_recipients(recp);
4983 * Citadel protocol command to do the same
4985 void cmd_isme(char *argbuf) {
4988 if (CtdlAccessCheck(ac_logged_in)) return;
4989 extract_token(addr, argbuf, 0, '|', sizeof addr);
4991 if (CtdlIsMe(addr, sizeof addr)) {
4992 cprintf("%d %s\n", CIT_OK, addr);
4995 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5001 /*****************************************************************************/
5002 /* MODULE INITIALIZATION STUFF */
5003 /*****************************************************************************/
5005 CTDL_MODULE_INIT(msgbase)
5008 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5009 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5010 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5011 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5012 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5013 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5014 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5015 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5016 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5017 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5018 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5019 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5022 /* return our Subversion id for the Log */