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

Reply via email to