Hello, Attached is patch that allows cyrus to ask another program about return path of a mail that is going to be redirected by sieve.
Motivation: ----------- Redirecting emails by sieve can cause problems in case sender's domain has SPF record. Cyrus doesn't change the return path, so for the final recipient (redirect destination) an SPF check will find conflict between return path and sender's (not the original one) IP address/hostname. In order to avoid this type of problems, attached patch allows cyrus to ask another program about return path. I choose solution with another program because in different systems appropriate return path can be constructed in different ways: * The simplest solution is to use as a return path recipient's email address. In this way the SPF check is OK, but in case of email bounced on redirect destination it won't go back to the original sender; * More complex solution is to construct return path that will allow either MTA or another system to rewrite headers and to forward bounced message to the original sender. MTA can utilize components like https://github.com/roehling/postsrsd. Alternatively all the return paths can be from dedicated domain/subdomain, configured to catch all emails in single mailbox and a script that reads this mailbox and forwards bounced messages. In both ways the return path should contain some sort of security token that will prevent using the system as open relay. In our system the return path consists of original sender e-mail address, recipient's e-mail address(not username), truncated part of the subject, timestamp and a token thus allowing us, in case of bounced e-mail, to send to the original sender bilingual, easy to understand message with technical details attached. Proposed patch is against version 2.5.10 and is backward compatible. It introduces two configuration parameters: * sieve_redirect_get_return_path - the path to the program, responsible for return path generation; * sieve_redirect_return_path_details - switch - whether to send to return path generator the e-mail content or not. Content can be used to extract some additional data - subject, etc. Please note that config2header must be executed after changes in imapoptions file. Here is a perl template for a program that will communicate with cyrus and will generate return path: --------------------------- #! /usr/bin/perl use strict; use utf8; ### command line options # -s the sender email address # -u the recipient username (could be different from e-mail address) # -d redirect destination email address # # then the whole message comes through STDIN (optional), can be used to extract # some data - subject, from header, etc. ### my (%args, $sender_email, $recipient_email, $redirect_destination, $msg_text, $line, $return_path); # read command line parameters %args = @ARGV; $sender_email = $args{'-s'}; $recipient_email = $args{'-u'}; $redirect_destination = $args{'-d'}; while($line = <STDIN> ) { $line =~ s/\0//; $msg_text .= $line; } ### # # Do what is necessary to construct return path # based on parameters and message (optional). The all # Return-path header must be shorter than 255 ASCII characters # ### print STDOUT $return_path; exit(0); --------------------------- I hope it will be useful for other cyrus users since SPF is very popular part of anti-spam systems. Best regards, Atanas Karashenski BlueBoard LLC
diff -Naur cyrus-imapd-2.5.10/imap/lmtp_sieve.c cyrus-imapd-2.5.10_sieve/imap/lmtp_sieve.c --- cyrus-imapd-2.5.10/imap/lmtp_sieve.c 2016-10-18 00:01:04.000000000 +0300 +++ cyrus-imapd-2.5.10_sieve/imap/lmtp_sieve.c 2017-07-05 11:13:04.369448747 +0300 @@ -366,6 +366,93 @@ return sm_stat; /* sendmail exit value */ } +static char * sieve_get_return_path(message_data_t *m, + script_data_t *sd, + sieve_redirect_context_t *rc) +{ + int parent_pipe[2]; + int child_pipe[2]; + int rv; + const char *argv[8]; + char buf[1024]; + + struct protstream *file = m->data; + char *return_path = m->return_path; + char *sender_email = m->return_path; + char *recipient_username = sd->username; + char *destination_email = rc->addr; + + char *cmd = config_getstring(IMAPOPT_SIEVE_REDIRECT_GET_RETURN_PATH); + + if(cmd != NULL) { + + if(pipe(parent_pipe) || pipe(child_pipe)) { + syslog(LOG_ERR, "Can't open pipe to determine redirect return path %m"); + exit(1); + } + + int pid = fork(); + if (pid == -1) { + syslog(LOG_ERR, "Can't fork to determine redirect return path %m"); + exit(1); + } + + if (pid == 0) { + // this is the child process. + int in, out; + in = child_pipe[0]; + out = parent_pipe[1]; + + dup2(out, STDOUT_FILENO); + dup2(in, STDIN_FILENO); + + argv[0] = "sieve_redirect_get_return_path"; + + argv[1] = "-s"; /* the sender email address*/ + argv[2] = sender_email; + + argv[3] = "-u"; /* the recipient username */ + argv[4] = recipient_username; + + argv[5] = "-d"; /* redirect destination */ + argv[6] = destination_email; + argv[7] = NULL; + + close(child_pipe[1]); + close(child_pipe[0]); + close(parent_pipe[0]); + close(parent_pipe[1]); + + execv(cmd, (char **) argv); + } else { + // this is the parent process + int in, out, i; + in = parent_pipe[0]; + out = child_pipe[1]; + + if(config_getswitch(IMAPOPT_SIEVE_REDIRECT_RETURN_PATH_DETAILS)) { + prot_rewind(file); + while (prot_fgets(buf, sizeof(buf), file)) { + write(out, buf, strlen(buf) + 1); + } + } + + close(child_pipe[1]); + + const ssize_t l = read(in, return_path, 1024); + return_path[l] = '\0'; + + close(parent_pipe[0]); + close(parent_pipe[1]); + close(child_pipe[0]); + + wait(&rv); + } + } + + return return_path; +} + static int sieve_redirect(void *ac, void *ic __attribute__((unused)), @@ -377,6 +464,7 @@ char buf[8192], *sievedb = NULL; duplicate_key_t dkey = DUPLICATE_INITIALIZER; int res; + char *return_path; /* if we have a msgid, we can track our redirects */ if (m->id) { @@ -393,7 +481,16 @@ } } - if ((res = send_forward(rc->addr, m->return_path, m->data)) == 0) { + /* if sieve redirect is forward the following will be used to determine return path + m->return_path -> sender return path + sd->username -> recipient username + rc->addr -> where to redirect + m->data -> email content + */ + + return_path = sieve_get_return_path(m, sd, rc); + + if ((res = send_forward(rc->addr, return_path, m->data)) == 0) { /* mark this message as redirected */ if (sievedb) duplicate_mark(&dkey, time(NULL), 0); @@ -987,3 +1084,4 @@ return IMAP_MAILBOX_NONEXISTENT; } + diff -Naur cyrus-imapd-2.5.10/lib/imapoptions cyrus-imapd-2.5.10_sieve/lib/imapoptions --- cyrus-imapd-2.5.10/lib/imapoptions 2016-10-18 00:01:04.000000000 +0300 +++ cyrus-imapd-2.5.10_sieve/lib/imapoptions 2017-07-01 13:05:19.212152977 +0300 @@ -2001,6 +2001,15 @@ /* The absolute path to the zoneinfo db file. If not specified, will be confdir/zoneinfo.db */ +{ "sieve_redirect_get_return_path", NULL, STRING } +/* The absolute path to the program that constructs return path + for sieve redirect. */ + +{ "sieve_redirect_return_path_details", 0, SWITCH } +/* Whether to send message content to the program responsible for + generation of return path that will pass SPF checks. */ + + /* .SH SEE ALSO .PP