git » chasquid » commit 1cc7b9a

smtpsrv: Pass EHLO/HELO domain to the post-data hook

author Alberto Bertogli
2020-09-14 20:17:32 UTC
committer Alberto Bertogli
2020-09-17 00:29:49 UTC
parent 5bebb00af9b6ac301acfdc79a16d314842c3df3f

smtpsrv: Pass EHLO/HELO domain to the post-data hook

Some utilities might want to access the EHLO/HELO domain in the
post-data hook (for example, to do additional SPF validations).

This patch implements that support, including sanitizing the EHLO domain
on the environment variable to reduce the risk of problems.

docs/hooks.md +4 -0
internal/smtpsrv/conn.go +27 -0
internal/smtpsrv/conn_test.go +30 -0
test/t-10-hooks/run.sh +2 -0

diff --git a/docs/hooks.md b/docs/hooks.md
index 7631e29..cc469bc 100644
--- a/docs/hooks.md
+++ b/docs/hooks.md
@@ -41,6 +41,10 @@ The environment will contain the following variables:
  - `$PATH`: The server's `$PATH` env variable.
  - `$PWD`: The working directory, which will be the config directory.
  - `$REMOTE_ADDR`: IP address of the remote side of the connection.
+ - `$EHLO_DOMAIN`: EHLO/HELO domain, as given by the client; sanitized for
+   safety.
+ - `$EHLO_DOMAIN_RAW`: Same as `$EHLO_DOMAIN`, but not sanitized; be careful as
+   it can contain problematic characters.
  - `$MAIL_FROM`: MAIL FROM address.
  - `$RCPT_TO`: RCPT TO addresses, space separated.
  - `$AUTH_AS`: Authenticated user; empty if the connection has not
diff --git a/internal/smtpsrv/conn.go b/internal/smtpsrv/conn.go
index 891d98b..4ad7617 100644
--- a/internal/smtpsrv/conn.go
+++ b/internal/smtpsrv/conn.go
@@ -767,6 +767,31 @@ func checkData(data []byte) error {
 	return nil
 }
 
+// Sanitize HELO/EHLO domain.
+// RFC is extremely flexible with EHLO domain values, allowing all printable
+// ASCII characters. They can be tricky to use in shell scripts (commonly used
+// as post-data hooks), so this function sanitizes the value to make it
+// shell-safe.
+func sanitizeEHLODomain(s string) string {
+	n := ""
+	for _, c := range s {
+		// Allow a-zA-Z0-9 and []-.:
+		// That's enough for all domains, IPv4 and IPv6 literals, and also
+		// shell-safe.
+		// Non-ASCII are forbidden as EHLO domains per RFC.
+		switch {
+		case c >= 'a' && c <= 'z',
+			c >= 'A' && c <= 'Z',
+			c >= '0' && c <= '9',
+			c == '-', c == '.',
+			c == '[', c == ']', c == ':':
+			n += string(c)
+		}
+	}
+
+	return n
+}
+
 // runPostDataHook and return the new headers to add, and on error a boolean
 // indicating if it's permanent, and the error itself.
 func (c *Conn) runPostDataHook(data []byte) ([]byte, bool, error) {
@@ -789,6 +814,8 @@ func (c *Conn) runPostDataHook(data []byte) ([]byte, bool, error) {
 		cmd.Env = append(cmd.Env, v+"="+os.Getenv(v))
 	}
 	cmd.Env = append(cmd.Env, "REMOTE_ADDR="+c.conn.RemoteAddr().String())
+	cmd.Env = append(cmd.Env, "EHLO_DOMAIN="+sanitizeEHLODomain(c.ehloDomain))
+	cmd.Env = append(cmd.Env, "EHLO_DOMAIN_RAW="+c.ehloDomain)
 	cmd.Env = append(cmd.Env, "MAIL_FROM="+c.mailFrom)
 	cmd.Env = append(cmd.Env, "RCPT_TO="+strings.Join(c.rcptTo, " "))
 
diff --git a/internal/smtpsrv/conn_test.go b/internal/smtpsrv/conn_test.go
index 412dd73..415d799 100644
--- a/internal/smtpsrv/conn_test.go
+++ b/internal/smtpsrv/conn_test.go
@@ -169,3 +169,33 @@ func TestAddrLiteral(t *testing.T) {
 		}
 	}
 }
+
+func TestSanitizeEHLODomain(t *testing.T) {
+	equal := []string{
+		"domain", "do.main", "do-main",
+		"1.2.3.4", "a:b:c", "[a:b:c]",
+		"abz", "AbZ",
+	}
+	for _, str := range equal {
+		if got := sanitizeEHLODomain(str); got != str {
+			t.Errorf("sanitizeEHLODomain(%q) returned %q, expected %q",
+				str, got, str)
+		}
+	}
+
+	invalid := []struct {
+		str      string
+		expected string
+	}{
+		{"├▒aca", "aca"}, {"a\nb", "ab"}, {"a\x00b", "ab"}, {"a\x7fb", "ab"},
+		{"a/z", "az"}, {"a;b", "ab"}, {"a$b", "ab"}, {"a^b", "ab"},
+		{"a b", "ab"}, {"a+b", "ab"}, {"a@b", "ab"}, {`a"b`, "ab"},
+		{`a\b`, "ab"},
+	}
+	for _, c := range invalid {
+		if got := sanitizeEHLODomain(c.str); got != c.expected {
+			t.Errorf("sanitizeEHLODomain(%q) returned %q, expected %q",
+				c.str, got, c.expected)
+		}
+	}
+}
diff --git a/test/t-10-hooks/run.sh b/test/t-10-hooks/run.sh
index 8e41301..490b225 100755
--- a/test/t-10-hooks/run.sh
+++ b/test/t-10-hooks/run.sh
@@ -38,6 +38,8 @@ check "RCPT_TO=someone@testserver"
 check "MAIL_FROM=user@testserver"
 check "USER=$USER"
 check "PWD=$PWD/config"
+check "EHLO_DOMAIN=localhost"
+check "EHLO_DOMAIN_RAW=localhost"
 check "FROM_LOCAL_DOMAIN=1"
 check "ON_TLS=1"
 check "AUTH_AS=user@testserver"