diff --git a/backend/model/smtpConfiguration.go b/backend/model/smtpConfiguration.go index 794de5d6..5afb8d0f 100644 --- a/backend/model/smtpConfiguration.go +++ b/backend/model/smtpConfiguration.go @@ -46,6 +46,15 @@ func (s *SMTPConfiguration) Validate() error { if err := validate.NullableFieldRequired("ignoreCertErrors", s.IgnoreCertErrors); err != nil { return err } + // helo is sent as a line in the SMTP dialog, so a control character such as + // a newline must never pass into it + if s.Helo.IsSpecified() { + if helo, err := s.Helo.Get(); err == nil { + if err := validate.ErrorIfContainsControlChars("helo", helo.String()); err != nil { + return err + } + } + } return nil } diff --git a/backend/validate/validate.go b/backend/validate/validate.go index 33cfb3ea..e734941f 100644 --- a/backend/validate/validate.go +++ b/backend/validate/validate.go @@ -11,6 +11,7 @@ import ( "slices" "strings" "time" + "unicode" "github.com/go-errors/errors" @@ -278,6 +279,19 @@ func ErrorIfNotAlphaNumeric(s string) error { ) } +// ErrorIfContainsControlChars rejects a string that holds any control +// character such as a carriage return, line feed or tab. It guards fields that +// are placed into a line based protocol like the SMTP dialog, where an +// embedded newline would let a value inject a second command or header. +func ErrorIfContainsControlChars(field string, s string) error { + if strings.IndexFunc(s, unicode.IsControl) < 0 { + return nil + } + return errs.NewValidationError( + fmt.Errorf("%s must not contain control characters", field), + ) +} + // IsValidEmail checks if a string is a valid email func ErrorIfMailInvalid(s string) error { const min = 5