diff --git a/go.mod b/go.mod index aad4860..aea85df 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( gitlab.com/etke.cc/go/mxidwc v1.0.0 gitlab.com/etke.cc/go/psd v1.1.2 gitlab.com/etke.cc/go/secgen v1.2.0 - gitlab.com/etke.cc/go/validator v1.0.7 + gitlab.com/etke.cc/go/validator/v2 v2.2.0 gitlab.com/etke.cc/linkpearl v0.0.0-20240716084747-f2a547f02d54 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 maunium.net/go/mautrix v0.19.0 diff --git a/go.sum b/go.sum index 5dd49dc..a7b6579 100644 --- a/go.sum +++ b/go.sum @@ -116,8 +116,8 @@ gitlab.com/etke.cc/go/secgen v1.2.0 h1:qpV7rUn5Rs6eWxAmbGG/idPCOgsN4HggGmSZ+1R/L gitlab.com/etke.cc/go/secgen v1.2.0/go.mod h1:v5L07AIXtNpC/miYiK0TMIn+ZKbiYrTRiXTw6qTL6pw= gitlab.com/etke.cc/go/trysmtp v1.1.3 h1:e2EHond77onMaecqCg6mWumffTSEf+ycgj88nbeefDI= gitlab.com/etke.cc/go/trysmtp v1.1.3/go.mod h1:lOO7tTdAE0a3ETV3wN3GJ7I1Tqewu7YTpPWaOmTteV0= -gitlab.com/etke.cc/go/validator v1.0.7 h1:4BGDTa9x68vJhbyn7m8W2yX+2Nb5im9+JLRrgoLUlF4= -gitlab.com/etke.cc/go/validator v1.0.7/go.mod h1:Id0SxRj0J3IPhiKlj0w1plxVLZfHlkwipn7HfRZsDts= +gitlab.com/etke.cc/go/validator/v2 v2.2.0 h1:XgB8XikOc3WzzFyimpZOt3p+kbHny98sGkXtp2tSwzQ= +gitlab.com/etke.cc/go/validator/v2 v2.2.0/go.mod h1:NBY0v4ZqR1GQpuojf369EW2oS/4Ll1uIs9YTgijRbaY= gitlab.com/etke.cc/linkpearl v0.0.0-20240716084747-f2a547f02d54 h1:+ihVGkVBBWlRyugJ3dvmA1rwqC9jFXrUZSslkYFN0Gw= gitlab.com/etke.cc/linkpearl v0.0.0-20240716084747-f2a547f02d54/go.mod h1:R/uxbOoajQVxm0qCbWYfox6B9uqnM5R0/KMJcLSr6zM= go.mau.fi/util v0.6.0 h1:W6SyB3Bm/GjenQ5iq8Z8WWdN85Gy2xS6L0wmnR7SVjg= diff --git a/smtp/logger.go b/smtp/logger.go index 1968061..0a95127 100644 --- a/smtp/logger.go +++ b/smtp/logger.go @@ -2,23 +2,8 @@ package smtp import ( "strings" - - "github.com/rs/zerolog" ) -// validatorLoggerWrapper is a wrapper around zerolog.Logger to implement validator.Logger interface -type validatorLoggerWrapper struct { - log *zerolog.Logger -} - -func (l validatorLoggerWrapper) Info(msg string, args ...any) { - l.log.Info().Msgf(msg, args...) -} - -func (l validatorLoggerWrapper) Error(msg string, args ...any) { - l.log.Error().Msgf(msg, args...) -} - // loggerWrapper is a wrapper around any logger to implement smtp.Logger interface type loggerWrapper struct { log func(string, ...any) diff --git a/smtp/session.go b/smtp/session.go index a5b8c47..2572070 100644 --- a/smtp/session.go +++ b/smtp/session.go @@ -15,7 +15,7 @@ import ( "github.com/emersion/go-smtp" "github.com/jhillyerd/enmime" "github.com/rs/zerolog" - "gitlab.com/etke.cc/go/validator" + "gitlab.com/etke.cc/go/validator/v2" "gitlab.com/etke.cc/postmoogle/email" "gitlab.com/etke.cc/postmoogle/utils" "maunium.net/go/mautrix/id" @@ -171,7 +171,7 @@ func (s *session) incomingData(r io.Reader) error { addr := s.getAddr(envelope) reader.Seek(0, io.SeekStart) //nolint:errcheck // becase we're sure that's ok validations := s.bot.GetIFOptions(s.ctx, s.roomID) - if !validateIncoming(s.from, s.tos[0], addr, s.log, validations) { + if !validateIncoming(s.from, envelope.GetHeader("Return-Path"), addr, s.log, validations) { s.bot.BanAuth(s.ctx, addr) return ErrBanned } @@ -288,7 +288,7 @@ func (s *session) getAddr(envelope *enmime.Envelope) net.Addr { return realAddr } -func validateIncoming(from, to string, senderAddr net.Addr, log *zerolog.Logger, options email.IncomingFilteringOptions) bool { +func validateIncoming(from, returnPath string, senderAddr net.Addr, log *zerolog.Logger, options email.IncomingFilteringOptions) bool { var sender net.IP switch netaddr := senderAddr.(type) { case *net.TCPAddr: @@ -298,13 +298,18 @@ func validateIncoming(from, to string, senderAddr net.Addr, log *zerolog.Logger, sender = net.ParseIP(host) } - enforce := validator.Enforce{ - Email: true, - MX: options.SpamcheckMX(), - SPF: options.SpamcheckSPF(), - SMTP: options.SpamcheckSMTP(), + vcfg := &validator.Config{ + Log: log.Warn().Msgf, + Email: validator.Email{ + Enforce: true, + Spamlist: options.Spamlist(), + MX: options.SpamcheckMX(), + SPF: options.SpamcheckSPF(), + SMTP: options.SpamcheckSMTP(), + From: from, + }, } - v := validator.New(options.Spamlist(), enforce, to, &validatorLoggerWrapper{log: log}) + v := validator.New(vcfg) - return v.Email(from, sender) + return v.Email(from, returnPath, sender) } diff --git a/vendor/gitlab.com/etke.cc/go/validator/.gitlab-ci.yml b/vendor/gitlab.com/etke.cc/go/validator/.gitlab-ci.yml deleted file mode 100644 index 45fcf2d..0000000 --- a/vendor/gitlab.com/etke.cc/go/validator/.gitlab-ci.yml +++ /dev/null @@ -1,11 +0,0 @@ -lint: - image: registry.gitlab.com/etke.cc/base - script: - - golangci-lint run ./... - -unit: - image: registry.gitlab.com/etke.cc/base - script: - - go test -coverprofile=cover.out ./... - - go tool cover -func=cover.out - - rm -f cover.out diff --git a/vendor/gitlab.com/etke.cc/go/validator/emails.go b/vendor/gitlab.com/etke.cc/go/validator/emails.go deleted file mode 100644 index 4c4ebe3..0000000 --- a/vendor/gitlab.com/etke.cc/go/validator/emails.go +++ /dev/null @@ -1,142 +0,0 @@ -package validator - -import ( - "fmt" - "net" - "net/mail" - "strings" - "time" - - "blitiri.com.ar/go/spf" - "gitlab.com/etke.cc/go/trysmtp" - "golang.org/x/net/context" -) - -// Email checks if email is valid -func (v *V) Email(email string, optionalSenderIP ...net.IP) bool { - - // edge case: email may be optional - if email == "" { - return !v.enforce.Email - } - - address, err := mail.ParseAddress(email) - if err != nil { - v.log.Info("email %s invalid, reason: %v", email, err) - return false - } - email = address.Address - return v.emailChecks(email, optionalSenderIP...) -} - -func (v *V) emailChecks(email string, optionalSenderIP ...net.IP) bool { - maxChecks := 4 - var senderIP net.IP - if len(optionalSenderIP) > 0 { - senderIP = optionalSenderIP[0] - } - errchan := make(chan error, maxChecks) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - go v.emailSpamlist(ctx, email, errchan) - go v.emailNoMX(ctx, email, errchan) - go v.emailNoSPF(ctx, email, senderIP, errchan) - go v.emailNoSMTP(ctx, email, errchan) - - var checks int - for { - checks++ - err := <-errchan - if err != nil { - v.log.Info("email %q is invalid, reason: %v", email, err) - return false - } - if checks >= maxChecks { - return true - } - } -} - -func (v *V) emailSpamlist(ctx context.Context, email string, errchan chan error) { - select { - case <-ctx.Done(): - return - default: - emailb := []byte(email) - for _, spamregex := range v.spamlist { - if spamregex.Match(emailb) { - errchan <- fmt.Errorf("spamlist") - return - } - } - errchan <- nil - } -} - -func (v *V) emailNoMX(ctx context.Context, email string, errchan chan error) { - select { - case <-ctx.Done(): - return - default: - if !v.enforce.MX { - errchan <- nil - return - } - - at := strings.LastIndex(email, "@") - domain := email[at+1:] - if !v.MX(domain) { - v.log.Info("email %s domain %s invalid, reason: no MX", email, domain) - errchan <- fmt.Errorf("no MX") - return - } - errchan <- nil - } -} - -func (v *V) emailNoSMTP(ctx context.Context, email string, errchan chan error) { - select { - case <-ctx.Done(): - return - default: - if !v.enforce.SMTP { - errchan <- nil - return - } - - client, err := trysmtp.Connect(v.from, email) - if err != nil { - if strings.HasPrefix(err.Error(), "45") { - v.log.Info("email %s may be invalid, reason: SMTP check (%v)", email, err) - errchan <- nil - return - } - - v.log.Info("email %s invalid, reason: SMTP check (%v)", email, err) - errchan <- fmt.Errorf("SMTP") - return - } - client.Close() - errchan <- nil - } -} - -func (v *V) emailNoSPF(ctx context.Context, email string, senderIP net.IP, errchan chan error) { - select { - case <-ctx.Done(): - return - default: - if !v.enforce.SPF { - errchan <- nil - return - } - - result, _ := spf.CheckHostWithSender(senderIP, "", email, spf.WithTraceFunc(v.log.Info)) //nolint:errcheck // not a error - if result == spf.Fail { - errchan <- fmt.Errorf("SPF") - return - } - errchan <- nil - } -} diff --git a/vendor/gitlab.com/etke.cc/go/validator/lookups.go b/vendor/gitlab.com/etke.cc/go/validator/lookups.go deleted file mode 100644 index 34eb022..0000000 --- a/vendor/gitlab.com/etke.cc/go/validator/lookups.go +++ /dev/null @@ -1,45 +0,0 @@ -package validator - -import "net" - -// A checks if host has at least one A record -func (v *V) A(host string) bool { - if host == "" { - return false - } - ips, err := net.LookupIP(host) - if err != nil { - v.log.Error("cannot get A records of %s: %v", host, err) - return false - } - - return len(ips) > 0 -} - -// CNAME checks if host has at least one CNAME record -func (v *V) CNAME(host string) bool { - if host == "" { - return false - } - cname, err := net.LookupCNAME(host) - if err != nil { - v.log.Error("cannot get CNAME records of %s: %v", host, err) - return false - } - - return cname != "" -} - -// MX checks if host has at least one MX record -func (v *V) MX(host string) bool { - if host == "" { - return false - } - mxs, err := net.LookupMX(host) - if err != nil { - v.log.Error("cannot get MX records of %s: %v", host, err) - return false - } - - return len(mxs) > 0 -} diff --git a/vendor/gitlab.com/etke.cc/go/validator/v2/.gitlab-ci.yml b/vendor/gitlab.com/etke.cc/go/validator/v2/.gitlab-ci.yml new file mode 100644 index 0000000..bb358d5 --- /dev/null +++ b/vendor/gitlab.com/etke.cc/go/validator/v2/.gitlab-ci.yml @@ -0,0 +1,11 @@ +lint: + stage: test + image: registry.gitlab.com/etke.cc/base/build + script: + - just lint + +unit: + stage: test + image: registry.gitlab.com/etke.cc/base/build + script: + - just test diff --git a/vendor/gitlab.com/etke.cc/go/validator/v2/.golangci.yml b/vendor/gitlab.com/etke.cc/go/validator/v2/.golangci.yml new file mode 100644 index 0000000..69e9d6e --- /dev/null +++ b/vendor/gitlab.com/etke.cc/go/validator/v2/.golangci.yml @@ -0,0 +1,142 @@ +run: + concurrency: 4 + timeout: 30m + issues-exit-code: 1 + tests: true + build-tags: [] + skip-dirs-use-default: true + skip-files: [] + modules-download-mode: readonly + +output: + formats: + - format: colored-line-number + print-issued-lines: true + print-linter-name: true + sort-results: true + +linters-settings: + decorder: + dec-order: + - const + - var + - type + - func + dogsled: + max-blank-identifiers: 3 + errcheck: + check-type-assertions: true + check-blank: true + errchkjson: + report-no-exported: true + exhaustive: + check: + - switch + - map + default-signifies-exhaustive: true + gocognit: + min-complexity: 15 + nestif: + min-complexity: 5 + gocritic: + enabled-tags: + - diagnostic + - style + - performance + gofmt: + simplify: true + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + - pattern: 'a[b:len(a)]' + replacement: 'a[b:]' + gofumpt: + extra-rules: true + grouper: + const-require-single-const: true + import-require-single-import: true + var-require-single-var: true + misspell: + locale: US + usestdlibvars: + time-month: true + time-layout: true + crypto-hash: true + default-rpc-path: true + sql-isolation-level: true + tls-signature-scheme: true + constant-kind: true + unparam: + check-exported: true +linters: + disable-all: false + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - decorder + - dogsled + - dupl + - dupword + - durationcheck + - errcheck + - errchkjson + - errname + - errorlint + - exhaustive + - exportloopref + - forcetypeassert + - gocognit + - gocritic + - gocyclo + - gofmt + - gofumpt + - goimports + - gosec + - gosimple + - gosmopolitan + - govet + - ineffassign + - makezero + - mirror + - misspell + - nestif + - nolintlint + - prealloc + - predeclared + - revive + - sqlclosecheck + - staticcheck + - unconvert + - unparam + - unused + - usestdlibvars + - wastedassign + fast: false + + +issues: + exclude-dirs: + - mocks + exclude-rules: + - path: _test\.go + linters: + - gocyclo + - gocognit + - errcheck + - dupl + - gosec + - linters: + - staticcheck + text: "SA9003:" + - linters: + - lll + source: "^//go:generate " + - linters: + - revive + text: "returns unexported type" + max-issues-per-linter: 0 + max-same-issues: 0 + new: false diff --git a/vendor/gitlab.com/etke.cc/go/validator/LICENSE b/vendor/gitlab.com/etke.cc/go/validator/v2/LICENSE similarity index 100% rename from vendor/gitlab.com/etke.cc/go/validator/LICENSE rename to vendor/gitlab.com/etke.cc/go/validator/v2/LICENSE diff --git a/vendor/gitlab.com/etke.cc/go/validator/README.md b/vendor/gitlab.com/etke.cc/go/validator/v2/README.md similarity index 100% rename from vendor/gitlab.com/etke.cc/go/validator/README.md rename to vendor/gitlab.com/etke.cc/go/validator/v2/README.md diff --git a/vendor/gitlab.com/etke.cc/go/validator/v2/config.go b/vendor/gitlab.com/etke.cc/go/validator/v2/config.go new file mode 100644 index 0000000..b3e26e7 --- /dev/null +++ b/vendor/gitlab.com/etke.cc/go/validator/v2/config.go @@ -0,0 +1,35 @@ +package validator + +import "regexp" + +// Config of the validator +type Config struct { + Email Email // Email config + Domain Domain // Domain config + Log func(msg string, args ...any) // Log func +} + +// Email checks configuration +type Email struct { + // Enforce enforces email check and rejects empty emails + Enforce bool + // SMTP enforces SMTP check (email actually exists on mail server) and rejects non-existing emails + SMTP bool + // SPF enforces SPF record check (sender allowed to use that email and send emails) and rejects unathorized emails + SPF bool + // MX enforces MX records check on email's mail server + MX bool + // From is a valid email address that will be used for SMTP checks + From string + // Spamlist is a list of spam emails with wildcards + Spamlist []string + spamlist []*regexp.Regexp +} + +// Domain checks configuration +type Domain struct { + // Enforce enforces domain check and rejects empty domains + Enforce bool + // PrivateSuffixes considers subdomains with the following suffixes as domains + PrivateSuffixes []string +} diff --git a/vendor/gitlab.com/etke.cc/go/validator/domains.go b/vendor/gitlab.com/etke.cc/go/validator/v2/domains.go similarity index 87% rename from vendor/gitlab.com/etke.cc/go/validator/domains.go rename to vendor/gitlab.com/etke.cc/go/validator/v2/domains.go index 658caa5..3204658 100644 --- a/vendor/gitlab.com/etke.cc/go/validator/domains.go +++ b/vendor/gitlab.com/etke.cc/go/validator/v2/domains.go @@ -9,12 +9,11 @@ import ( // based on W3C email regex, ref: https://www.w3.org/TR/2016/REC-html51-20161101/sec-forms.html#email-state-typeemail var domainRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$`) -var privateSuffix = []string{".etke.host", ".onmatrix.chat"} // Domain checks if domain is valid func (v *V) Domain(domain string) bool { if domain == "" { - return !v.enforce.Domain + return !v.cfg.Domain.Enforce } if !v.DomainString(domain) { @@ -27,12 +26,12 @@ func (v *V) Domain(domain string) bool { // DomainString checks if domain string / value is valid using string checks like length and regexp func (v *V) DomainString(domain string) bool { if len(domain) < 4 || len(domain) > 77 { - v.log.Info("domain %s invalid, reason: length", domain) + v.cfg.Log("domain %s invalid, reason: length", domain) return false } if !domainRegex.MatchString(domain) { - v.log.Info("domain %s invalid, reason: regexp", domain) + v.cfg.Log("domain %s invalid, reason: regexp", domain) return false } @@ -61,7 +60,7 @@ func (v *V) GetBase(domain string) string { // hasSuffix checks if domain has a suffix from public suffix list or from predefined suffix list func (v *V) hasSuffix(domain string) bool { - for _, suffix := range privateSuffix { + for _, suffix := range v.cfg.Domain.PrivateSuffixes { if strings.HasSuffix(domain, suffix) { return true } diff --git a/vendor/gitlab.com/etke.cc/go/validator/v2/emails.go b/vendor/gitlab.com/etke.cc/go/validator/v2/emails.go new file mode 100644 index 0000000..86bd7ce --- /dev/null +++ b/vendor/gitlab.com/etke.cc/go/validator/v2/emails.go @@ -0,0 +1,197 @@ +package validator + +import ( + "fmt" + "net" + "net/mail" + "strings" + "time" + + "blitiri.com.ar/go/spf" + "gitlab.com/etke.cc/go/trysmtp" + "golang.org/x/net/context" +) + +var ( + ErrSpamlist = fmt.Errorf("spamlist") + ErrNoMX = fmt.Errorf("no MX") + ErrSMTP = fmt.Errorf("SMTP") + ErrSPF = fmt.Errorf("SPF") +) + +// Email checks if email is valid +// returnPath and optionalSenderIP are optional fields +func (v *V) Email(email, returnPath string, optionalSenderIP ...net.IP) bool { + // edge case: email may be optional + if email == "" { + return !v.cfg.Email.Enforce + } + + address, err := mail.ParseAddress(email) + if err != nil { + v.cfg.Log("email %s invalid, reason: %v", email, err) + return false + } + if returnPath != "" { + rpAddress, err := mail.ParseAddress(returnPath) + if err != nil { + v.cfg.Log("return path %s invalid, reason: %v", returnPath, err) + } + if rpAddress != nil { + returnPath = rpAddress.Address + } + } + + email = address.Address + return v.emailChecks(email, returnPath, optionalSenderIP...) +} + +func (v *V) emailChecks(email, returnPath string, optionalSenderIP ...net.IP) bool { + maxChecks := 4 + var senderIP net.IP + if len(optionalSenderIP) > 0 { + senderIP = optionalSenderIP[0] + } + errchan := make(chan error, maxChecks) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + go v.emailSpamlist(ctx, email, errchan) + go v.emailNoMX(ctx, email, errchan) + go v.emailNoSPF(ctx, email, returnPath, senderIP, errchan) + go v.emailNoSMTP(ctx, email, errchan) + + var checks int + for { + checks++ + err := <-errchan + if err != nil { + v.cfg.Log("email %q is invalid, reason: %v", email, err) + return false + } + if checks >= maxChecks { + return true + } + } +} + +func (v *V) emailSpamlist(ctx context.Context, email string, errchan chan error) { + select { + case <-ctx.Done(): + return + default: + emailb := []byte(email) + for _, spamregex := range v.cfg.Email.spamlist { + if spamregex.Match(emailb) { + errchan <- ErrSpamlist + return + } + } + errchan <- nil + } +} + +func (v *V) emailNoMX(ctx context.Context, email string, errchan chan error) { + select { + case <-ctx.Done(): + return + default: + if !v.cfg.Email.MX { + errchan <- nil + return + } + + at := strings.LastIndex(email, "@") + domain := email[at+1:] + if !v.MX(domain) { + v.cfg.Log("email %s domain %s invalid, reason: no MX", email, domain) + errchan <- ErrNoMX + return + } + errchan <- nil + } +} + +func (v *V) emailNoSMTP(ctx context.Context, email string, errchan chan error) { + select { + case <-ctx.Done(): + return + default: + if !v.cfg.Email.SMTP { + errchan <- nil + return + } + + client, err := trysmtp.Connect(v.cfg.Email.From, email) + if err != nil { + if strings.HasPrefix(err.Error(), "45") { + v.cfg.Log("email %s may be invalid, reason: SMTP check (%v)", email, err) + errchan <- nil + return + } + + v.cfg.Log("email %s invalid, reason: SMTP check (%v)", email, err) + errchan <- ErrSMTP + return + } + client.Close() + errchan <- nil + } +} + +// shouldValidateReturnPath checks if returnPath should be validated in SPF check +func (v *V) shouldValidateReturnPath(email, returnPath string) bool { + if returnPath == "" { + return false + } + if email == returnPath { + return false + } + + var emailDomain, returnPathDomain string + emailParts := strings.Split(email, "@") + if len(emailParts) < 2 { + return false + } + emailDomain = v.GetBase(emailParts[1]) + + if returnPath != "" { + returnPathParts := strings.Split(returnPath, "@") + if len(returnPathParts) < 2 { + return false + } + returnPathDomain = v.GetBase(returnPathParts[1]) + } + return emailDomain == returnPathDomain +} + +func (v *V) emailNoSPF(ctx context.Context, email, returnPath string, senderIP net.IP, errchan chan error) { + select { + case <-ctx.Done(): + return + default: + if !v.cfg.Email.SPF { + errchan <- nil + return + } + + opts := []spf.Option{ + spf.WithContext(ctx), + spf.WithTraceFunc(v.cfg.Log), + } + + resultEmail, _ := spf.CheckHostWithSender(senderIP, "", email, opts...) //nolint:errcheck // not a error + if resultEmail == spf.Fail { + if v.shouldValidateReturnPath(email, returnPath) { + resultReturnPath, _ := spf.CheckHostWithSender(senderIP, "", returnPath, opts...) //nolint:errcheck // not a error + if resultReturnPath == spf.Fail { + errchan <- ErrSPF + return + } + } + errchan <- ErrSPF + return + } + errchan <- nil + } +} diff --git a/vendor/gitlab.com/etke.cc/go/validator/v2/justfile b/vendor/gitlab.com/etke.cc/go/validator/v2/justfile new file mode 100644 index 0000000..e016978 --- /dev/null +++ b/vendor/gitlab.com/etke.cc/go/validator/v2/justfile @@ -0,0 +1,22 @@ +# show help by default +default: + @just --list --justfile {{ justfile() }} + +# update go deps +update *flags: + go get {{flags}} . + go mod tidy + +# run linter +lint: + golangci-lint run ./... + +# automatically fix liter issues +lintfix: + golangci-lint run --fix ./... + +# run unit tests +test: + @go test -cover -coverprofile=cover.out -coverpkg=./... -covermode=set ./... + @go tool cover -func=cover.out + -@rm -f cover.out diff --git a/vendor/gitlab.com/etke.cc/go/validator/v2/lookups.go b/vendor/gitlab.com/etke.cc/go/validator/v2/lookups.go new file mode 100644 index 0000000..52e8804 --- /dev/null +++ b/vendor/gitlab.com/etke.cc/go/validator/v2/lookups.go @@ -0,0 +1,80 @@ +package validator + +import ( + "net" + "strings" +) + +// A checks if host has at least one A record +func (v *V) A(host string) bool { + if host == "" { + return false + } + ips, err := net.LookupIP(host) + if err != nil { + v.cfg.Log("cannot get A records of %s: %v", host, err) + return false + } + + return len(ips) > 0 +} + +// CNAME checks if host has at least one CNAME record +func (v *V) CNAME(host string) bool { + if host == "" { + return false + } + cname, err := net.LookupCNAME(host) + if err != nil { + v.cfg.Log("cannot get CNAME records of %s: %v", host, err) + return false + } + + return cname != "" +} + +// MX checks if host has at least one MX record +func (v *V) MX(host string) bool { + if host == "" { + return false + } + mxs, err := net.LookupMX(host) + if err != nil { + v.cfg.Log("cannot get MX records of %s: %v", host, err) + return false + } + + return len(mxs) > 0 +} + +// NS checks if host has at least one NS record, +// and optionally checks if the NS record contains the given string +func (v *V) NS(host string, contains ...string) bool { + if host == "" { + return false + } + nss, err := net.LookupNS(host) + if err != nil { + v.cfg.Log("cannot get NS records of %s: %v", host, err) + return false + } + if len(nss) == 0 { + v.cfg.Log("%s doesn't have NS records", host) + return false + } + + if len(contains) == 0 { + return true + } + + if len(contains) > 0 { + for _, ns := range nss { + for _, c := range contains { + if strings.Contains(ns.Host, c) { + return true + } + } + } + } + return false +} diff --git a/vendor/gitlab.com/etke.cc/go/validator/v2/validator.go b/vendor/gitlab.com/etke.cc/go/validator/v2/validator.go new file mode 100644 index 0000000..a753647 --- /dev/null +++ b/vendor/gitlab.com/etke.cc/go/validator/v2/validator.go @@ -0,0 +1,56 @@ +package validator + +import ( + "fmt" + "regexp" + "strings" +) + +// V is a validator implementation +type V struct { + cfg *Config +} + +var defaultLogfunc = func(msg string, args ...any) { + fmt.Printf(msg+"\n", args...) +} + +// New validator +func New(cfg *Config) *V { + if cfg.Log == nil { + cfg.Log = defaultLogfunc + } + spamregexes, err := parseSpamlist(cfg.Email.Spamlist) + if err != nil { + cfg.Log("cannot parse spamlist: %v", err) + } + cfg.Email.spamlist = spamregexes + return &V{cfg} +} + +func parseSpamlist(patterns []string) ([]*regexp.Regexp, error) { + regexes := []*regexp.Regexp{} + for _, pattern := range patterns { + rule, err := regexp.Compile("^" + parsePattern(pattern) + "$") + if err != nil { + return regexes, err + } + + regexes = append(regexes, rule) + } + + return regexes, nil +} + +func parsePattern(pattern string) string { + var regexpattern strings.Builder + for _, runeItem := range pattern { + if runeItem == '*' { + regexpattern.WriteString("(.*)") + continue + } + regexpattern.WriteString(regexp.QuoteMeta(string(runeItem))) + } + + return regexpattern.String() +} diff --git a/vendor/gitlab.com/etke.cc/go/validator/validator.go b/vendor/gitlab.com/etke.cc/go/validator/validator.go deleted file mode 100644 index 2fc2ecd..0000000 --- a/vendor/gitlab.com/etke.cc/go/validator/validator.go +++ /dev/null @@ -1,75 +0,0 @@ -package validator - -import ( - "regexp" - "strings" -) - -// V is a validator implementation -type V struct { - spamlist []*regexp.Regexp - enforce Enforce - from string - log Logger -} - -// Enforce checks -type Enforce struct { - // Email enforces email check and rejects empty emails - Email bool - // Domain enforces domain check and rejects empty domains - Domain bool - // SMTP enforces SMTP check (email actually exists on mail server) and rejects non-existing emails - SMTP bool - // SPF enforces SPF record check (sender allowed to use that email and send emails) and rejects unathorized emails - SPF bool - // MX enforces MX records check on email's mail server - MX bool -} - -type Logger interface { - Info(string, ...interface{}) - Error(string, ...interface{}) -} - -// New Validator, accepts spamlist with wildcards -func New(spamlist []string, enforce Enforce, smtpFrom string, log Logger) *V { - spamregexes, err := parseSpamlist(spamlist) - if err != nil { - log.Error("cannot parse spamlist: %v", err) - } - - return &V{ - spamlist: spamregexes, - enforce: enforce, - from: smtpFrom, - log: log, - } -} - -func parseSpamlist(patterns []string) ([]*regexp.Regexp, error) { - regexes := []*regexp.Regexp{} - for _, pattern := range patterns { - rule, err := regexp.Compile("^" + parsePattern(pattern) + "$") - if err != nil { - return regexes, err - } - - regexes = append(regexes, rule) - } - - return regexes, nil -} - -func parsePattern(pattern string) string { - var regexpattern strings.Builder - for _, runeItem := range pattern { - if runeItem == '*' { - regexpattern.WriteString("(.*)") - continue - } - regexpattern.WriteString(regexp.QuoteMeta(string(runeItem))) - } - - return regexpattern.String() -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 54ba50d..4e6585e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -159,9 +159,9 @@ gitlab.com/etke.cc/go/secgen # gitlab.com/etke.cc/go/trysmtp v1.1.3 ## explicit; go 1.17 gitlab.com/etke.cc/go/trysmtp -# gitlab.com/etke.cc/go/validator v1.0.7 +# gitlab.com/etke.cc/go/validator/v2 v2.2.0 ## explicit; go 1.18 -gitlab.com/etke.cc/go/validator +gitlab.com/etke.cc/go/validator/v2 # gitlab.com/etke.cc/linkpearl v0.0.0-20240716084747-f2a547f02d54 ## explicit; go 1.21 gitlab.com/etke.cc/linkpearl