diff --git a/helpers/inbound/inbound.go b/helpers/inbound/inbound.go index ba7f0b89..9a298ada 100644 --- a/helpers/inbound/inbound.go +++ b/helpers/inbound/inbound.go @@ -233,10 +233,37 @@ func (email *ParsedEmail) parseHeaders(headers string) { } } +// ValidateConfig adds configuration options for validating a parsed email. Specifically turning DKIM as optional. DKIM validates emails in transit from tampering. However many valid email clients +// do not have it configured +// SPF could potentially be added but it poses a more significant security risk +type ValidateConfig struct { + dkimOptional bool +} + +// NewValidateConfig returns a configuration struct for validating the parsed email. Specifically turning DKIM as optional. DKIM validates emails in transit from tampering. However many valid email clients +// do not have it configured +func NewValidateConfig() ValidateConfig { + return ValidateConfig{} +} + +// WithDKIMOptional validates DKIM values only if they exist. This allows the inbound client to support more email clients that might not support this configuration +func (vc ValidateConfig) WithDKIMOptional() ValidateConfig { + vc.dkimOptional = true + return vc +} + // Validate validates the DKIM and SPF scores to ensure that the email client and address was not spoofed -func (email *ParsedEmail) Validate() error { - if len(email.rawValues["dkim"]) == 0 || len(email.rawValues["SPF"]) == 0 { - return fmt.Errorf("missing DKIM and SPF score") +func (email *ParsedEmail) Validate(config ...ValidateConfig) error { + var dkimOptional bool + if len(config) > 0 { + dkimOptional = config[0].dkimOptional + } + if len(email.rawValues["SPF"]) == 0 { + return fmt.Errorf("missing SPF score") + } + + if !dkimOptional && len(email.rawValues["dkim"]) == 0 { + return fmt.Errorf("missing DKIM score") } for _, val := range email.rawValues["dkim"] { diff --git a/helpers/inbound/inbound_test.go b/helpers/inbound/inbound_test.go index 4dac61ab..b6af43a6 100644 --- a/helpers/inbound/inbound_test.go +++ b/helpers/inbound/inbound_test.go @@ -136,13 +136,18 @@ func TestValidate(t *testing.T) { name string values map[string][]string expectedError error + config ValidateConfig }{ { - name: "MissingHeaders", + name: "MissingSPFHeaders", values: map[string][]string{}, - expectedError: fmt.Errorf("missing DKIM and SPF score"), + expectedError: fmt.Errorf("missing SPF score"), }, { + name: "MissingDKIMHeaders", + values: map[string][]string{"SPF": {"pass"}}, + expectedError: fmt.Errorf("missing DKIM score"), + }, { name: "FailedDkim", values: map[string][]string{"dkim": {"pass", "fail", "pass"}, "SPF": {"pass"}}, expectedError: fmt.Errorf("DKIM validation failed"), @@ -161,13 +166,29 @@ func TestValidate(t *testing.T) { name: "success", values: map[string][]string{"dkim": {"pass", "pass", "pass"}, "SPF": {"pass", "pass", "pass"}}, }, + { + name: "dkimOptionalAndPresent", + values: map[string][]string{"dkim": {"pass", "pass", "pass"}, "SPF": {"pass", "pass", "pass"}}, + config: NewValidateConfig().WithDKIMOptional(), + }, + { + name: "dkimOptionalWithFailure", + values: map[string][]string{"dkim": {"fail", "pass", "pass"}, "SPF": {"pass", "pass", "pass"}}, + config: NewValidateConfig().WithDKIMOptional(), + expectedError: fmt.Errorf("DKIM validation failed"), + }, + { + name: "dkimOptionalAndAbsent", + values: map[string][]string{"SPF": {"pass", "pass", "pass"}}, + config: NewValidateConfig().WithDKIMOptional(), + }, } for _, test := range tests { t.Run(test.name, func(subTest *testing.T) { //Load POST body email := ParsedEmail{rawValues: test.values} - err := email.Validate() + err := email.Validate(test.config) if test.expectedError != nil { assert.EqualError(subTest, test.expectedError, err.Error())