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:
@@ -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
|
||||
|
||||
@@ -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, " "))
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user