1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00

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.
This commit is contained in:
Alberto Bertogli
2020-09-14 21:17:32 +01:00
parent 5bebb00af9
commit 1cc7b9a864
4 changed files with 63 additions and 0 deletions

View File

@@ -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

View File

@@ -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, " "))

View File

@@ -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)
}
}
}

View File

@@ -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"