4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
32 #include <sys/types.h>
34 #include <libcitadel.h>
37 #include "serv_extensions.h"
41 #include "sysdep_decls.h"
42 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "euidindex.h"
51 #include "journaling.h"
52 #include "citadel_dirs.h"
53 #include "clientsocket.h"
54 #include "serv_network.h"
57 #include "ctdl_module.h"
60 struct addresses_to_be_filed *atbf = NULL;
62 /* This temp file holds the queue of operations for AdjRefCount() */
63 static FILE *arcfp = NULL;
66 * This really belongs in serv_network.c, but I don't know how to export
67 * symbols between modules.
69 struct FilterList *filterlist = NULL;
73 * These are the four-character field headers we use when outputting
74 * messages in Citadel format (as opposed to RFC822 format).
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
81 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
113 * This function is self explanatory.
114 * (What can I say, I'm in a weird mood today...)
116 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
120 for (i = 0; i < strlen(name); ++i) {
121 if (name[i] == '@') {
122 while (isspace(name[i - 1]) && i > 0) {
123 strcpy(&name[i - 1], &name[i]);
126 while (isspace(name[i + 1])) {
127 strcpy(&name[i + 1], &name[i + 2]);
135 * Aliasing for network mail.
136 * (Error messages have been commented out, because this is a server.)
138 int alias(char *name)
139 { /* process alias and routing info for mail */
142 char aaa[SIZ], bbb[SIZ];
143 char *ignetcfg = NULL;
144 char *ignetmap = NULL;
150 char original_name[256];
151 safestrncpy(original_name, name, sizeof original_name);
154 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
155 stripallbut(name, '<', '>');
157 fp = fopen(file_mail_aliases, "r");
159 fp = fopen("/dev/null", "r");
166 while (fgets(aaa, sizeof aaa, fp) != NULL) {
167 while (isspace(name[0]))
168 strcpy(name, &name[1]);
169 aaa[strlen(aaa) - 1] = 0;
171 for (a = 0; a < strlen(aaa); ++a) {
173 strcpy(bbb, &aaa[a + 1]);
177 if (!strcasecmp(name, aaa))
182 /* Hit the Global Address Book */
183 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
187 if (strcasecmp(original_name, name)) {
188 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
191 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
192 for (a=0; a<strlen(name); ++a) {
193 if (name[a] == '@') {
194 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
196 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
201 /* determine local or remote type, see citadel.h */
202 at = haschar(name, '@');
203 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
204 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
205 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
207 /* figure out the delivery mode */
208 extract_token(node, name, 1, '@', sizeof node);
210 /* If there are one or more dots in the nodename, we assume that it
211 * is an FQDN and will attempt SMTP delivery to the Internet.
213 if (haschar(node, '.') > 0) {
214 return(MES_INTERNET);
217 /* Otherwise we look in the IGnet maps for a valid Citadel node.
218 * Try directly-connected nodes first...
220 ignetcfg = CtdlGetSysConfig(IGNETCFG);
221 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
222 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
223 extract_token(testnode, buf, 0, '|', sizeof testnode);
224 if (!strcasecmp(node, testnode)) {
232 * Then try nodes that are two or more hops away.
234 ignetmap = CtdlGetSysConfig(IGNETMAP);
235 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
236 extract_token(buf, ignetmap, i, '\n', sizeof buf);
237 extract_token(testnode, buf, 0, '|', sizeof testnode);
238 if (!strcasecmp(node, testnode)) {
245 /* If we get to this point it's an invalid node name */
251 * Back end for the MSGS command: output message number only.
253 void simple_listing(long msgnum, void *userdata)
255 cprintf("%ld\n", msgnum);
261 * Back end for the MSGS command: output header summary.
263 void headers_listing(long msgnum, void *userdata)
265 struct CtdlMessage *msg;
267 msg = CtdlFetchMessage(msgnum, 0);
269 cprintf("%ld|0|||||\n", msgnum);
273 cprintf("%ld|%s|%s|%s|%s|%s|\n",
275 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
276 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
277 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
278 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
279 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
281 CtdlFreeMessage(msg);
286 /* Determine if a given message matches the fields in a message template.
287 * Return 0 for a successful match.
289 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
292 /* If there aren't any fields in the template, all messages will
295 if (template == NULL) return(0);
297 /* Null messages are bogus. */
298 if (msg == NULL) return(1);
300 for (i='A'; i<='Z'; ++i) {
301 if (template->cm_fields[i] != NULL) {
302 if (msg->cm_fields[i] == NULL) {
305 if (strcasecmp(msg->cm_fields[i],
306 template->cm_fields[i])) return 1;
310 /* All compares succeeded: we have a match! */
317 * Retrieve the "seen" message list for the current room.
319 void CtdlGetSeen(char *buf, int which_set) {
322 /* Learn about the user and room in question */
323 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
325 if (which_set == ctdlsetseen_seen)
326 safestrncpy(buf, vbuf.v_seen, SIZ);
327 if (which_set == ctdlsetseen_answered)
328 safestrncpy(buf, vbuf.v_answered, SIZ);
334 * Manipulate the "seen msgs" string (or other message set strings)
336 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
337 int target_setting, int which_set,
338 struct ctdluser *which_user, struct ctdlroom *which_room) {
339 struct cdbdata *cdbfr;
349 char *is_set; /* actually an array of booleans */
353 char setstr[SIZ], lostr[SIZ], histr[SIZ];
355 /* Don't bother doing *anything* if we were passed a list of zero messages */
356 if (num_target_msgnums < 1) {
360 /* If no room was specified, we go with the current room. */
362 which_room = &CC->room;
365 /* If no user was specified, we go with the current user. */
367 which_user = &CC->user;
370 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
371 num_target_msgnums, target_msgnums[0],
372 (target_setting ? "SET" : "CLEAR"),
376 /* Learn about the user and room in question */
377 CtdlGetRelationship(&vbuf, which_user, which_room);
379 /* Load the message list */
380 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
382 msglist = (long *) cdbfr->ptr;
383 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
384 num_msgs = cdbfr->len / sizeof(long);
387 return; /* No messages at all? No further action. */
390 is_set = malloc(num_msgs * sizeof(char));
391 memset(is_set, 0, (num_msgs * sizeof(char)) );
393 /* Decide which message set we're manipulating */
395 case ctdlsetseen_seen:
396 safestrncpy(vset, vbuf.v_seen, sizeof vset);
398 case ctdlsetseen_answered:
399 safestrncpy(vset, vbuf.v_answered, sizeof vset);
404 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
405 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
406 for (i=0; i<num_msgs; ++i) {
407 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
409 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
410 for (k=0; k<num_target_msgnums; ++k) {
411 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
415 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", vset);
417 /* Translate the existing sequence set into an array of booleans */
418 num_sets = num_tokens(vset, ',');
419 for (s=0; s<num_sets; ++s) {
420 extract_token(setstr, vset, s, ',', sizeof setstr);
422 extract_token(lostr, setstr, 0, ':', sizeof lostr);
423 if (num_tokens(setstr, ':') >= 2) {
424 extract_token(histr, setstr, 1, ':', sizeof histr);
427 strcpy(histr, lostr);
430 if (!strcmp(histr, "*")) {
437 for (i = 0; i < num_msgs; ++i) {
438 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
445 /* Now translate the array of booleans back into a sequence set */
451 for (i=0; i<num_msgs; ++i) {
455 for (k=0; k<num_target_msgnums; ++k) {
456 if (msglist[i] == target_msgnums[k]) {
457 is_seen = target_setting;
461 w = 0; /* set to 1 if we write something to the string */
463 if ((was_seen == 0) && (is_seen == 1)) {
466 else if ((was_seen == 1) && (is_seen == 0)) {
470 if (!IsEmptyStr(vset)) {
474 sprintf(&vset[strlen(vset)], "%ld", hi);
477 sprintf(&vset[strlen(vset)], "%ld:%ld", lo, hi);
480 else if ((is_seen) && (i == num_msgs - 1)) {
482 if (!IsEmptyStr(vset)) {
485 if ((i==0) || (was_seen == 0)) {
486 sprintf(&vset[strlen(vset)], "%ld", msglist[i]);
489 sprintf(&vset[strlen(vset)], "%ld:%ld", lo, msglist[i]);
493 /* If the string is getting too long, truncate it at the beginning; repeat up to 9 times */
494 if (w) for (j=0; j<9; ++j) {
495 if ((strlen(vset) + 20) > sizeof vset) {
496 remove_token(vset, 0, ',');
497 if (which_set == ctdlsetseen_seen) {
499 sprintf(temp, "1:%ld,", atol(vset)-1L);
509 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", vset);
511 /* Decide which message set we're manipulating */
513 case ctdlsetseen_seen:
514 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
516 case ctdlsetseen_answered:
517 safestrncpy(vbuf.v_answered, vset, sizeof vbuf.v_answered);
523 CtdlSetRelationship(&vbuf, which_user, which_room);
528 * API function to perform an operation for each qualifying message in the
529 * current room. (Returns the number of messages processed.)
531 int CtdlForEachMessage(int mode, long ref, char *search_string,
533 struct CtdlMessage *compare,
534 void (*CallBack) (long, void *),
540 struct cdbdata *cdbfr;
541 long *msglist = NULL;
543 int num_processed = 0;
546 struct CtdlMessage *msg = NULL;
549 int printed_lastold = 0;
550 int num_search_msgs = 0;
551 long *search_msgs = NULL;
553 int need_to_free_re = 0;
556 if ((content_type) && (!IsEmptyStr(content_type))) {
557 regcomp(&re, content_type, 0);
561 /* Learn about the user and room in question */
562 getuser(&CC->user, CC->curr_user);
563 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
565 /* Load the message list */
566 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
568 msglist = (long *) cdbfr->ptr;
569 num_msgs = cdbfr->len / sizeof(long);
571 if (need_to_free_re) regfree(&re);
572 return 0; /* No messages at all? No further action. */
577 * Now begin the traversal.
579 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
581 /* If the caller is looking for a specific MIME type, filter
582 * out all messages which are not of the type requested.
584 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
586 /* This call to GetMetaData() sits inside this loop
587 * so that we only do the extra database read per msg
588 * if we need to. Doing the extra read all the time
589 * really kills the server. If we ever need to use
590 * metadata for another search criterion, we need to
591 * move the read somewhere else -- but still be smart
592 * enough to only do the read if the caller has
593 * specified something that will need it.
595 GetMetaData(&smi, msglist[a]);
597 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
598 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
604 num_msgs = sort_msglist(msglist, num_msgs);
606 /* If a template was supplied, filter out the messages which
607 * don't match. (This could induce some delays!)
610 if (compare != NULL) {
611 for (a = 0; a < num_msgs; ++a) {
612 msg = CtdlFetchMessage(msglist[a], 1);
614 if (CtdlMsgCmp(msg, compare)) {
617 CtdlFreeMessage(msg);
623 /* If a search string was specified, get a message list from
624 * the full text index and remove messages which aren't on both
628 * Since the lists are sorted and strictly ascending, and the
629 * output list is guaranteed to be shorter than or equal to the
630 * input list, we overwrite the bottom of the input list. This
631 * eliminates the need to memmove big chunks of the list over and
634 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
636 /* Call search module via hook mechanism.
637 * NULL means use any search function available.
638 * otherwise replace with a char * to name of search routine
640 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
642 if (num_search_msgs > 0) {
646 orig_num_msgs = num_msgs;
648 for (i=0; i<orig_num_msgs; ++i) {
649 for (j=0; j<num_search_msgs; ++j) {
650 if (msglist[i] == search_msgs[j]) {
651 msglist[num_msgs++] = msglist[i];
657 num_msgs = 0; /* No messages qualify */
659 if (search_msgs != NULL) free(search_msgs);
661 /* Now that we've purged messages which don't contain the search
662 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
669 * Now iterate through the message list, according to the
670 * criteria supplied by the caller.
673 for (a = 0; a < num_msgs; ++a) {
674 thismsg = msglist[a];
675 if (mode == MSGS_ALL) {
679 is_seen = is_msg_in_sequence_set(
680 vbuf.v_seen, thismsg);
681 if (is_seen) lastold = thismsg;
687 || ((mode == MSGS_OLD) && (is_seen))
688 || ((mode == MSGS_NEW) && (!is_seen))
689 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
690 || ((mode == MSGS_FIRST) && (a < ref))
691 || ((mode == MSGS_GT) && (thismsg > ref))
692 || ((mode == MSGS_EQ) && (thismsg == ref))
695 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
697 CallBack(lastold, userdata);
701 if (CallBack) CallBack(thismsg, userdata);
705 cdb_free(cdbfr); /* Clean up */
706 if (need_to_free_re) regfree(&re);
707 return num_processed;
713 * cmd_msgs() - get list of message #'s in this room
714 * implements the MSGS server command using CtdlForEachMessage()
716 void cmd_msgs(char *cmdbuf)
725 int with_template = 0;
726 struct CtdlMessage *template = NULL;
727 int with_headers = 0;
728 char search_string[1024];
730 extract_token(which, cmdbuf, 0, '|', sizeof which);
731 cm_ref = extract_int(cmdbuf, 1);
732 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
733 with_template = extract_int(cmdbuf, 2);
734 with_headers = extract_int(cmdbuf, 3);
737 if (!strncasecmp(which, "OLD", 3))
739 else if (!strncasecmp(which, "NEW", 3))
741 else if (!strncasecmp(which, "FIRST", 5))
743 else if (!strncasecmp(which, "LAST", 4))
745 else if (!strncasecmp(which, "GT", 2))
747 else if (!strncasecmp(which, "SEARCH", 6))
752 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
753 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
757 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
758 cprintf("%d Full text index is not enabled on this server.\n",
759 ERROR + CMD_NOT_SUPPORTED);
765 cprintf("%d Send template then receive message list\n",
767 template = (struct CtdlMessage *)
768 malloc(sizeof(struct CtdlMessage));
769 memset(template, 0, sizeof(struct CtdlMessage));
770 template->cm_magic = CTDLMESSAGE_MAGIC;
771 template->cm_anon_type = MES_NORMAL;
773 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
774 extract_token(tfield, buf, 0, '|', sizeof tfield);
775 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
776 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
777 if (!strcasecmp(tfield, msgkeys[i])) {
778 template->cm_fields[i] =
786 cprintf("%d \n", LISTING_FOLLOWS);
789 CtdlForEachMessage(mode,
790 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
791 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
794 (with_headers ? headers_listing : simple_listing),
797 if (template != NULL) CtdlFreeMessage(template);
805 * help_subst() - support routine for help file viewer
807 void help_subst(char *strbuf, char *source, char *dest)
812 while (p = pattern2(strbuf, source), (p >= 0)) {
813 strcpy(workbuf, &strbuf[p + strlen(source)]);
814 strcpy(&strbuf[p], dest);
815 strcat(strbuf, workbuf);
820 void do_help_subst(char *buffer)
824 help_subst(buffer, "^nodename", config.c_nodename);
825 help_subst(buffer, "^humannode", config.c_humannode);
826 help_subst(buffer, "^fqdn", config.c_fqdn);
827 help_subst(buffer, "^username", CC->user.fullname);
828 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
829 help_subst(buffer, "^usernum", buf2);
830 help_subst(buffer, "^sysadm", config.c_sysadm);
831 help_subst(buffer, "^variantname", CITADEL);
832 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
833 help_subst(buffer, "^maxsessions", buf2);
834 help_subst(buffer, "^bbsdir", ctdl_message_dir);
840 * memfmout() - Citadel text formatter and paginator.
841 * Although the original purpose of this routine was to format
842 * text to the reader's screen width, all we're really using it
843 * for here is to format text out to 80 columns before sending it
844 * to the client. The client software may reformat it again.
847 char *mptr, /* where are we going to get our text from? */
848 char subst, /* nonzero if we should do substitutions */
849 char *nl) /* string to terminate lines with */
857 static int width = 80;
862 c = 1; /* c is the current pos */
866 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
868 buffer[strlen(buffer) + 1] = 0;
869 buffer[strlen(buffer)] = ch;
872 if (buffer[0] == '^')
873 do_help_subst(buffer);
875 buffer[strlen(buffer) + 1] = 0;
877 strcpy(buffer, &buffer[1]);
885 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
888 if (((old == 13) || (old == 10)) && (isspace(real))) {
893 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
894 cprintf("%s%s", nl, aaa);
903 if ((strlen(aaa) + c) > (width - 5)) {
912 if ((ch == 13) || (ch == 10)) {
913 cprintf("%s%s", aaa, nl);
920 cprintf("%s%s", aaa, nl);
926 * Callback function for mime parser that simply lists the part
928 void list_this_part(char *name, char *filename, char *partnum, char *disp,
929 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
930 char *cbid, void *cbuserdata)
934 ma = (struct ma_info *)cbuserdata;
935 if (ma->is_ma == 0) {
936 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
937 name, filename, partnum, disp, cbtype, (long)length, cbid);
942 * Callback function for multipart prefix
944 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
945 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
946 char *cbid, void *cbuserdata)
950 ma = (struct ma_info *)cbuserdata;
951 if (!strcasecmp(cbtype, "multipart/alternative")) {
955 if (ma->is_ma == 0) {
956 cprintf("pref=%s|%s\n", partnum, cbtype);
961 * Callback function for multipart sufffix
963 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
964 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
965 char *cbid, void *cbuserdata)
969 ma = (struct ma_info *)cbuserdata;
970 if (ma->is_ma == 0) {
971 cprintf("suff=%s|%s\n", partnum, cbtype);
973 if (!strcasecmp(cbtype, "multipart/alternative")) {
980 * Callback function for mime parser that opens a section for downloading
982 void mime_download(char *name, char *filename, char *partnum, char *disp,
983 void *content, char *cbtype, char *cbcharset, size_t length,
984 char *encoding, char *cbid, void *cbuserdata)
987 /* Silently go away if there's already a download open. */
988 if (CC->download_fp != NULL)
992 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
993 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
995 CC->download_fp = tmpfile();
996 if (CC->download_fp == NULL)
999 fwrite(content, length, 1, CC->download_fp);
1000 fflush(CC->download_fp);
1001 rewind(CC->download_fp);
1003 OpenCmdResult(filename, cbtype);
1010 * Callback function for mime parser that outputs a section all at once.
1011 * We can specify the desired section by part number *or* content-id.
1013 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1014 void *content, char *cbtype, char *cbcharset, size_t length,
1015 char *encoding, char *cbid, void *cbuserdata)
1017 int *found_it = (int *)cbuserdata;
1020 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1021 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1024 cprintf("%d %d|-1|%s|%s\n",
1030 client_write(content, length);
1037 * Load a message from disk into memory.
1038 * This is used by CtdlOutputMsg() and other fetch functions.
1040 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1041 * using the CtdlMessageFree() function.
1043 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1045 struct cdbdata *dmsgtext;
1046 struct CtdlMessage *ret = NULL;
1050 cit_uint8_t field_header;
1052 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1054 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1055 if (dmsgtext == NULL) {
1058 mptr = dmsgtext->ptr;
1059 upper_bound = mptr + dmsgtext->len;
1061 /* Parse the three bytes that begin EVERY message on disk.
1062 * The first is always 0xFF, the on-disk magic number.
1063 * The second is the anonymous/public type byte.
1064 * The third is the format type byte (vari, fixed, or MIME).
1068 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1072 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1073 memset(ret, 0, sizeof(struct CtdlMessage));
1075 ret->cm_magic = CTDLMESSAGE_MAGIC;
1076 ret->cm_anon_type = *mptr++; /* Anon type byte */
1077 ret->cm_format_type = *mptr++; /* Format type byte */
1080 * The rest is zero or more arbitrary fields. Load them in.
1081 * We're done when we encounter either a zero-length field or
1082 * have just processed the 'M' (message text) field.
1085 if (mptr >= upper_bound) {
1088 field_header = *mptr++;
1089 ret->cm_fields[field_header] = strdup(mptr);
1091 while (*mptr++ != 0); /* advance to next field */
1093 } while ((mptr < upper_bound) && (field_header != 'M'));
1097 /* Always make sure there's something in the msg text field. If
1098 * it's NULL, the message text is most likely stored separately,
1099 * so go ahead and fetch that. Failing that, just set a dummy
1100 * body so other code doesn't barf.
1102 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1103 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1104 if (dmsgtext != NULL) {
1105 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1109 if (ret->cm_fields['M'] == NULL) {
1110 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1113 /* Perform "before read" hooks (aborting if any return nonzero) */
1114 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1115 CtdlFreeMessage(ret);
1124 * Returns 1 if the supplied pointer points to a valid Citadel message.
1125 * If the pointer is NULL or the magic number check fails, returns 0.
1127 int is_valid_message(struct CtdlMessage *msg) {
1130 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1131 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1139 * 'Destructor' for struct CtdlMessage
1141 void CtdlFreeMessage(struct CtdlMessage *msg)
1145 if (is_valid_message(msg) == 0)
1147 if (msg != NULL) free (msg);
1151 for (i = 0; i < 256; ++i)
1152 if (msg->cm_fields[i] != NULL) {
1153 free(msg->cm_fields[i]);
1156 msg->cm_magic = 0; /* just in case */
1162 * Pre callback function for multipart/alternative
1164 * NOTE: this differs from the standard behavior for a reason. Normally when
1165 * displaying multipart/alternative you want to show the _last_ usable
1166 * format in the message. Here we show the _first_ one, because it's
1167 * usually text/plain. Since this set of functions is designed for text
1168 * output to non-MIME-aware clients, this is the desired behavior.
1171 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1172 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1173 char *cbid, void *cbuserdata)
1177 ma = (struct ma_info *)cbuserdata;
1178 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1179 if (!strcasecmp(cbtype, "multipart/alternative")) {
1183 if (!strcasecmp(cbtype, "message/rfc822")) {
1189 * Post callback function for multipart/alternative
1191 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1192 void *content, char *cbtype, char *cbcharset, size_t length,
1193 char *encoding, char *cbid, void *cbuserdata)
1197 ma = (struct ma_info *)cbuserdata;
1198 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1199 if (!strcasecmp(cbtype, "multipart/alternative")) {
1203 if (!strcasecmp(cbtype, "message/rfc822")) {
1209 * Inline callback function for mime parser that wants to display text
1211 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1212 void *content, char *cbtype, char *cbcharset, size_t length,
1213 char *encoding, char *cbid, void *cbuserdata)
1220 ma = (struct ma_info *)cbuserdata;
1222 CtdlLogPrintf(CTDL_DEBUG,
1223 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1224 partnum, filename, cbtype, (long)length);
1227 * If we're in the middle of a multipart/alternative scope and
1228 * we've already printed another section, skip this one.
1230 if ( (ma->is_ma) && (ma->did_print) ) {
1231 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1236 if ( (!strcasecmp(cbtype, "text/plain"))
1237 || (IsEmptyStr(cbtype)) ) {
1240 client_write(wptr, length);
1241 if (wptr[length-1] != '\n') {
1248 if (!strcasecmp(cbtype, "text/html")) {
1249 ptr = html_to_ascii(content, length, 80, 0);
1251 client_write(ptr, wlen);
1252 if (ptr[wlen-1] != '\n') {
1259 if (ma->use_fo_hooks) {
1260 if (PerformFixedOutputHooks(cbtype, content, length)) {
1261 /* above function returns nonzero if it handled the part */
1266 if (strncasecmp(cbtype, "multipart/", 10)) {
1267 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1268 partnum, filename, cbtype, (long)length);
1274 * The client is elegant and sophisticated and wants to be choosy about
1275 * MIME content types, so figure out which multipart/alternative part
1276 * we're going to send.
1278 * We use a system of weights. When we find a part that matches one of the
1279 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1280 * and then set ma->chosen_pref to that MIME type's position in our preference
1281 * list. If we then hit another match, we only replace the first match if
1282 * the preference value is lower.
1284 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1285 void *content, char *cbtype, char *cbcharset, size_t length,
1286 char *encoding, char *cbid, void *cbuserdata)
1292 ma = (struct ma_info *)cbuserdata;
1294 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1295 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1296 // I don't know if there are any side effects! Please TEST TEST TEST
1297 //if (ma->is_ma > 0) {
1299 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1300 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1301 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1302 if (i < ma->chosen_pref) {
1303 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1304 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1305 ma->chosen_pref = i;
1312 * Now that we've chosen our preferred part, output it.
1314 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1315 void *content, char *cbtype, char *cbcharset, size_t length,
1316 char *encoding, char *cbid, void *cbuserdata)
1320 int add_newline = 0;
1324 ma = (struct ma_info *)cbuserdata;
1326 /* This is not the MIME part you're looking for... */
1327 if (strcasecmp(partnum, ma->chosen_part)) return;
1329 /* If the content-type of this part is in our preferred formats
1330 * list, we can simply output it verbatim.
1332 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1333 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1334 if (!strcasecmp(buf, cbtype)) {
1335 /* Yeah! Go! W00t!! */
1337 text_content = (char *)content;
1338 if (text_content[length-1] != '\n') {
1341 cprintf("Content-type: %s", cbtype);
1342 if (!IsEmptyStr(cbcharset)) {
1343 cprintf("; charset=%s", cbcharset);
1345 cprintf("\nContent-length: %d\n",
1346 (int)(length + add_newline) );
1347 if (!IsEmptyStr(encoding)) {
1348 cprintf("Content-transfer-encoding: %s\n", encoding);
1351 cprintf("Content-transfer-encoding: 7bit\n");
1353 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1355 client_write(content, length);
1356 if (add_newline) cprintf("\n");
1361 /* No translations required or possible: output as text/plain */
1362 cprintf("Content-type: text/plain\n\n");
1363 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1364 length, encoding, cbid, cbuserdata);
1369 char desired_section[64];
1376 * Callback function for
1378 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1379 void *content, char *cbtype, char *cbcharset, size_t length,
1380 char *encoding, char *cbid, void *cbuserdata)
1382 struct encapmsg *encap;
1384 encap = (struct encapmsg *)cbuserdata;
1386 /* Only proceed if this is the desired section... */
1387 if (!strcasecmp(encap->desired_section, partnum)) {
1388 encap->msglen = length;
1389 encap->msg = malloc(length + 2);
1390 memcpy(encap->msg, content, length);
1400 * Get a message off disk. (returns om_* values found in msgbase.h)
1403 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1404 int mode, /* how would you like that message? */
1405 int headers_only, /* eschew the message body? */
1406 int do_proto, /* do Citadel protocol responses? */
1407 int crlf, /* Use CRLF newlines instead of LF? */
1408 char *section, /* NULL or a message/rfc822 section */
1409 int flags /* should the bessage be exported clean? */
1411 struct CtdlMessage *TheMessage = NULL;
1412 int retcode = om_no_such_msg;
1413 struct encapmsg encap;
1415 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1417 (section ? section : "<>")
1420 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1421 if (do_proto) cprintf("%d Not logged in.\n",
1422 ERROR + NOT_LOGGED_IN);
1423 return(om_not_logged_in);
1426 /* FIXME: check message id against msglist for this room */
1429 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1430 * request that we don't even bother loading the body into memory.
1432 if (headers_only == HEADERS_FAST) {
1433 TheMessage = CtdlFetchMessage(msg_num, 0);
1436 TheMessage = CtdlFetchMessage(msg_num, 1);
1439 if (TheMessage == NULL) {
1440 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1441 ERROR + MESSAGE_NOT_FOUND, msg_num);
1442 return(om_no_such_msg);
1445 /* Here is the weird form of this command, to process only an
1446 * encapsulated message/rfc822 section.
1448 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1449 memset(&encap, 0, sizeof encap);
1450 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1451 mime_parser(TheMessage->cm_fields['M'],
1453 *extract_encapsulated_message,
1454 NULL, NULL, (void *)&encap, 0
1456 CtdlFreeMessage(TheMessage);
1460 encap.msg[encap.msglen] = 0;
1461 TheMessage = convert_internet_message(encap.msg);
1462 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1464 /* Now we let it fall through to the bottom of this
1465 * function, because TheMessage now contains the
1466 * encapsulated message instead of the top-level
1467 * message. Isn't that neat?
1472 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1473 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1474 retcode = om_no_such_msg;
1479 /* Ok, output the message now */
1480 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1481 CtdlFreeMessage(TheMessage);
1487 char *qp_encode_email_addrs(char *source)
1489 char user[256], node[256], name[256];
1490 const char headerStr[] = "=?UTF-8?Q?";
1494 int need_to_encode = 0;
1500 long nAddrPtrMax = 50;
1505 if (source == NULL) return source;
1506 if (IsEmptyStr(source)) return source;
1508 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1509 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1510 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1513 while (!IsEmptyStr (&source[i])) {
1514 if (nColons >= nAddrPtrMax){
1517 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1518 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1519 free (AddrPtr), AddrPtr = ptr;
1521 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1522 memset(&ptr[nAddrPtrMax], 0,
1523 sizeof (long) * nAddrPtrMax);
1525 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1526 free (AddrUtf8), AddrUtf8 = ptr;
1529 if (((unsigned char) source[i] < 32) ||
1530 ((unsigned char) source[i] > 126)) {
1532 AddrUtf8[nColons] = 1;
1534 if (source[i] == '"')
1535 InQuotes = !InQuotes;
1536 if (!InQuotes && source[i] == ',') {
1537 AddrPtr[nColons] = i;
1542 if (need_to_encode == 0) {
1549 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1550 Encoded = (char*) malloc (EncodedMaxLen);
1552 for (i = 0; i < nColons; i++)
1553 source[AddrPtr[i]++] = '\0';
1557 for (i = 0; i < nColons && nPtr != NULL; i++) {
1558 nmax = EncodedMaxLen - (nPtr - Encoded);
1560 process_rfc822_addr(&source[AddrPtr[i]],
1564 /* TODO: libIDN here ! */
1565 if (IsEmptyStr(name)) {
1566 n = snprintf(nPtr, nmax,
1567 (i==0)?"%s@%s" : ",%s@%s",
1571 EncodedName = rfc2047encode(name, strlen(name));
1572 n = snprintf(nPtr, nmax,
1573 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1574 EncodedName, user, node);
1579 n = snprintf(nPtr, nmax,
1580 (i==0)?"%s" : ",%s",
1581 &source[AddrPtr[i]]);
1587 ptr = (char*) malloc(EncodedMaxLen * 2);
1588 memcpy(ptr, Encoded, EncodedMaxLen);
1589 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1590 free(Encoded), Encoded = ptr;
1592 i--; /* do it once more with properly lengthened buffer */
1595 for (i = 0; i < nColons; i++)
1596 source[--AddrPtr[i]] = ',';
1603 /* If the last item in a list of recipients was truncated to a partial address,
1604 * remove it completely in order to avoid choking libSieve
1606 void sanitize_truncated_recipient(char *str)
1609 if (num_tokens(str, ',') < 2) return;
1611 int len = strlen(str);
1612 if (len < 900) return;
1613 if (len > 998) str[998] = 0;
1615 char *cptr = strrchr(str, ',');
1618 char *lptr = strchr(cptr, '<');
1619 char *rptr = strchr(cptr, '>');
1621 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1629 * Get a message off disk. (returns om_* values found in msgbase.h)
1631 int CtdlOutputPreLoadedMsg(
1632 struct CtdlMessage *TheMessage,
1633 int mode, /* how would you like that message? */
1634 int headers_only, /* eschew the message body? */
1635 int do_proto, /* do Citadel protocol responses? */
1636 int crlf, /* Use CRLF newlines instead of LF? */
1637 int flags /* should the bessage be exported clean? */
1641 cit_uint8_t ch, prev_ch;
1643 char display_name[256];
1645 char *nl; /* newline string */
1647 int subject_found = 0;
1650 /* Buffers needed for RFC822 translation. These are all filled
1651 * using functions that are bounds-checked, and therefore we can
1652 * make them substantially smaller than SIZ.
1659 char datestamp[100];
1661 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1662 ((TheMessage == NULL) ? "NULL" : "not null"),
1663 mode, headers_only, do_proto, crlf);
1665 strcpy(mid, "unknown");
1666 nl = (crlf ? "\r\n" : "\n");
1668 if (!is_valid_message(TheMessage)) {
1669 CtdlLogPrintf(CTDL_ERR,
1670 "ERROR: invalid preloaded message for output\n");
1672 return(om_no_such_msg);
1675 /* Are we downloading a MIME component? */
1676 if (mode == MT_DOWNLOAD) {
1677 if (TheMessage->cm_format_type != FMT_RFC822) {
1679 cprintf("%d This is not a MIME message.\n",
1680 ERROR + ILLEGAL_VALUE);
1681 } else if (CC->download_fp != NULL) {
1682 if (do_proto) cprintf(
1683 "%d You already have a download open.\n",
1684 ERROR + RESOURCE_BUSY);
1686 /* Parse the message text component */
1687 mptr = TheMessage->cm_fields['M'];
1688 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1689 /* If there's no file open by this time, the requested
1690 * section wasn't found, so print an error
1692 if (CC->download_fp == NULL) {
1693 if (do_proto) cprintf(
1694 "%d Section %s not found.\n",
1695 ERROR + FILE_NOT_FOUND,
1696 CC->download_desired_section);
1699 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1702 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1703 * in a single server operation instead of opening a download file.
1705 if (mode == MT_SPEW_SECTION) {
1706 if (TheMessage->cm_format_type != FMT_RFC822) {
1708 cprintf("%d This is not a MIME message.\n",
1709 ERROR + ILLEGAL_VALUE);
1711 /* Parse the message text component */
1714 mptr = TheMessage->cm_fields['M'];
1715 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1716 /* If section wasn't found, print an error
1719 if (do_proto) cprintf(
1720 "%d Section %s not found.\n",
1721 ERROR + FILE_NOT_FOUND,
1722 CC->download_desired_section);
1725 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1728 /* now for the user-mode message reading loops */
1729 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1731 /* Does the caller want to skip the headers? */
1732 if (headers_only == HEADERS_NONE) goto START_TEXT;
1734 /* Tell the client which format type we're using. */
1735 if ( (mode == MT_CITADEL) && (do_proto) ) {
1736 cprintf("type=%d\n", TheMessage->cm_format_type);
1739 /* nhdr=yes means that we're only displaying headers, no body */
1740 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1741 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1744 cprintf("nhdr=yes\n");
1747 /* begin header processing loop for Citadel message format */
1749 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1751 safestrncpy(display_name, "<unknown>", sizeof display_name);
1752 if (TheMessage->cm_fields['A']) {
1753 strcpy(buf, TheMessage->cm_fields['A']);
1754 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1755 safestrncpy(display_name, "****", sizeof display_name);
1757 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1758 safestrncpy(display_name, "anonymous", sizeof display_name);
1761 safestrncpy(display_name, buf, sizeof display_name);
1763 if ((is_room_aide())
1764 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1765 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1766 size_t tmp = strlen(display_name);
1767 snprintf(&display_name[tmp],
1768 sizeof display_name - tmp,
1773 /* Don't show Internet address for users on the
1774 * local Citadel network.
1777 if (TheMessage->cm_fields['N'] != NULL)
1778 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1779 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1783 /* Now spew the header fields in the order we like them. */
1784 safestrncpy(allkeys, FORDER, sizeof allkeys);
1785 for (i=0; i<strlen(allkeys); ++i) {
1786 k = (int) allkeys[i];
1788 if ( (TheMessage->cm_fields[k] != NULL)
1789 && (msgkeys[k] != NULL) ) {
1790 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1791 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1794 if (do_proto) cprintf("%s=%s\n",
1798 else if ((k == 'F') && (suppress_f)) {
1801 /* Masquerade display name if needed */
1803 if (do_proto) cprintf("%s=%s\n",
1805 TheMessage->cm_fields[k]
1814 /* begin header processing loop for RFC822 transfer format */
1819 strcpy(snode, NODENAME);
1820 if (mode == MT_RFC822) {
1821 for (i = 0; i < 256; ++i) {
1822 if (TheMessage->cm_fields[i]) {
1823 mptr = mpptr = TheMessage->cm_fields[i];
1826 safestrncpy(luser, mptr, sizeof luser);
1827 safestrncpy(suser, mptr, sizeof suser);
1829 else if (i == 'Y') {
1830 if ((flags & QP_EADDR) != 0) {
1831 mptr = qp_encode_email_addrs(mptr);
1833 sanitize_truncated_recipient(mptr);
1834 cprintf("CC: %s%s", mptr, nl);
1836 else if (i == 'P') {
1837 cprintf("Return-Path: %s%s", mptr, nl);
1839 else if (i == 'L') {
1840 cprintf("List-ID: %s%s", mptr, nl);
1842 else if (i == 'V') {
1843 if ((flags & QP_EADDR) != 0)
1844 mptr = qp_encode_email_addrs(mptr);
1845 cprintf("Envelope-To: %s%s", mptr, nl);
1847 else if (i == 'U') {
1848 cprintf("Subject: %s%s", mptr, nl);
1852 safestrncpy(mid, mptr, sizeof mid);
1854 safestrncpy(fuser, mptr, sizeof fuser);
1855 /* else if (i == 'O')
1856 cprintf("X-Citadel-Room: %s%s",
1859 safestrncpy(snode, mptr, sizeof snode);
1862 if (haschar(mptr, '@') == 0)
1864 sanitize_truncated_recipient(mptr);
1865 cprintf("To: %s@%s", mptr, config.c_fqdn);
1870 if ((flags & QP_EADDR) != 0) {
1871 mptr = qp_encode_email_addrs(mptr);
1873 sanitize_truncated_recipient(mptr);
1874 cprintf("To: %s", mptr);
1878 else if (i == 'T') {
1879 datestring(datestamp, sizeof datestamp,
1880 atol(mptr), DATESTRING_RFC822);
1881 cprintf("Date: %s%s", datestamp, nl);
1883 else if (i == 'W') {
1884 cprintf("References: ");
1885 k = num_tokens(mptr, '|');
1886 for (j=0; j<k; ++j) {
1887 extract_token(buf, mptr, j, '|', sizeof buf);
1888 cprintf("<%s>", buf);
1901 if (subject_found == 0) {
1902 cprintf("Subject: (no subject)%s", nl);
1906 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1907 suser[i] = tolower(suser[i]);
1908 if (!isalnum(suser[i])) suser[i]='_';
1911 if (mode == MT_RFC822) {
1912 if (!strcasecmp(snode, NODENAME)) {
1913 safestrncpy(snode, FQDN, sizeof snode);
1916 /* Construct a fun message id */
1917 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1918 if (strchr(mid, '@')==NULL) {
1919 cprintf("@%s", snode);
1923 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1924 cprintf("From: \"----\" <x@x.org>%s", nl);
1926 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1927 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1929 else if (!IsEmptyStr(fuser)) {
1930 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1933 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1936 /* Blank line signifying RFC822 end-of-headers */
1937 if (TheMessage->cm_format_type != FMT_RFC822) {
1942 /* end header processing loop ... at this point, we're in the text */
1944 if (headers_only == HEADERS_FAST) goto DONE;
1945 mptr = TheMessage->cm_fields['M'];
1947 /* Tell the client about the MIME parts in this message */
1948 if (TheMessage->cm_format_type == FMT_RFC822) {
1949 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1950 memset(&ma, 0, sizeof(struct ma_info));
1951 mime_parser(mptr, NULL,
1952 (do_proto ? *list_this_part : NULL),
1953 (do_proto ? *list_this_pref : NULL),
1954 (do_proto ? *list_this_suff : NULL),
1957 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1958 char *start_of_text = NULL;
1959 start_of_text = strstr(mptr, "\n\r\n");
1960 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1961 if (start_of_text == NULL) start_of_text = mptr;
1963 start_of_text = strstr(start_of_text, "\n");
1968 int nllen = strlen(nl);
1970 while (ch=*mptr, ch!=0) {
1976 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
1977 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
1978 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
1981 sprintf(&outbuf[outlen], "%s", nl);
1985 outbuf[outlen++] = ch;
1989 if (flags & ESC_DOT)
1991 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
1993 outbuf[outlen++] = '.';
1998 if (outlen > 1000) {
1999 client_write(outbuf, outlen);
2004 client_write(outbuf, outlen);
2012 if (headers_only == HEADERS_ONLY) {
2016 /* signify start of msg text */
2017 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2018 if (do_proto) cprintf("text\n");
2021 /* If the format type on disk is 1 (fixed-format), then we want
2022 * everything to be output completely literally ... regardless of
2023 * what message transfer format is in use.
2025 if (TheMessage->cm_format_type == FMT_FIXED) {
2027 if (mode == MT_MIME) {
2028 cprintf("Content-type: text/plain\n\n");
2032 while (ch = *mptr++, ch > 0) {
2035 if ((ch == 10) || (buflen > 250)) {
2037 cprintf("%s%s", buf, nl);
2046 if (!IsEmptyStr(buf))
2047 cprintf("%s%s", buf, nl);
2050 /* If the message on disk is format 0 (Citadel vari-format), we
2051 * output using the formatter at 80 columns. This is the final output
2052 * form if the transfer format is RFC822, but if the transfer format
2053 * is Citadel proprietary, it'll still work, because the indentation
2054 * for new paragraphs is correct and the client will reformat the
2055 * message to the reader's screen width.
2057 if (TheMessage->cm_format_type == FMT_CITADEL) {
2058 if (mode == MT_MIME) {
2059 cprintf("Content-type: text/x-citadel-variformat\n\n");
2061 memfmout(mptr, 0, nl);
2064 /* If the message on disk is format 4 (MIME), we've gotta hand it
2065 * off to the MIME parser. The client has already been told that
2066 * this message is format 1 (fixed format), so the callback function
2067 * we use will display those parts as-is.
2069 if (TheMessage->cm_format_type == FMT_RFC822) {
2070 memset(&ma, 0, sizeof(struct ma_info));
2072 if (mode == MT_MIME) {
2073 ma.use_fo_hooks = 0;
2074 strcpy(ma.chosen_part, "1");
2075 ma.chosen_pref = 9999;
2076 mime_parser(mptr, NULL,
2077 *choose_preferred, *fixed_output_pre,
2078 *fixed_output_post, (void *)&ma, 0);
2079 mime_parser(mptr, NULL,
2080 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2083 ma.use_fo_hooks = 1;
2084 mime_parser(mptr, NULL,
2085 *fixed_output, *fixed_output_pre,
2086 *fixed_output_post, (void *)&ma, 0);
2091 DONE: /* now we're done */
2092 if (do_proto) cprintf("000\n");
2099 * display a message (mode 0 - Citadel proprietary)
2101 void cmd_msg0(char *cmdbuf)
2104 int headers_only = HEADERS_ALL;
2106 msgid = extract_long(cmdbuf, 0);
2107 headers_only = extract_int(cmdbuf, 1);
2109 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2115 * display a message (mode 2 - RFC822)
2117 void cmd_msg2(char *cmdbuf)
2120 int headers_only = HEADERS_ALL;
2122 msgid = extract_long(cmdbuf, 0);
2123 headers_only = extract_int(cmdbuf, 1);
2125 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2131 * display a message (mode 3 - IGnet raw format - internal programs only)
2133 void cmd_msg3(char *cmdbuf)
2136 struct CtdlMessage *msg = NULL;
2139 if (CC->internal_pgm == 0) {
2140 cprintf("%d This command is for internal programs only.\n",
2141 ERROR + HIGHER_ACCESS_REQUIRED);
2145 msgnum = extract_long(cmdbuf, 0);
2146 msg = CtdlFetchMessage(msgnum, 1);
2148 cprintf("%d Message %ld not found.\n",
2149 ERROR + MESSAGE_NOT_FOUND, msgnum);
2153 serialize_message(&smr, msg);
2154 CtdlFreeMessage(msg);
2157 cprintf("%d Unable to serialize message\n",
2158 ERROR + INTERNAL_ERROR);
2162 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2163 client_write((char *)smr.ser, (int)smr.len);
2170 * Display a message using MIME content types
2172 void cmd_msg4(char *cmdbuf)
2177 msgid = extract_long(cmdbuf, 0);
2178 extract_token(section, cmdbuf, 1, '|', sizeof section);
2179 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2185 * Client tells us its preferred message format(s)
2187 void cmd_msgp(char *cmdbuf)
2189 if (!strcasecmp(cmdbuf, "dont_decode")) {
2190 CC->msg4_dont_decode = 1;
2191 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2194 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2195 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2201 * Open a component of a MIME message as a download file
2203 void cmd_opna(char *cmdbuf)
2206 char desired_section[128];
2208 msgid = extract_long(cmdbuf, 0);
2209 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2210 safestrncpy(CC->download_desired_section, desired_section,
2211 sizeof CC->download_desired_section);
2212 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2217 * Open a component of a MIME message and transmit it all at once
2219 void cmd_dlat(char *cmdbuf)
2222 char desired_section[128];
2224 msgid = extract_long(cmdbuf, 0);
2225 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2226 safestrncpy(CC->download_desired_section, desired_section,
2227 sizeof CC->download_desired_section);
2228 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2233 * Save one or more message pointers into a specified room
2234 * (Returns 0 for success, nonzero for failure)
2235 * roomname may be NULL to use the current room
2237 * Note that the 'supplied_msg' field may be set to NULL, in which case
2238 * the message will be fetched from disk, by number, if we need to perform
2239 * replication checks. This adds an additional database read, so if the
2240 * caller already has the message in memory then it should be supplied. (Obviously
2241 * this mode of operation only works if we're saving a single message.)
2243 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2244 int do_repl_check, struct CtdlMessage *supplied_msg)
2247 char hold_rm[ROOMNAMELEN];
2248 struct cdbdata *cdbfr;
2251 long highest_msg = 0L;
2254 struct CtdlMessage *msg = NULL;
2256 long *msgs_to_be_merged = NULL;
2257 int num_msgs_to_be_merged = 0;
2259 CtdlLogPrintf(CTDL_DEBUG,
2260 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2261 roomname, num_newmsgs, do_repl_check);
2263 strcpy(hold_rm, CC->room.QRname);
2266 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2267 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2268 if (num_newmsgs > 1) supplied_msg = NULL;
2270 /* Now the regular stuff */
2271 if (lgetroom(&CC->room,
2272 ((roomname != NULL) ? roomname : CC->room.QRname) )
2274 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2275 return(ERROR + ROOM_NOT_FOUND);
2279 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2280 num_msgs_to_be_merged = 0;
2283 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2284 if (cdbfr == NULL) {
2288 msglist = (long *) cdbfr->ptr;
2289 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2290 num_msgs = cdbfr->len / sizeof(long);
2295 /* Create a list of msgid's which were supplied by the caller, but do
2296 * not already exist in the target room. It is absolutely taboo to
2297 * have more than one reference to the same message in a room.
2299 for (i=0; i<num_newmsgs; ++i) {
2301 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2302 if (msglist[j] == newmsgidlist[i]) {
2307 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2311 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2314 * Now merge the new messages
2316 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2317 if (msglist == NULL) {
2318 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2320 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2321 num_msgs += num_msgs_to_be_merged;
2323 /* Sort the message list, so all the msgid's are in order */
2324 num_msgs = sort_msglist(msglist, num_msgs);
2326 /* Determine the highest message number */
2327 highest_msg = msglist[num_msgs - 1];
2329 /* Write it back to disk. */
2330 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2331 msglist, (int)(num_msgs * sizeof(long)));
2333 /* Free up the memory we used. */
2336 /* Update the highest-message pointer and unlock the room. */
2337 CC->room.QRhighest = highest_msg;
2338 lputroom(&CC->room);
2340 /* Perform replication checks if necessary */
2341 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2342 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2344 for (i=0; i<num_msgs_to_be_merged; ++i) {
2345 msgid = msgs_to_be_merged[i];
2347 if (supplied_msg != NULL) {
2351 msg = CtdlFetchMessage(msgid, 0);
2355 ReplicationChecks(msg);
2357 /* If the message has an Exclusive ID, index that... */
2358 if (msg->cm_fields['E'] != NULL) {
2359 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2362 /* Free up the memory we may have allocated */
2363 if (msg != supplied_msg) {
2364 CtdlFreeMessage(msg);
2372 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2375 /* Submit this room for processing by hooks */
2376 PerformRoomHooks(&CC->room);
2378 /* Go back to the room we were in before we wandered here... */
2379 getroom(&CC->room, hold_rm);
2381 /* Bump the reference count for all messages which were merged */
2382 for (i=0; i<num_msgs_to_be_merged; ++i) {
2383 AdjRefCount(msgs_to_be_merged[i], +1);
2386 /* Free up memory... */
2387 if (msgs_to_be_merged != NULL) {
2388 free(msgs_to_be_merged);
2391 /* Return success. */
2397 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2400 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2401 int do_repl_check, struct CtdlMessage *supplied_msg)
2403 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2410 * Message base operation to save a new message to the message store
2411 * (returns new message number)
2413 * This is the back end for CtdlSubmitMsg() and should not be directly
2414 * called by server-side modules.
2417 long send_message(struct CtdlMessage *msg) {
2425 /* Get a new message number */
2426 newmsgid = get_new_message_number();
2427 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2429 /* Generate an ID if we don't have one already */
2430 if (msg->cm_fields['I']==NULL) {
2431 msg->cm_fields['I'] = strdup(msgidbuf);
2434 /* If the message is big, set its body aside for storage elsewhere */
2435 if (msg->cm_fields['M'] != NULL) {
2436 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2438 holdM = msg->cm_fields['M'];
2439 msg->cm_fields['M'] = NULL;
2443 /* Serialize our data structure for storage in the database */
2444 serialize_message(&smr, msg);
2447 msg->cm_fields['M'] = holdM;
2451 cprintf("%d Unable to serialize message\n",
2452 ERROR + INTERNAL_ERROR);
2456 /* Write our little bundle of joy into the message base */
2457 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2458 smr.ser, smr.len) < 0) {
2459 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2463 cdb_store(CDB_BIGMSGS,
2473 /* Free the memory we used for the serialized message */
2476 /* Return the *local* message ID to the caller
2477 * (even if we're storing an incoming network message)
2485 * Serialize a struct CtdlMessage into the format used on disk and network.
2487 * This function loads up a "struct ser_ret" (defined in server.h) which
2488 * contains the length of the serialized message and a pointer to the
2489 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2491 void serialize_message(struct ser_ret *ret, /* return values */
2492 struct CtdlMessage *msg) /* unserialized msg */
2494 size_t wlen, fieldlen;
2496 static char *forder = FORDER;
2499 * Check for valid message format
2501 if (is_valid_message(msg) == 0) {
2502 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2509 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2510 ret->len = ret->len +
2511 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2513 ret->ser = malloc(ret->len);
2514 if (ret->ser == NULL) {
2515 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2516 (long)ret->len, strerror(errno));
2523 ret->ser[1] = msg->cm_anon_type;
2524 ret->ser[2] = msg->cm_format_type;
2527 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2528 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2529 ret->ser[wlen++] = (char)forder[i];
2530 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2531 wlen = wlen + fieldlen + 1;
2533 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2534 (long)ret->len, (long)wlen);
2541 * Serialize a struct CtdlMessage into the format used on disk and network.
2543 * This function loads up a "struct ser_ret" (defined in server.h) which
2544 * contains the length of the serialized message and a pointer to the
2545 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2547 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2548 long Siz) /* how many chars ? */
2552 static char *forder = FORDER;
2556 * Check for valid message format
2558 if (is_valid_message(msg) == 0) {
2559 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2563 buf = (char*) malloc (Siz + 1);
2567 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2568 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2569 msg->cm_fields[(int)forder[i]]);
2570 client_write (buf, strlen(buf));
2579 * Check to see if any messages already exist in the current room which
2580 * carry the same Exclusive ID as this one. If any are found, delete them.
2582 void ReplicationChecks(struct CtdlMessage *msg) {
2583 long old_msgnum = (-1L);
2585 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2587 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2590 /* No exclusive id? Don't do anything. */
2591 if (msg == NULL) return;
2592 if (msg->cm_fields['E'] == NULL) return;
2593 if (IsEmptyStr(msg->cm_fields['E'])) return;
2594 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2595 msg->cm_fields['E'], CC->room.QRname);*/
2597 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2598 if (old_msgnum > 0L) {
2599 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2600 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2607 * Save a message to disk and submit it into the delivery system.
2609 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2610 struct recptypes *recps, /* recipients (if mail) */
2611 char *force, /* force a particular room? */
2612 int flags /* should the bessage be exported clean? */
2614 char submit_filename[128];
2615 char generated_timestamp[32];
2616 char hold_rm[ROOMNAMELEN];
2617 char actual_rm[ROOMNAMELEN];
2618 char force_room[ROOMNAMELEN];
2619 char content_type[SIZ]; /* We have to learn this */
2620 char recipient[SIZ];
2623 struct ctdluser userbuf;
2625 struct MetaData smi;
2626 FILE *network_fp = NULL;
2627 static int seqnum = 1;
2628 struct CtdlMessage *imsg = NULL;
2630 size_t instr_alloc = 0;
2632 char *hold_R, *hold_D;
2633 char *collected_addresses = NULL;
2634 struct addresses_to_be_filed *aptr = NULL;
2635 char *saved_rfc822_version = NULL;
2636 int qualified_for_journaling = 0;
2637 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2638 char bounce_to[1024] = "";
2641 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2642 if (is_valid_message(msg) == 0) return(-1); /* self check */
2644 /* If this message has no timestamp, we take the liberty of
2645 * giving it one, right now.
2647 if (msg->cm_fields['T'] == NULL) {
2648 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2649 msg->cm_fields['T'] = strdup(generated_timestamp);
2652 /* If this message has no path, we generate one.
2654 if (msg->cm_fields['P'] == NULL) {
2655 if (msg->cm_fields['A'] != NULL) {
2656 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2657 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2658 if (isspace(msg->cm_fields['P'][a])) {
2659 msg->cm_fields['P'][a] = ' ';
2664 msg->cm_fields['P'] = strdup("unknown");
2668 if (force == NULL) {
2669 strcpy(force_room, "");
2672 strcpy(force_room, force);
2675 /* Learn about what's inside, because it's what's inside that counts */
2676 if (msg->cm_fields['M'] == NULL) {
2677 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2681 switch (msg->cm_format_type) {
2683 strcpy(content_type, "text/x-citadel-variformat");
2686 strcpy(content_type, "text/plain");
2689 strcpy(content_type, "text/plain");
2690 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2693 safestrncpy(content_type, &mptr[13], sizeof content_type);
2694 striplt(content_type);
2695 aptr = content_type;
2696 while (!IsEmptyStr(aptr)) {
2708 /* Goto the correct room */
2709 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2710 strcpy(hold_rm, CCC->room.QRname);
2711 strcpy(actual_rm, CCC->room.QRname);
2712 if (recps != NULL) {
2713 strcpy(actual_rm, SENTITEMS);
2716 /* If the user is a twit, move to the twit room for posting */
2718 if (CCC->user.axlevel == 2) {
2719 strcpy(hold_rm, actual_rm);
2720 strcpy(actual_rm, config.c_twitroom);
2721 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2725 /* ...or if this message is destined for Aide> then go there. */
2726 if (!IsEmptyStr(force_room)) {
2727 strcpy(actual_rm, force_room);
2730 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2731 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2732 /* getroom(&CCC->room, actual_rm); */
2733 usergoto(actual_rm, 0, 1, NULL, NULL);
2737 * If this message has no O (room) field, generate one.
2739 if (msg->cm_fields['O'] == NULL) {
2740 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2743 /* Perform "before save" hooks (aborting if any return nonzero) */
2744 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2745 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2748 * If this message has an Exclusive ID, and the room is replication
2749 * checking enabled, then do replication checks.
2751 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2752 ReplicationChecks(msg);
2755 /* Save it to disk */
2756 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2757 newmsgid = send_message(msg);
2758 if (newmsgid <= 0L) return(-5);
2760 /* Write a supplemental message info record. This doesn't have to
2761 * be a critical section because nobody else knows about this message
2764 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2765 memset(&smi, 0, sizeof(struct MetaData));
2766 smi.meta_msgnum = newmsgid;
2767 smi.meta_refcount = 0;
2768 safestrncpy(smi.meta_content_type, content_type,
2769 sizeof smi.meta_content_type);
2772 * Measure how big this message will be when rendered as RFC822.
2773 * We do this for two reasons:
2774 * 1. We need the RFC822 length for the new metadata record, so the
2775 * POP and IMAP services don't have to calculate message lengths
2776 * while the user is waiting (multiplied by potentially hundreds
2777 * or thousands of messages).
2778 * 2. If journaling is enabled, we will need an RFC822 version of the
2779 * message to attach to the journalized copy.
2781 if (CCC->redirect_buffer != NULL) {
2782 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2785 CCC->redirect_buffer = malloc(SIZ);
2786 CCC->redirect_len = 0;
2787 CCC->redirect_alloc = SIZ;
2788 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2789 smi.meta_rfc822_length = CCC->redirect_len;
2790 saved_rfc822_version = CCC->redirect_buffer;
2791 CCC->redirect_buffer = NULL;
2792 CCC->redirect_len = 0;
2793 CCC->redirect_alloc = 0;
2797 /* Now figure out where to store the pointers */
2798 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2800 /* If this is being done by the networker delivering a private
2801 * message, we want to BYPASS saving the sender's copy (because there
2802 * is no local sender; it would otherwise go to the Trashcan).
2804 if ((!CCC->internal_pgm) || (recps == NULL)) {
2805 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2806 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2807 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2811 /* For internet mail, drop a copy in the outbound queue room */
2812 if ((recps != NULL) && (recps->num_internet > 0)) {
2813 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2816 /* If other rooms are specified, drop them there too. */
2817 if ((recps != NULL) && (recps->num_room > 0))
2818 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2819 extract_token(recipient, recps->recp_room, i,
2820 '|', sizeof recipient);
2821 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2822 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2825 /* Bump this user's messages posted counter. */
2826 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2827 lgetuser(&CCC->user, CCC->curr_user);
2828 CCC->user.posted = CCC->user.posted + 1;
2829 lputuser(&CCC->user);
2831 /* Decide where bounces need to be delivered */
2832 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2833 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2835 else if (CCC->logged_in) {
2836 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2839 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2842 /* If this is private, local mail, make a copy in the
2843 * recipient's mailbox and bump the reference count.
2845 if ((recps != NULL) && (recps->num_local > 0))
2846 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2847 extract_token(recipient, recps->recp_local, i,
2848 '|', sizeof recipient);
2849 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2851 if (getuser(&userbuf, recipient) == 0) {
2852 // Add a flag so the Funambol module knows its mail
2853 msg->cm_fields['W'] = strdup(recipient);
2854 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2855 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2856 BumpNewMailCounter(userbuf.usernum);
2857 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2858 /* Generate a instruction message for the Funambol notification
2859 * server, in the same style as the SMTP queue
2862 instr = malloc(instr_alloc);
2863 snprintf(instr, instr_alloc,
2864 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2866 SPOOLMIME, newmsgid, (long)time(NULL),
2870 imsg = malloc(sizeof(struct CtdlMessage));
2871 memset(imsg, 0, sizeof(struct CtdlMessage));
2872 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2873 imsg->cm_anon_type = MES_NORMAL;
2874 imsg->cm_format_type = FMT_RFC822;
2875 imsg->cm_fields['A'] = strdup("Citadel");
2876 imsg->cm_fields['J'] = strdup("do not journal");
2877 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2878 imsg->cm_fields['W'] = strdup(recipient);
2879 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2880 CtdlFreeMessage(imsg);
2884 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2885 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2890 /* Perform "after save" hooks */
2891 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2892 PerformMessageHooks(msg, EVT_AFTERSAVE);
2894 /* For IGnet mail, we have to save a new copy into the spooler for
2895 * each recipient, with the R and D fields set to the recipient and
2896 * destination-node. This has two ugly side effects: all other
2897 * recipients end up being unlisted in this recipient's copy of the
2898 * message, and it has to deliver multiple messages to the same
2899 * node. We'll revisit this again in a year or so when everyone has
2900 * a network spool receiver that can handle the new style messages.
2902 if ((recps != NULL) && (recps->num_ignet > 0))
2903 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2904 extract_token(recipient, recps->recp_ignet, i,
2905 '|', sizeof recipient);
2907 hold_R = msg->cm_fields['R'];
2908 hold_D = msg->cm_fields['D'];
2909 msg->cm_fields['R'] = malloc(SIZ);
2910 msg->cm_fields['D'] = malloc(128);
2911 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2912 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2914 serialize_message(&smr, msg);
2916 snprintf(submit_filename, sizeof submit_filename,
2917 "%s/netmail.%04lx.%04x.%04x",
2919 (long) getpid(), CCC->cs_pid, ++seqnum);
2920 network_fp = fopen(submit_filename, "wb+");
2921 if (network_fp != NULL) {
2922 fwrite(smr.ser, smr.len, 1, network_fp);
2928 free(msg->cm_fields['R']);
2929 free(msg->cm_fields['D']);
2930 msg->cm_fields['R'] = hold_R;
2931 msg->cm_fields['D'] = hold_D;
2934 /* Go back to the room we started from */
2935 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2936 if (strcasecmp(hold_rm, CCC->room.QRname))
2937 usergoto(hold_rm, 0, 1, NULL, NULL);
2939 /* For internet mail, generate delivery instructions.
2940 * Yes, this is recursive. Deal with it. Infinite recursion does
2941 * not happen because the delivery instructions message does not
2942 * contain a recipient.
2944 if ((recps != NULL) && (recps->num_internet > 0)) {
2945 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
2947 instr = malloc(instr_alloc);
2948 snprintf(instr, instr_alloc,
2949 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2951 SPOOLMIME, newmsgid, (long)time(NULL),
2955 if (recps->envelope_from != NULL) {
2956 tmp = strlen(instr);
2957 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
2960 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2961 tmp = strlen(instr);
2962 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2963 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
2964 instr_alloc = instr_alloc * 2;
2965 instr = realloc(instr, instr_alloc);
2967 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
2970 imsg = malloc(sizeof(struct CtdlMessage));
2971 memset(imsg, 0, sizeof(struct CtdlMessage));
2972 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2973 imsg->cm_anon_type = MES_NORMAL;
2974 imsg->cm_format_type = FMT_RFC822;
2975 imsg->cm_fields['A'] = strdup("Citadel");
2976 imsg->cm_fields['J'] = strdup("do not journal");
2977 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2978 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
2979 CtdlFreeMessage(imsg);
2983 * Any addresses to harvest for someone's address book?
2985 if ( (CCC->logged_in) && (recps != NULL) ) {
2986 collected_addresses = harvest_collected_addresses(msg);
2989 if (collected_addresses != NULL) {
2990 aptr = (struct addresses_to_be_filed *)
2991 malloc(sizeof(struct addresses_to_be_filed));
2992 MailboxName(actual_rm, sizeof actual_rm,
2993 &CCC->user, USERCONTACTSROOM);
2994 aptr->roomname = strdup(actual_rm);
2995 aptr->collected_addresses = collected_addresses;
2996 begin_critical_section(S_ATBF);
2999 end_critical_section(S_ATBF);
3003 * Determine whether this message qualifies for journaling.
3005 if (msg->cm_fields['J'] != NULL) {
3006 qualified_for_journaling = 0;
3009 if (recps == NULL) {
3010 qualified_for_journaling = config.c_journal_pubmsgs;
3012 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3013 qualified_for_journaling = config.c_journal_email;
3016 qualified_for_journaling = config.c_journal_pubmsgs;
3021 * Do we have to perform journaling? If so, hand off the saved
3022 * RFC822 version will be handed off to the journaler for background
3023 * submit. Otherwise, we have to free the memory ourselves.
3025 if (saved_rfc822_version != NULL) {
3026 if (qualified_for_journaling) {
3027 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3030 free(saved_rfc822_version);
3043 * Convenience function for generating small administrative messages.
3045 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3046 int format_type, const char *subject)
3048 struct CtdlMessage *msg;
3049 struct recptypes *recp = NULL;
3051 msg = malloc(sizeof(struct CtdlMessage));
3052 memset(msg, 0, sizeof(struct CtdlMessage));
3053 msg->cm_magic = CTDLMESSAGE_MAGIC;
3054 msg->cm_anon_type = MES_NORMAL;
3055 msg->cm_format_type = format_type;
3058 msg->cm_fields['A'] = strdup(from);
3060 else if (fromaddr != NULL) {
3061 msg->cm_fields['A'] = strdup(fromaddr);
3062 if (strchr(msg->cm_fields['A'], '@')) {
3063 *strchr(msg->cm_fields['A'], '@') = 0;
3067 msg->cm_fields['A'] = strdup("Citadel");
3070 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3071 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3072 msg->cm_fields['N'] = strdup(NODENAME);
3074 msg->cm_fields['R'] = strdup(to);
3075 recp = validate_recipients(to, NULL, 0);
3077 if (subject != NULL) {
3078 msg->cm_fields['U'] = strdup(subject);
3080 msg->cm_fields['M'] = strdup(text);
3082 CtdlSubmitMsg(msg, recp, room, 0);
3083 CtdlFreeMessage(msg);
3084 if (recp != NULL) free_recipients(recp);
3090 * Back end function used by CtdlMakeMessage() and similar functions
3092 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3093 size_t maxlen, /* maximum message length */
3094 char *exist, /* if non-null, append to it;
3095 exist is ALWAYS freed */
3096 int crlf, /* CRLF newlines instead of LF */
3097 int sock /* socket handle or 0 for this session's client socket */
3101 size_t message_len = 0;
3102 size_t buffer_len = 0;
3109 if (exist == NULL) {
3116 message_len = strlen(exist);
3117 buffer_len = message_len + 4096;
3118 m = realloc(exist, buffer_len);
3125 /* Do we need to change leading ".." to "." for SMTP escaping? */
3126 if (!strcmp(terminator, ".")) {
3130 /* flush the input if we have nowhere to store it */
3135 /* read in the lines of message text one by one */
3138 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3141 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3143 if (!strcmp(buf, terminator)) finished = 1;
3145 strcat(buf, "\r\n");
3151 /* Unescape SMTP-style input of two dots at the beginning of the line */
3153 if (!strncmp(buf, "..", 2)) {
3154 strcpy(buf, &buf[1]);
3158 if ( (!flushing) && (!finished) ) {
3159 /* Measure the line */
3160 linelen = strlen(buf);
3162 /* augment the buffer if we have to */
3163 if ((message_len + linelen) >= buffer_len) {
3164 ptr = realloc(m, (buffer_len * 2) );
3165 if (ptr == NULL) { /* flush if can't allocate */
3168 buffer_len = (buffer_len * 2);
3170 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3174 /* Add the new line to the buffer. NOTE: this loop must avoid
3175 * using functions like strcat() and strlen() because they
3176 * traverse the entire buffer upon every call, and doing that
3177 * for a multi-megabyte message slows it down beyond usability.
3179 strcpy(&m[message_len], buf);
3180 message_len += linelen;
3183 /* if we've hit the max msg length, flush the rest */
3184 if (message_len >= maxlen) flushing = 1;
3186 } while (!finished);
3194 * Build a binary message to be saved on disk.
3195 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3196 * will become part of the message. This means you are no longer
3197 * responsible for managing that memory -- it will be freed along with
3198 * the rest of the fields when CtdlFreeMessage() is called.)
3201 struct CtdlMessage *CtdlMakeMessage(
3202 struct ctdluser *author, /* author's user structure */
3203 char *recipient, /* NULL if it's not mail */
3204 char *recp_cc, /* NULL if it's not mail */
3205 char *room, /* room where it's going */
3206 int type, /* see MES_ types in header file */
3207 int format_type, /* variformat, plain text, MIME... */
3208 char *fake_name, /* who we're masquerading as */
3209 char *my_email, /* which of my email addresses to use (empty is ok) */
3210 char *subject, /* Subject (optional) */
3211 char *supplied_euid, /* ...or NULL if this is irrelevant */
3212 char *preformatted_text, /* ...or NULL to read text from client */
3213 char *references /* Thread references */
3215 char dest_node[256];
3217 struct CtdlMessage *msg;
3219 msg = malloc(sizeof(struct CtdlMessage));
3220 memset(msg, 0, sizeof(struct CtdlMessage));
3221 msg->cm_magic = CTDLMESSAGE_MAGIC;
3222 msg->cm_anon_type = type;
3223 msg->cm_format_type = format_type;
3225 /* Don't confuse the poor folks if it's not routed mail. */
3226 strcpy(dest_node, "");
3231 /* Path or Return-Path */
3232 if (my_email == NULL) my_email = "";
3234 if (!IsEmptyStr(my_email)) {
3235 msg->cm_fields['P'] = strdup(my_email);
3238 snprintf(buf, sizeof buf, "%s", author->fullname);
3239 msg->cm_fields['P'] = strdup(buf);
3241 convert_spaces_to_underscores(msg->cm_fields['P']);
3243 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3244 msg->cm_fields['T'] = strdup(buf);
3246 if (fake_name[0]) /* author */
3247 msg->cm_fields['A'] = strdup(fake_name);
3249 msg->cm_fields['A'] = strdup(author->fullname);
3251 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3252 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3255 msg->cm_fields['O'] = strdup(CC->room.QRname);
3258 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3259 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3261 if (recipient[0] != 0) {
3262 msg->cm_fields['R'] = strdup(recipient);
3264 if (recp_cc[0] != 0) {
3265 msg->cm_fields['Y'] = strdup(recp_cc);
3267 if (dest_node[0] != 0) {
3268 msg->cm_fields['D'] = strdup(dest_node);
3271 if (!IsEmptyStr(my_email)) {
3272 msg->cm_fields['F'] = strdup(my_email);
3274 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3275 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3278 if (subject != NULL) {
3281 length = strlen(subject);
3287 while ((subject[i] != '\0') &&
3288 (IsAscii = isascii(subject[i]) != 0 ))
3291 msg->cm_fields['U'] = strdup(subject);
3292 else /* ok, we've got utf8 in the string. */
3294 msg->cm_fields['U'] = rfc2047encode(subject, length);
3300 if (supplied_euid != NULL) {
3301 msg->cm_fields['E'] = strdup(supplied_euid);
3304 if (references != NULL) {
3305 if (!IsEmptyStr(references)) {
3306 msg->cm_fields['W'] = strdup(references);
3310 if (preformatted_text != NULL) {
3311 msg->cm_fields['M'] = preformatted_text;
3314 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3322 * Check to see whether we have permission to post a message in the current
3323 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3324 * returns 0 on success.
3326 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3328 const char* RemoteIdentifier,
3332 if (!(CC->logged_in) &&
3333 (PostPublic == POST_LOGGED_IN)) {
3334 snprintf(errmsgbuf, n, "Not logged in.");
3335 return (ERROR + NOT_LOGGED_IN);
3337 else if (PostPublic == CHECK_EXISTANCE) {
3338 return (0); // We're Evaling whether a recipient exists
3340 else if (!(CC->logged_in)) {
3342 if ((CC->room.QRflags & QR_READONLY)) {
3343 snprintf(errmsgbuf, n, "Not logged in.");
3344 return (ERROR + NOT_LOGGED_IN);
3346 if (CC->room.QRflags2 & QR2_MODERATED) {
3347 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3348 return (ERROR + NOT_LOGGED_IN);
3350 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3355 if (RemoteIdentifier == NULL)
3357 snprintf(errmsgbuf, n, "Need sender to permit access.");
3358 return (ERROR + USERNAME_REQUIRED);
3361 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3362 begin_critical_section(S_NETCONFIGS);
3363 if (!read_spoolcontrol_file(&sc, filename))
3365 end_critical_section(S_NETCONFIGS);
3366 snprintf(errmsgbuf, n,
3367 "This mailing list only accepts posts from subscribers.");
3368 return (ERROR + NO_SUCH_USER);
3370 end_critical_section(S_NETCONFIGS);
3371 found = is_recipient (sc, RemoteIdentifier);
3372 free_spoolcontrol_struct(&sc);
3377 snprintf(errmsgbuf, n,
3378 "This mailing list only accepts posts from subscribers.");
3379 return (ERROR + NO_SUCH_USER);
3386 if ((CC->user.axlevel < 2)
3387 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3388 snprintf(errmsgbuf, n, "Need to be validated to enter "
3389 "(except in %s> to sysop)", MAILROOM);
3390 return (ERROR + HIGHER_ACCESS_REQUIRED);
3393 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3394 if (!(ra & UA_POSTALLOWED)) {
3395 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3396 return (ERROR + HIGHER_ACCESS_REQUIRED);
3399 strcpy(errmsgbuf, "Ok");
3405 * Check to see if the specified user has Internet mail permission
3406 * (returns nonzero if permission is granted)
3408 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3410 /* Do not allow twits to send Internet mail */
3411 if (who->axlevel <= 2) return(0);
3413 /* Globally enabled? */
3414 if (config.c_restrict == 0) return(1);
3416 /* User flagged ok? */
3417 if (who->flags & US_INTERNET) return(2);
3419 /* Aide level access? */
3420 if (who->axlevel >= 6) return(3);
3422 /* No mail for you! */
3428 * Validate recipients, count delivery types and errors, and handle aliasing
3429 * FIXME check for dupes!!!!!
3431 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3432 * were specified, or the number of addresses found invalid.
3434 * Caller needs to free the result using free_recipients()
3436 struct recptypes *validate_recipients(char *supplied_recipients,
3437 const char *RemoteIdentifier,
3439 struct recptypes *ret;
3440 char *recipients = NULL;
3441 char this_recp[256];
3442 char this_recp_cooked[256];
3448 struct ctdluser tempUS;
3449 struct ctdlroom tempQR;
3450 struct ctdlroom tempQR2;
3456 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3457 if (ret == NULL) return(NULL);
3459 /* Set all strings to null and numeric values to zero */
3460 memset(ret, 0, sizeof(struct recptypes));
3462 if (supplied_recipients == NULL) {
3463 recipients = strdup("");
3466 recipients = strdup(supplied_recipients);
3469 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3470 * actually need, but it's healthier for the heap than doing lots of tiny
3471 * realloc() calls instead.
3474 ret->errormsg = malloc(strlen(recipients) + 1024);
3475 ret->recp_local = malloc(strlen(recipients) + 1024);
3476 ret->recp_internet = malloc(strlen(recipients) + 1024);
3477 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3478 ret->recp_room = malloc(strlen(recipients) + 1024);
3479 ret->display_recp = malloc(strlen(recipients) + 1024);
3481 ret->errormsg[0] = 0;
3482 ret->recp_local[0] = 0;
3483 ret->recp_internet[0] = 0;
3484 ret->recp_ignet[0] = 0;
3485 ret->recp_room[0] = 0;
3486 ret->display_recp[0] = 0;
3488 ret->recptypes_magic = RECPTYPES_MAGIC;
3490 /* Change all valid separator characters to commas */
3491 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3492 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3493 recipients[i] = ',';
3497 /* Now start extracting recipients... */
3499 while (!IsEmptyStr(recipients)) {
3501 for (i=0; i<=strlen(recipients); ++i) {
3502 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3503 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3504 safestrncpy(this_recp, recipients, i+1);
3506 if (recipients[i] == ',') {
3507 strcpy(recipients, &recipients[i+1]);
3510 strcpy(recipients, "");
3517 if (IsEmptyStr(this_recp))
3519 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3521 mailtype = alias(this_recp);
3522 mailtype = alias(this_recp);
3523 mailtype = alias(this_recp);
3525 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3526 if (this_recp[j]=='_') {
3527 this_recp_cooked[j] = ' ';
3530 this_recp_cooked[j] = this_recp[j];
3533 this_recp_cooked[j] = '\0';
3538 if (!strcasecmp(this_recp, "sysop")) {
3540 strcpy(this_recp, config.c_aideroom);
3541 if (!IsEmptyStr(ret->recp_room)) {
3542 strcat(ret->recp_room, "|");
3544 strcat(ret->recp_room, this_recp);
3546 else if ( (!strncasecmp(this_recp, "room_", 5))
3547 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
3549 /* Save room so we can restore it later */
3553 /* Check permissions to send mail to this room */
3554 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3566 if (!IsEmptyStr(ret->recp_room)) {
3567 strcat(ret->recp_room, "|");
3569 strcat(ret->recp_room, &this_recp_cooked[5]);
3572 /* Restore room in case something needs it */
3576 else if (getuser(&tempUS, this_recp) == 0) {
3578 strcpy(this_recp, tempUS.fullname);
3579 if (!IsEmptyStr(ret->recp_local)) {
3580 strcat(ret->recp_local, "|");
3582 strcat(ret->recp_local, this_recp);
3584 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3586 strcpy(this_recp, tempUS.fullname);
3587 if (!IsEmptyStr(ret->recp_local)) {
3588 strcat(ret->recp_local, "|");
3590 strcat(ret->recp_local, this_recp);
3598 /* Yes, you're reading this correctly: if the target
3599 * domain points back to the local system or an attached
3600 * Citadel directory, the address is invalid. That's
3601 * because if the address were valid, we would have
3602 * already translated it to a local address by now.
3604 if (IsDirectory(this_recp, 0)) {
3609 ++ret->num_internet;
3610 if (!IsEmptyStr(ret->recp_internet)) {
3611 strcat(ret->recp_internet, "|");
3613 strcat(ret->recp_internet, this_recp);
3618 if (!IsEmptyStr(ret->recp_ignet)) {
3619 strcat(ret->recp_ignet, "|");
3621 strcat(ret->recp_ignet, this_recp);
3629 if (IsEmptyStr(errmsg)) {
3630 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3633 snprintf(append, sizeof append, "%s", errmsg);
3635 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3636 if (!IsEmptyStr(ret->errormsg)) {
3637 strcat(ret->errormsg, "; ");
3639 strcat(ret->errormsg, append);
3643 if (IsEmptyStr(ret->display_recp)) {
3644 strcpy(append, this_recp);
3647 snprintf(append, sizeof append, ", %s", this_recp);
3649 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3650 strcat(ret->display_recp, append);
3655 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3656 ret->num_room + ret->num_error) == 0) {
3657 ret->num_error = (-1);
3658 strcpy(ret->errormsg, "No recipients specified.");
3661 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3662 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3663 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3664 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3665 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3666 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3674 * Destructor for struct recptypes
3676 void free_recipients(struct recptypes *valid) {
3678 if (valid == NULL) {
3682 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3683 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3687 if (valid->errormsg != NULL) free(valid->errormsg);
3688 if (valid->recp_local != NULL) free(valid->recp_local);
3689 if (valid->recp_internet != NULL) free(valid->recp_internet);
3690 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3691 if (valid->recp_room != NULL) free(valid->recp_room);
3692 if (valid->display_recp != NULL) free(valid->display_recp);
3693 if (valid->bounce_to != NULL) free(valid->bounce_to);
3694 if (valid->envelope_from != NULL) free(valid->envelope_from);
3701 * message entry - mode 0 (normal)
3703 void cmd_ent0(char *entargs)
3709 char supplied_euid[128];
3711 int format_type = 0;
3712 char newusername[256];
3713 char newuseremail[256];
3714 struct CtdlMessage *msg;
3718 struct recptypes *valid = NULL;
3719 struct recptypes *valid_to = NULL;
3720 struct recptypes *valid_cc = NULL;
3721 struct recptypes *valid_bcc = NULL;
3723 int subject_required = 0;
3728 int newuseremail_ok = 0;
3729 char references[SIZ];
3734 post = extract_int(entargs, 0);
3735 extract_token(recp, entargs, 1, '|', sizeof recp);
3736 anon_flag = extract_int(entargs, 2);
3737 format_type = extract_int(entargs, 3);
3738 extract_token(subject, entargs, 4, '|', sizeof subject);
3739 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3740 do_confirm = extract_int(entargs, 6);
3741 extract_token(cc, entargs, 7, '|', sizeof cc);
3742 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3743 switch(CC->room.QRdefaultview) {
3746 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3749 supplied_euid[0] = 0;
3752 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3753 extract_token(references, entargs, 11, '|', sizeof references);
3754 for (ptr=references; *ptr != 0; ++ptr) {
3755 if (*ptr == '!') *ptr = '|';
3758 /* first check to make sure the request is valid. */
3760 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3763 cprintf("%d %s\n", err, errmsg);
3767 /* Check some other permission type things. */
3769 if (IsEmptyStr(newusername)) {
3770 strcpy(newusername, CC->user.fullname);
3772 if ( (CC->user.axlevel < 6)
3773 && (strcasecmp(newusername, CC->user.fullname))
3774 && (strcasecmp(newusername, CC->cs_inet_fn))
3776 cprintf("%d You don't have permission to author messages as '%s'.\n",
3777 ERROR + HIGHER_ACCESS_REQUIRED,
3784 if (IsEmptyStr(newuseremail)) {
3785 newuseremail_ok = 1;
3788 if (!IsEmptyStr(newuseremail)) {
3789 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3790 newuseremail_ok = 1;
3792 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3793 j = num_tokens(CC->cs_inet_other_emails, '|');
3794 for (i=0; i<j; ++i) {
3795 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3796 if (!strcasecmp(newuseremail, buf)) {
3797 newuseremail_ok = 1;
3803 if (!newuseremail_ok) {
3804 cprintf("%d You don't have permission to author messages as '%s'.\n",
3805 ERROR + HIGHER_ACCESS_REQUIRED,
3811 CC->cs_flags |= CS_POSTING;
3813 /* In mailbox rooms we have to behave a little differently --
3814 * make sure the user has specified at least one recipient. Then
3815 * validate the recipient(s). We do this for the Mail> room, as
3816 * well as any room which has the "Mailbox" view set.
3819 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3820 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3822 if (CC->user.axlevel < 2) {
3823 strcpy(recp, "sysop");
3828 valid_to = validate_recipients(recp, NULL, 0);
3829 if (valid_to->num_error > 0) {
3830 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3831 free_recipients(valid_to);
3835 valid_cc = validate_recipients(cc, NULL, 0);
3836 if (valid_cc->num_error > 0) {
3837 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3838 free_recipients(valid_to);
3839 free_recipients(valid_cc);
3843 valid_bcc = validate_recipients(bcc, NULL, 0);
3844 if (valid_bcc->num_error > 0) {
3845 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3846 free_recipients(valid_to);
3847 free_recipients(valid_cc);
3848 free_recipients(valid_bcc);
3852 /* Recipient required, but none were specified */
3853 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3854 free_recipients(valid_to);
3855 free_recipients(valid_cc);
3856 free_recipients(valid_bcc);
3857 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3861 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3862 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3863 cprintf("%d You do not have permission "
3864 "to send Internet mail.\n",
3865 ERROR + HIGHER_ACCESS_REQUIRED);
3866 free_recipients(valid_to);
3867 free_recipients(valid_cc);
3868 free_recipients(valid_bcc);
3873 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)
3874 && (CC->user.axlevel < 4) ) {
3875 cprintf("%d Higher access required for network mail.\n",
3876 ERROR + HIGHER_ACCESS_REQUIRED);
3877 free_recipients(valid_to);
3878 free_recipients(valid_cc);
3879 free_recipients(valid_bcc);
3883 if ((RESTRICT_INTERNET == 1)
3884 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3885 && ((CC->user.flags & US_INTERNET) == 0)
3886 && (!CC->internal_pgm)) {
3887 cprintf("%d You don't have access to Internet mail.\n",
3888 ERROR + HIGHER_ACCESS_REQUIRED);
3889 free_recipients(valid_to);
3890 free_recipients(valid_cc);
3891 free_recipients(valid_bcc);
3897 /* Is this a room which has anonymous-only or anonymous-option? */
3898 anonymous = MES_NORMAL;
3899 if (CC->room.QRflags & QR_ANONONLY) {
3900 anonymous = MES_ANONONLY;
3902 if (CC->room.QRflags & QR_ANONOPT) {
3903 if (anon_flag == 1) { /* only if the user requested it */
3904 anonymous = MES_ANONOPT;
3908 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3912 /* Recommend to the client that the use of a message subject is
3913 * strongly recommended in this room, if either the SUBJECTREQ flag
3914 * is set, or if there is one or more Internet email recipients.
3916 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3917 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3918 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3919 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3921 /* If we're only checking the validity of the request, return
3922 * success without creating the message.
3925 cprintf("%d %s|%d\n", CIT_OK,
3926 ((valid_to != NULL) ? valid_to->display_recp : ""),
3928 free_recipients(valid_to);
3929 free_recipients(valid_cc);
3930 free_recipients(valid_bcc);
3934 /* We don't need these anymore because we'll do it differently below */
3935 free_recipients(valid_to);
3936 free_recipients(valid_cc);
3937 free_recipients(valid_bcc);
3939 /* Read in the message from the client. */
3941 cprintf("%d send message\n", START_CHAT_MODE);
3943 cprintf("%d send message\n", SEND_LISTING);
3946 msg = CtdlMakeMessage(&CC->user, recp, cc,
3947 CC->room.QRname, anonymous, format_type,
3948 newusername, newuseremail, subject,
3949 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
3952 /* Put together one big recipients struct containing to/cc/bcc all in
3953 * one. This is for the envelope.
3955 char *all_recps = malloc(SIZ * 3);
3956 strcpy(all_recps, recp);
3957 if (!IsEmptyStr(cc)) {
3958 if (!IsEmptyStr(all_recps)) {
3959 strcat(all_recps, ",");
3961 strcat(all_recps, cc);
3963 if (!IsEmptyStr(bcc)) {
3964 if (!IsEmptyStr(all_recps)) {
3965 strcat(all_recps, ",");
3967 strcat(all_recps, bcc);
3969 if (!IsEmptyStr(all_recps)) {
3970 valid = validate_recipients(all_recps, NULL, 0);
3978 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
3981 cprintf("%ld\n", msgnum);
3983 cprintf("Message accepted.\n");
3986 cprintf("Internal error.\n");
3988 if (msg->cm_fields['E'] != NULL) {
3989 cprintf("%s\n", msg->cm_fields['E']);
3996 CtdlFreeMessage(msg);
3998 if (valid != NULL) {
3999 free_recipients(valid);
4007 * API function to delete messages which match a set of criteria
4008 * (returns the actual number of messages deleted)
4010 int CtdlDeleteMessages(char *room_name, /* which room */
4011 long *dmsgnums, /* array of msg numbers to be deleted */
4012 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4013 char *content_type /* or "" for any. regular expressions expected. */
4016 struct ctdlroom qrbuf;
4017 struct cdbdata *cdbfr;
4018 long *msglist = NULL;
4019 long *dellist = NULL;
4022 int num_deleted = 0;
4024 struct MetaData smi;
4027 int need_to_free_re = 0;
4029 if (content_type) if (!IsEmptyStr(content_type)) {
4030 regcomp(&re, content_type, 0);
4031 need_to_free_re = 1;
4033 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4034 room_name, num_dmsgnums, content_type);
4036 /* get room record, obtaining a lock... */
4037 if (lgetroom(&qrbuf, room_name) != 0) {
4038 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4040 if (need_to_free_re) regfree(&re);
4041 return (0); /* room not found */
4043 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4045 if (cdbfr != NULL) {
4046 dellist = malloc(cdbfr->len);
4047 msglist = (long *) cdbfr->ptr;
4048 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4049 num_msgs = cdbfr->len / sizeof(long);
4053 for (i = 0; i < num_msgs; ++i) {
4056 /* Set/clear a bit for each criterion */
4058 /* 0 messages in the list or a null list means that we are
4059 * interested in deleting any messages which meet the other criteria.
4061 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4062 delete_this |= 0x01;
4065 for (j=0; j<num_dmsgnums; ++j) {
4066 if (msglist[i] == dmsgnums[j]) {
4067 delete_this |= 0x01;
4072 if (IsEmptyStr(content_type)) {
4073 delete_this |= 0x02;
4075 GetMetaData(&smi, msglist[i]);
4076 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4077 delete_this |= 0x02;
4081 /* Delete message only if all bits are set */
4082 if (delete_this == 0x03) {
4083 dellist[num_deleted++] = msglist[i];
4088 num_msgs = sort_msglist(msglist, num_msgs);
4089 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4090 msglist, (int)(num_msgs * sizeof(long)));
4092 qrbuf.QRhighest = msglist[num_msgs - 1];
4096 /* Go through the messages we pulled out of the index, and decrement
4097 * their reference counts by 1. If this is the only room the message
4098 * was in, the reference count will reach zero and the message will
4099 * automatically be deleted from the database. We do this in a
4100 * separate pass because there might be plug-in hooks getting called,
4101 * and we don't want that happening during an S_ROOMS critical
4104 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4105 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4106 AdjRefCount(dellist[i], -1);
4109 /* Now free the memory we used, and go away. */
4110 if (msglist != NULL) free(msglist);
4111 if (dellist != NULL) free(dellist);
4112 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4113 if (need_to_free_re) regfree(&re);
4114 return (num_deleted);
4120 * Check whether the current user has permission to delete messages from
4121 * the current room (returns 1 for yes, 0 for no)
4123 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4125 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4126 if (ra & UA_DELETEALLOWED) return(1);
4134 * Delete message from current room
4136 void cmd_dele(char *args)
4145 extract_token(msgset, args, 0, '|', sizeof msgset);
4146 num_msgs = num_tokens(msgset, ',');
4148 cprintf("%d Nothing to do.\n", CIT_OK);
4152 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4153 cprintf("%d Higher access required.\n",
4154 ERROR + HIGHER_ACCESS_REQUIRED);
4159 * Build our message set to be moved/copied
4161 msgs = malloc(num_msgs * sizeof(long));
4162 for (i=0; i<num_msgs; ++i) {
4163 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4164 msgs[i] = atol(msgtok);
4167 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4171 cprintf("%d %d message%s deleted.\n", CIT_OK,
4172 num_deleted, ((num_deleted != 1) ? "s" : ""));
4174 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4180 * Back end API function for moves and deletes (multiple messages)
4182 int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) {
4185 err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL);
4186 if (err != 0) return(err);
4195 * move or copy a message to another room
4197 void cmd_move(char *args)
4204 char targ[ROOMNAMELEN];
4205 struct ctdlroom qtemp;
4212 extract_token(msgset, args, 0, '|', sizeof msgset);
4213 num_msgs = num_tokens(msgset, ',');
4215 cprintf("%d Nothing to do.\n", CIT_OK);
4219 extract_token(targ, args, 1, '|', sizeof targ);
4220 convert_room_name_macros(targ, sizeof targ);
4221 targ[ROOMNAMELEN - 1] = 0;
4222 is_copy = extract_int(args, 2);
4224 if (getroom(&qtemp, targ) != 0) {
4225 cprintf("%d '%s' does not exist.\n",
4226 ERROR + ROOM_NOT_FOUND, targ);
4230 getuser(&CC->user, CC->curr_user);
4231 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4233 /* Check for permission to perform this operation.
4234 * Remember: "CC->room" is source, "qtemp" is target.
4238 /* Aides can move/copy */
4239 if (CC->user.axlevel >= 6) permit = 1;
4241 /* Room aides can move/copy */
4242 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4244 /* Permit move/copy from personal rooms */
4245 if ((CC->room.QRflags & QR_MAILBOX)
4246 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4248 /* Permit only copy from public to personal room */
4250 && (!(CC->room.QRflags & QR_MAILBOX))
4251 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4253 /* Permit message removal from collaborative delete rooms */
4254 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4256 /* Users allowed to post into the target room may move into it too. */
4257 if ((CC->room.QRflags & QR_MAILBOX) &&
4258 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4260 /* User must have access to target room */
4261 if (!(ra & UA_KNOWN)) permit = 0;
4264 cprintf("%d Higher access required.\n",
4265 ERROR + HIGHER_ACCESS_REQUIRED);
4270 * Build our message set to be moved/copied
4272 msgs = malloc(num_msgs * sizeof(long));
4273 for (i=0; i<num_msgs; ++i) {
4274 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4275 msgs[i] = atol(msgtok);
4281 err = CtdlCopyMsgsToRoom(msgs, num_msgs, targ);
4283 cprintf("%d Cannot store message(s) in %s: error %d\n",
4289 /* Now delete the message from the source room,
4290 * if this is a 'move' rather than a 'copy' operation.
4293 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4297 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4303 * GetMetaData() - Get the supplementary record for a message
4305 void GetMetaData(struct MetaData *smibuf, long msgnum)
4308 struct cdbdata *cdbsmi;
4311 memset(smibuf, 0, sizeof(struct MetaData));
4312 smibuf->meta_msgnum = msgnum;
4313 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4315 /* Use the negative of the message number for its supp record index */
4316 TheIndex = (0L - msgnum);
4318 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4319 if (cdbsmi == NULL) {
4320 return; /* record not found; go with defaults */
4322 memcpy(smibuf, cdbsmi->ptr,
4323 ((cdbsmi->len > sizeof(struct MetaData)) ?
4324 sizeof(struct MetaData) : cdbsmi->len));
4331 * PutMetaData() - (re)write supplementary record for a message
4333 void PutMetaData(struct MetaData *smibuf)
4337 /* Use the negative of the message number for the metadata db index */
4338 TheIndex = (0L - smibuf->meta_msgnum);
4340 cdb_store(CDB_MSGMAIN,
4341 &TheIndex, (int)sizeof(long),
4342 smibuf, (int)sizeof(struct MetaData));
4347 * AdjRefCount - submit an adjustment to the reference count for a message.
4348 * (These are just queued -- we actually process them later.)
4350 void AdjRefCount(long msgnum, int incr)
4352 struct arcq new_arcq;
4354 begin_critical_section(S_SUPPMSGMAIN);
4355 if (arcfp == NULL) {
4356 arcfp = fopen(file_arcq, "ab+");
4358 end_critical_section(S_SUPPMSGMAIN);
4360 /* msgnum < 0 means that we're trying to close the file */
4362 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4363 begin_critical_section(S_SUPPMSGMAIN);
4364 if (arcfp != NULL) {
4368 end_critical_section(S_SUPPMSGMAIN);
4373 * If we can't open the queue, perform the operation synchronously.
4375 if (arcfp == NULL) {
4376 TDAP_AdjRefCount(msgnum, incr);
4380 new_arcq.arcq_msgnum = msgnum;
4381 new_arcq.arcq_delta = incr;
4382 fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4390 * TDAP_ProcessAdjRefCountQueue()
4392 * Process the queue of message count adjustments that was created by calls
4393 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4394 * for each one. This should be an "off hours" operation.
4396 int TDAP_ProcessAdjRefCountQueue(void)
4398 char file_arcq_temp[PATH_MAX];
4401 struct arcq arcq_rec;
4402 int num_records_processed = 0;
4404 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4406 begin_critical_section(S_SUPPMSGMAIN);
4407 if (arcfp != NULL) {
4412 r = link(file_arcq, file_arcq_temp);
4414 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4415 end_critical_section(S_SUPPMSGMAIN);
4416 return(num_records_processed);
4420 end_critical_section(S_SUPPMSGMAIN);
4422 fp = fopen(file_arcq_temp, "rb");
4424 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4425 return(num_records_processed);
4428 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4429 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4430 ++num_records_processed;
4434 r = unlink(file_arcq_temp);
4436 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4439 return(num_records_processed);
4445 * TDAP_AdjRefCount - adjust the reference count for a message.
4446 * This one does it "for real" because it's called by
4447 * the autopurger function that processes the queue
4448 * created by AdjRefCount(). If a message's reference
4449 * count becomes zero, we also delete the message from
4450 * disk and de-index it.
4452 void TDAP_AdjRefCount(long msgnum, int incr)
4455 struct MetaData smi;
4458 /* This is a *tight* critical section; please keep it that way, as
4459 * it may get called while nested in other critical sections.
4460 * Complicating this any further will surely cause deadlock!
4462 begin_critical_section(S_SUPPMSGMAIN);
4463 GetMetaData(&smi, msgnum);
4464 smi.meta_refcount += incr;
4466 end_critical_section(S_SUPPMSGMAIN);
4467 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4468 msgnum, incr, smi.meta_refcount);
4470 /* If the reference count is now zero, delete the message
4471 * (and its supplementary record as well).
4473 if (smi.meta_refcount == 0) {
4474 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4476 /* Call delete hooks with NULL room to show it has gone altogether */
4477 PerformDeleteHooks(NULL, msgnum);
4479 /* Remove from message base */
4481 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4482 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4484 /* Remove metadata record */
4485 delnum = (0L - msgnum);
4486 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4492 * Write a generic object to this room
4494 * Note: this could be much more efficient. Right now we use two temporary
4495 * files, and still pull the message into memory as with all others.
4497 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4498 char *content_type, /* MIME type of this object */
4499 char *raw_message, /* Data to be written */
4500 off_t raw_length, /* Size of raw_message */
4501 struct ctdluser *is_mailbox, /* Mailbox room? */
4502 int is_binary, /* Is encoding necessary? */
4503 int is_unique, /* Del others of this type? */
4504 unsigned int flags /* Internal save flags */
4508 struct ctdlroom qrbuf;
4509 char roomname[ROOMNAMELEN];
4510 struct CtdlMessage *msg;
4511 char *encoded_message = NULL;
4513 if (is_mailbox != NULL) {
4514 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4517 safestrncpy(roomname, req_room, sizeof(roomname));
4520 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4523 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4526 encoded_message = malloc((size_t)(raw_length + 4096));
4529 sprintf(encoded_message, "Content-type: %s\n", content_type);
4532 sprintf(&encoded_message[strlen(encoded_message)],
4533 "Content-transfer-encoding: base64\n\n"
4537 sprintf(&encoded_message[strlen(encoded_message)],
4538 "Content-transfer-encoding: 7bit\n\n"
4544 &encoded_message[strlen(encoded_message)],
4552 &encoded_message[strlen(encoded_message)],
4558 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4559 msg = malloc(sizeof(struct CtdlMessage));
4560 memset(msg, 0, sizeof(struct CtdlMessage));
4561 msg->cm_magic = CTDLMESSAGE_MAGIC;
4562 msg->cm_anon_type = MES_NORMAL;
4563 msg->cm_format_type = 4;
4564 msg->cm_fields['A'] = strdup(CC->user.fullname);
4565 msg->cm_fields['O'] = strdup(req_room);
4566 msg->cm_fields['N'] = strdup(config.c_nodename);
4567 msg->cm_fields['H'] = strdup(config.c_humannode);
4568 msg->cm_flags = flags;
4570 msg->cm_fields['M'] = encoded_message;
4572 /* Create the requested room if we have to. */
4573 if (getroom(&qrbuf, roomname) != 0) {
4574 create_room(roomname,
4575 ( (is_mailbox != NULL) ? 5 : 3 ),
4576 "", 0, 1, 0, VIEW_BBS);
4578 /* If the caller specified this object as unique, delete all
4579 * other objects of this type that are currently in the room.
4582 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4583 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4586 /* Now write the data */
4587 CtdlSubmitMsg(msg, NULL, roomname, 0);
4588 CtdlFreeMessage(msg);
4596 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4597 config_msgnum = msgnum;
4601 char *CtdlGetSysConfig(char *sysconfname) {
4602 char hold_rm[ROOMNAMELEN];
4605 struct CtdlMessage *msg;
4608 strcpy(hold_rm, CC->room.QRname);
4609 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
4610 getroom(&CC->room, hold_rm);
4615 /* We want the last (and probably only) config in this room */
4616 begin_critical_section(S_CONFIG);
4617 config_msgnum = (-1L);
4618 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4619 CtdlGetSysConfigBackend, NULL);
4620 msgnum = config_msgnum;
4621 end_critical_section(S_CONFIG);
4627 msg = CtdlFetchMessage(msgnum, 1);
4629 conf = strdup(msg->cm_fields['M']);
4630 CtdlFreeMessage(msg);
4637 getroom(&CC->room, hold_rm);
4639 if (conf != NULL) do {
4640 extract_token(buf, conf, 0, '\n', sizeof buf);
4641 strcpy(conf, &conf[strlen(buf)+1]);
4642 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4648 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4649 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4654 * Determine whether a given Internet address belongs to the current user
4656 int CtdlIsMe(char *addr, int addr_buf_len)
4658 struct recptypes *recp;
4661 recp = validate_recipients(addr, NULL, 0);
4662 if (recp == NULL) return(0);
4664 if (recp->num_local == 0) {
4665 free_recipients(recp);
4669 for (i=0; i<recp->num_local; ++i) {
4670 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4671 if (!strcasecmp(addr, CC->user.fullname)) {
4672 free_recipients(recp);
4677 free_recipients(recp);
4683 * Citadel protocol command to do the same
4685 void cmd_isme(char *argbuf) {
4688 if (CtdlAccessCheck(ac_logged_in)) return;
4689 extract_token(addr, argbuf, 0, '|', sizeof addr);
4691 if (CtdlIsMe(addr, sizeof addr)) {
4692 cprintf("%d %s\n", CIT_OK, addr);
4695 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4701 /*****************************************************************************/
4702 /* MODULE INITIALIZATION STUFF */
4703 /*****************************************************************************/
4705 CTDL_MODULE_INIT(msgbase)
4707 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4708 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4709 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4710 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4711 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4712 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4713 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4714 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4715 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4716 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4717 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4718 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4720 /* return our Subversion id for the Log */