SPF and DKIM checks
This commit is contained in:
@@ -113,6 +113,8 @@ If you want to change them - check available options in the help message (`!pm h
|
||||
---
|
||||
|
||||
* **!pm spamcheck:mx** - only accept email from servers which seem prepared to receive it (those having valid MX records) (`true` - enable, `false` - disable)
|
||||
* **!pm spamcheck:spf** - only accept email from senders which authorized to send it (those matching SPF records) (`true` - enable, `false` - disable)
|
||||
* **!pm spamcheck:dkim** - only accept correctly authorized emails (without DKIM signature at all or with valid DKIM signature) (`true` - enable, `false` - disable)
|
||||
* **!pm spamcheck:smtp** - only accept email from servers which seem prepared to receive it (those listening on an SMTP port) (`true` - enable, `false` - disable)
|
||||
* **!pm spamlist** - Get or set `spamlist` of the room (comma-separated list), eg: `spammer@example.com,*@spammer.org,noreply@*`
|
||||
|
||||
|
||||
@@ -173,6 +173,18 @@ func (b *Bot) initCommands() commandList {
|
||||
sanitizer: utils.SanitizeBoolString,
|
||||
allowed: b.allowOwner,
|
||||
},
|
||||
{
|
||||
key: roomOptionSpamcheckSPF,
|
||||
description: "only accept email from senders which authorized to send it (those matching SPF records) (`true` - enable, `false` - disable)",
|
||||
sanitizer: utils.SanitizeBoolString,
|
||||
allowed: b.allowOwner,
|
||||
},
|
||||
{
|
||||
key: roomOptionSpamcheckDKIM,
|
||||
description: "only accept correctly authorized emails (without DKIM signature at all or with valid DKIM signature) (`true` - enable, `false` - disable)",
|
||||
sanitizer: utils.SanitizeBoolString,
|
||||
allowed: b.allowOwner,
|
||||
},
|
||||
{
|
||||
key: roomOptionSpamcheckSMTP,
|
||||
description: "only accept email from servers which seem prepared to receive it (those listening on an SMTP port) (`true` - enable, `false` - disable)",
|
||||
|
||||
@@ -27,7 +27,9 @@ const (
|
||||
roomOptionNoThreads = "nothreads"
|
||||
roomOptionNoFiles = "nofiles"
|
||||
roomOptionPassword = "password"
|
||||
roomOptionSpamcheckDKIM = "spamcheck:dkim"
|
||||
roomOptionSpamcheckSMTP = "spamcheck:smtp"
|
||||
roomOptionSpamcheckSPF = "spamcheck:spf"
|
||||
roomOptionSpamcheckMX = "spamcheck:mx"
|
||||
roomOptionSpamlist = "spamlist"
|
||||
)
|
||||
@@ -96,10 +98,18 @@ func (s roomSettings) NoFiles() bool {
|
||||
return utils.Bool(s.Get(roomOptionNoFiles))
|
||||
}
|
||||
|
||||
func (s roomSettings) SpamcheckDKIM() bool {
|
||||
return utils.Bool(s.Get(roomOptionSpamcheckDKIM))
|
||||
}
|
||||
|
||||
func (s roomSettings) SpamcheckSMTP() bool {
|
||||
return utils.Bool(s.Get(roomOptionSpamcheckSMTP))
|
||||
}
|
||||
|
||||
func (s roomSettings) SpamcheckSPF() bool {
|
||||
return utils.Bool(s.Get(roomOptionSpamcheckSPF))
|
||||
}
|
||||
|
||||
func (s roomSettings) SpamcheckMX() bool {
|
||||
return utils.Bool(s.Get(roomOptionSpamcheckMX))
|
||||
}
|
||||
|
||||
42
docs/tricks.md
Normal file
42
docs/tricks.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# tricks
|
||||
|
||||
<!-- vim-markdown-toc GitLab -->
|
||||
|
||||
* [Logs](#logs)
|
||||
* [get most active hosts](#get-most-active-hosts)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## Logs
|
||||
|
||||
### get most active hosts
|
||||
|
||||
Even if you use postmoogle as an internal mail server and contact "outside internet" quite rarely,
|
||||
you will see lots of connections to your SMTP servers from random hosts over internet that do... nothing?
|
||||
They don't send any valid emails or do something meaningful, thus you can safely assume they are spammers.
|
||||
|
||||
To get top X (in example: top 10) hosts with biggest count of attempts to connect to your postmoogle instance, follow the steps:
|
||||
|
||||
1. enable debug log: `export POSTMOOGLE_LOGLEVEL=debug`
|
||||
2. restart postmoogle and wait some time to get stats
|
||||
3. run the following bash one-liner to show top 10 hosts by connections count:
|
||||
|
||||
```bash
|
||||
journalctl -o cat -u postmoogle | grep "smtp.DEBUG accepted connection from " | grep -oE "[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}" | sort | uniq -ci | sort -rn | head -n 10
|
||||
253 111.111.111.111
|
||||
183 222.222.222.222
|
||||
39 333.333.333.333
|
||||
38 444.444.444.444
|
||||
18 555.555.555.555
|
||||
16 666.666.666.666
|
||||
8 777.777.777.777
|
||||
5 888.888.888.888
|
||||
5 999.999.999.999
|
||||
4 010.010.010.010
|
||||
```
|
||||
|
||||
of course, IP addresses above are crafted just to visualize their place in that top, according to the number of connections done.
|
||||
In reality, you will see real IP addresses here. Usually, only hosts with hundreds or thousands of connections for the last 7 days worth checking.
|
||||
|
||||
What's next?
|
||||
Do **not** ban them right away. Check WHOIS info for each host and only after that decide if you really want to ban that host or not.
|
||||
@@ -2,7 +2,9 @@ package email
|
||||
|
||||
// IncomingFilteringOptions for incoming mail
|
||||
type IncomingFilteringOptions interface {
|
||||
SpamcheckDKIM() bool
|
||||
SpamcheckSMTP() bool
|
||||
SpamcheckSPF() bool
|
||||
SpamcheckMX() bool
|
||||
Spamlist() []string
|
||||
}
|
||||
|
||||
3
go.mod
3
go.mod
@@ -20,12 +20,13 @@ require (
|
||||
gitlab.com/etke.cc/go/mxidwc v1.0.0
|
||||
gitlab.com/etke.cc/go/secgen v1.1.1
|
||||
gitlab.com/etke.cc/go/trysmtp v1.0.0
|
||||
gitlab.com/etke.cc/go/validator v1.0.4
|
||||
gitlab.com/etke.cc/go/validator v1.0.5
|
||||
gitlab.com/etke.cc/linkpearl v0.0.0-20221116205701-65547c5608e6
|
||||
maunium.net/go/mautrix v0.12.3
|
||||
)
|
||||
|
||||
require (
|
||||
blitiri.com.ar/go/spf v1.5.1 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1,3 +1,5 @@
|
||||
blitiri.com.ar/go/spf v1.5.1 h1:CWUEasc44OrANJD8CzceRnRn1Jv0LttY68cYym2/pbE=
|
||||
blitiri.com.ar/go/spf v1.5.1/go.mod h1:E71N92TfL4+Yyd5lpKuE9CAF2pd4JrUq1xQfkTxoNdk=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||
@@ -99,8 +101,8 @@ gitlab.com/etke.cc/go/secgen v1.1.1 h1:RmKOki725HIhWJHzPtAc9X4YvBneczndchpMgoDkE
|
||||
gitlab.com/etke.cc/go/secgen v1.1.1/go.mod h1:3pJqRGeWApzx7qXjABqz2o2SMCNpKSZao/gXVdasqE8=
|
||||
gitlab.com/etke.cc/go/trysmtp v1.0.0 h1:f/7gSmzohKniVeLSLevI+ZsySYcPUGkT9cRlOTwjOr8=
|
||||
gitlab.com/etke.cc/go/trysmtp v1.0.0/go.mod h1:KqRuIB2IPElEEbAxXmFyKtm7S5YiuEb4lxwWthccqyE=
|
||||
gitlab.com/etke.cc/go/validator v1.0.4 h1:2HIBP12f/RZr/7KTNH5/PgPTzl1vi7Co3lmhNTWB31A=
|
||||
gitlab.com/etke.cc/go/validator v1.0.4/go.mod h1:3vdssRG4LwgdTr9IHz9MjGSEO+3/FO9hXPGMuSeweJ8=
|
||||
gitlab.com/etke.cc/go/validator v1.0.5 h1:YJs50yOHY3tKp/2NVP5U6Pbtvxof/YtGRpNOVjkGf+s=
|
||||
gitlab.com/etke.cc/go/validator v1.0.5/go.mod h1:Id0SxRj0J3IPhiKlj0w1plxVLZfHlkwipn7HfRZsDts=
|
||||
gitlab.com/etke.cc/linkpearl v0.0.0-20221116205701-65547c5608e6 h1:+HDT2/bx3Hug++aeDE/PaoRRcnKdYzEm6i2RlOAzPXo=
|
||||
gitlab.com/etke.cc/linkpearl v0.0.0-20221116205701-65547c5608e6/go.mod h1:Dgtu0qvymNjjky4Bu5WC8+iSohcb5xZ9CtkD3ezDqIA=
|
||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package smtp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/emersion/go-msgauth/dkim"
|
||||
"github.com/emersion/go-smtp"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/jhillyerd/enmime"
|
||||
@@ -26,6 +28,7 @@ type incomingSession struct {
|
||||
greylisted func(net.Addr) bool
|
||||
ban func(net.Addr)
|
||||
domains []string
|
||||
enforceDKIM bool
|
||||
|
||||
ctx context.Context
|
||||
addr net.Addr
|
||||
@@ -68,7 +71,8 @@ func (s *incomingSession) Rcpt(to string) error {
|
||||
}
|
||||
|
||||
validations := s.getFilters(roomID)
|
||||
if !validateEmail(s.from, to, s.log, validations) {
|
||||
s.enforceDKIM = validations.SpamcheckDKIM()
|
||||
if !validateIncoming(s.from, to, s.addr, s.log, validations) {
|
||||
s.ban(s.addr)
|
||||
return ErrBanned
|
||||
}
|
||||
@@ -85,8 +89,28 @@ func (s *incomingSession) Data(r io.Reader) error {
|
||||
Message: "You have been greylisted, try again a bit later.",
|
||||
}
|
||||
}
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
s.log.Error("cannot read DATA: %v", err)
|
||||
return err
|
||||
}
|
||||
reader := bytes.NewReader(data)
|
||||
if s.enforceDKIM {
|
||||
results, verr := dkim.Verify(reader)
|
||||
if verr != nil {
|
||||
s.log.Error("cannot verify DKIM: %v", verr)
|
||||
return verr
|
||||
}
|
||||
for _, result := range results {
|
||||
if result.Err != nil {
|
||||
s.log.Info("DKIM verification of %q failed: %v", result.Domain, result.Err)
|
||||
return result.Err
|
||||
}
|
||||
}
|
||||
reader.Seek(0, io.SeekStart) //nolint:errcheck
|
||||
}
|
||||
parser := enmime.NewParser()
|
||||
envelope, err := parser.ReadEnvelope(r)
|
||||
envelope, err := parser.ReadEnvelope(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -176,13 +200,23 @@ func (s *outgoingSession) Data(r io.Reader) error {
|
||||
func (s *outgoingSession) Reset() {}
|
||||
func (s *outgoingSession) Logout() error { return nil }
|
||||
|
||||
func validateEmail(from, to string, log *logger.Logger, options email.IncomingFilteringOptions) bool {
|
||||
func validateIncoming(from, to string, senderAddr net.Addr, log *logger.Logger, options email.IncomingFilteringOptions) bool {
|
||||
var sender net.IP
|
||||
switch netaddr := senderAddr.(type) {
|
||||
case *net.TCPAddr:
|
||||
sender = netaddr.IP
|
||||
default:
|
||||
host, _, _ := net.SplitHostPort(senderAddr.String()) // nolint:errcheck
|
||||
sender = net.ParseIP(host)
|
||||
}
|
||||
|
||||
enforce := validator.Enforce{
|
||||
Email: true,
|
||||
MX: options.SpamcheckMX(),
|
||||
SPF: options.SpamcheckSPF(),
|
||||
SMTP: options.SpamcheckSMTP(),
|
||||
}
|
||||
v := validator.New(options.Spamlist(), enforce, to, log)
|
||||
|
||||
return v.Email(from)
|
||||
return v.Email(from, sender)
|
||||
}
|
||||
|
||||
10
vendor/blitiri.com.ar/go/spf/.gitignore
generated
vendored
Normal file
10
vendor/blitiri.com.ar/go/spf/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Ignore anything beginning with a dot: these are usually temporary or
|
||||
# unimportant.
|
||||
.*
|
||||
|
||||
# Exceptions to the rule above: files we care about that would otherwise be
|
||||
# excluded.
|
||||
!.gitignore
|
||||
|
||||
# go-fuzz build artifacts.
|
||||
*-fuzz.zip
|
||||
27
vendor/blitiri.com.ar/go/spf/LICENSE
generated
vendored
Normal file
27
vendor/blitiri.com.ar/go/spf/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
Licensed under the MIT licence, which is reproduced below (from
|
||||
https://opensource.org/licenses/MIT).
|
||||
|
||||
-----
|
||||
|
||||
Copyright (c) 2016
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
49
vendor/blitiri.com.ar/go/spf/README.md
generated
vendored
Normal file
49
vendor/blitiri.com.ar/go/spf/README.md
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
# blitiri.com.ar/go/spf
|
||||
|
||||
[](https://pkg.go.dev/blitiri.com.ar/go/spf)
|
||||
[](https://gitlab.com/albertito/spf/-/pipelines)
|
||||
[](https://goreportcard.com/report/github.com/albertito/spf)
|
||||
[](https://coveralls.io/github/albertito/spf)
|
||||
|
||||
[spf](https://godoc.org/blitiri.com.ar/go/spf) is an open source
|
||||
implementation of the [Sender Policy Framework
|
||||
(SPF)](https://en.wikipedia.org/wiki/Sender_Policy_Framework) in Go.
|
||||
|
||||
It is used by the [chasquid](https://blitiri.com.ar/p/chasquid/) and
|
||||
[maddy](https://maddy.email) SMTP servers.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
// Check if `sender` is authorized to send from the given `ip`. The `domain`
|
||||
// is used if the sender doesn't have one.
|
||||
result, err := spf.CheckHostWithSender(ip, domain, sender)
|
||||
if result == spf.Fail {
|
||||
// Not authorized to send.
|
||||
}
|
||||
```
|
||||
|
||||
See the [package documentation](https://pkg.go.dev/blitiri.com.ar/go/spf) for
|
||||
more details.
|
||||
|
||||
|
||||
## Status
|
||||
|
||||
All SPF mechanisms, modifiers, and macros are supported.
|
||||
|
||||
The API should be considered stable. Major version changes will be announced
|
||||
to the mailing list (details below).
|
||||
|
||||
|
||||
## Contact
|
||||
|
||||
If you have any questions, comments or patches please send them to the mailing
|
||||
list, `chasquid@googlegroups.com`.
|
||||
|
||||
To subscribe, send an email to `chasquid+subscribe@googlegroups.com`.
|
||||
|
||||
You can also browse the
|
||||
[archives](https://groups.google.com/forum/#!forum/chasquid).
|
||||
|
||||
58
vendor/blitiri.com.ar/go/spf/fuzz.go
generated
vendored
Normal file
58
vendor/blitiri.com.ar/go/spf/fuzz.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// Fuzz testing for package spf.
|
||||
//
|
||||
// Run it with:
|
||||
//
|
||||
// go-fuzz-build blitiri.com.ar/go/spf
|
||||
// go-fuzz -bin=./spf-fuzz.zip -workdir=testdata/fuzz
|
||||
//
|
||||
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package spf
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"blitiri.com.ar/go/spf/internal/dnstest"
|
||||
)
|
||||
|
||||
// Parsed IP addresses, for convenience.
|
||||
var (
|
||||
ip1110 = net.ParseIP("1.1.1.0")
|
||||
ip1111 = net.ParseIP("1.1.1.1")
|
||||
ip6666 = net.ParseIP("2001:db8::68")
|
||||
ip6660 = net.ParseIP("2001:db8::0")
|
||||
)
|
||||
|
||||
// DNS resolver to use. Will be initialized once with the expected fixtures,
|
||||
// and then reused on each fuzz run.
|
||||
var dns = dnstest.NewResolver()
|
||||
|
||||
func init() {
|
||||
dns.Ip["d1111"] = []net.IP{ip1111}
|
||||
dns.Ip["d1110"] = []net.IP{ip1110}
|
||||
dns.Mx["d1110"] = []*net.MX{{"d1110", 5}, {"nothing", 10}}
|
||||
dns.Ip["d6666"] = []net.IP{ip6666}
|
||||
dns.Ip["d6660"] = []net.IP{ip6660}
|
||||
dns.Mx["d6660"] = []*net.MX{{"d6660", 5}, {"nothing", 10}}
|
||||
dns.Addr["2001:db8::68"] = []string{"sonlas6.", "domain.", "d6666."}
|
||||
dns.Addr["1.1.1.1"] = []string{"lalala.", "domain.", "d1111."}
|
||||
}
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
// The domain's TXT record comes from the fuzzer.
|
||||
dns.Txt["domain"] = []string{string(data)}
|
||||
|
||||
v4result, _ := CheckHostWithSender(
|
||||
ip1111, "helo", "domain", WithResolver(dns))
|
||||
v6result, _ := CheckHostWithSender(
|
||||
ip6666, "helo", "domain", WithResolver(dns))
|
||||
|
||||
// Raise priority if any of the results was something other than
|
||||
// PermError, as it means the data was better formed.
|
||||
if v4result != PermError || v6result != PermError {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
111
vendor/blitiri.com.ar/go/spf/internal/dnstest/dns.go
generated
vendored
Normal file
111
vendor/blitiri.com.ar/go/spf/internal/dnstest/dns.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// DNS resolver for testing purposes.
|
||||
//
|
||||
// In the future, when go fuzz can make use of _test.go files, we can rename
|
||||
// this file dns_test.go and remove this extra package entirely.
|
||||
// Until then, unfortunately this is the most reasonable way to share these
|
||||
// helpers between go and fuzz tests.
|
||||
package dnstest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Testing DNS resolver.
|
||||
//
|
||||
// Not exported since this is not part of the public API and only used
|
||||
// internally on tests.
|
||||
//
|
||||
type TestResolver struct {
|
||||
Txt map[string][]string
|
||||
Mx map[string][]*net.MX
|
||||
Ip map[string][]net.IP
|
||||
Addr map[string][]string
|
||||
Cname map[string]string
|
||||
Errors map[string]error
|
||||
}
|
||||
|
||||
func NewResolver() *TestResolver {
|
||||
return &TestResolver{
|
||||
Txt: map[string][]string{},
|
||||
Mx: map[string][]*net.MX{},
|
||||
Ip: map[string][]net.IP{},
|
||||
Addr: map[string][]string{},
|
||||
Cname: map[string]string{},
|
||||
Errors: map[string]error{},
|
||||
}
|
||||
}
|
||||
|
||||
var nxDomainErr = &net.DNSError{
|
||||
Err: "domain not found (for testing)",
|
||||
IsNotFound: true,
|
||||
}
|
||||
|
||||
func (r *TestResolver) LookupTXT(ctx context.Context, domain string) (txts []string, err error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
domain = strings.ToLower(domain)
|
||||
domain = strings.TrimRight(domain, ".")
|
||||
if cname, ok := r.Cname[domain]; ok {
|
||||
return r.LookupTXT(ctx, cname)
|
||||
}
|
||||
if _, ok := r.Txt[domain]; !ok && r.Errors[domain] == nil {
|
||||
return nil, nxDomainErr
|
||||
}
|
||||
return r.Txt[domain], r.Errors[domain]
|
||||
}
|
||||
|
||||
func (r *TestResolver) LookupMX(ctx context.Context, domain string) (mxs []*net.MX, err error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
domain = strings.ToLower(domain)
|
||||
domain = strings.TrimRight(domain, ".")
|
||||
if cname, ok := r.Cname[domain]; ok {
|
||||
return r.LookupMX(ctx, cname)
|
||||
}
|
||||
if _, ok := r.Mx[domain]; !ok && r.Errors[domain] == nil {
|
||||
return nil, nxDomainErr
|
||||
}
|
||||
return r.Mx[domain], r.Errors[domain]
|
||||
}
|
||||
|
||||
func (r *TestResolver) LookupIPAddr(ctx context.Context, host string) (as []net.IPAddr, err error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
host = strings.ToLower(host)
|
||||
host = strings.TrimRight(host, ".")
|
||||
if cname, ok := r.Cname[host]; ok {
|
||||
return r.LookupIPAddr(ctx, cname)
|
||||
}
|
||||
if _, ok := r.Ip[host]; !ok && r.Errors[host] == nil {
|
||||
return nil, nxDomainErr
|
||||
}
|
||||
return ipsToAddrs(r.Ip[host]), r.Errors[host]
|
||||
}
|
||||
|
||||
func ipsToAddrs(ips []net.IP) []net.IPAddr {
|
||||
as := []net.IPAddr{}
|
||||
for _, ip := range ips {
|
||||
as = append(as, net.IPAddr{IP: ip, Zone: ""})
|
||||
}
|
||||
return as
|
||||
}
|
||||
|
||||
func (r *TestResolver) LookupAddr(ctx context.Context, host string) (addrs []string, err error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
host = strings.ToLower(host)
|
||||
host = strings.TrimRight(host, ".")
|
||||
if cname, ok := r.Cname[host]; ok {
|
||||
return r.LookupAddr(ctx, cname)
|
||||
}
|
||||
if _, ok := r.Addr[host]; !ok && r.Errors[host] == nil {
|
||||
return nil, nxDomainErr
|
||||
}
|
||||
return r.Addr[host], r.Errors[host]
|
||||
}
|
||||
1044
vendor/blitiri.com.ar/go/spf/spf.go
generated
vendored
Normal file
1044
vendor/blitiri.com.ar/go/spf/spf.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
20
vendor/gitlab.com/etke.cc/go/validator/emails.go
generated
vendored
20
vendor/gitlab.com/etke.cc/go/validator/emails.go
generated
vendored
@@ -1,14 +1,21 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"blitiri.com.ar/go/spf"
|
||||
"gitlab.com/etke.cc/go/trysmtp"
|
||||
)
|
||||
|
||||
// Email checks if email is valid
|
||||
func (v *V) Email(email string) bool {
|
||||
func (v *V) Email(email string, optionalSenderIP ...net.IP) bool {
|
||||
var senderIP net.IP
|
||||
if len(optionalSenderIP) > 0 {
|
||||
senderIP = optionalSenderIP[0]
|
||||
}
|
||||
|
||||
// edge case: email may be optional
|
||||
if email == "" {
|
||||
return !v.enforce.Email
|
||||
@@ -41,6 +48,12 @@ func (v *V) Email(email string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if v.enforce.SPF {
|
||||
if v.emailNoSPF(email, senderIP) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if v.enforce.SMTP {
|
||||
if v.emailNoSMTP(email) {
|
||||
return false
|
||||
@@ -78,3 +91,8 @@ func (v *V) emailNoSMTP(email string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *V) emailNoSPF(email string, senderIP net.IP) bool {
|
||||
result, _ := spf.CheckHostWithSender(senderIP, "", email, spf.WithTraceFunc(v.log.Info)) //nolint:errcheck // not a error
|
||||
return result == spf.Fail
|
||||
}
|
||||
|
||||
2
vendor/gitlab.com/etke.cc/go/validator/validator.go
generated
vendored
2
vendor/gitlab.com/etke.cc/go/validator/validator.go
generated
vendored
@@ -21,6 +21,8 @@ type Enforce struct {
|
||||
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
|
||||
}
|
||||
|
||||
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@@ -1,3 +1,7 @@
|
||||
# blitiri.com.ar/go/spf v1.5.1
|
||||
## explicit; go 1.15
|
||||
blitiri.com.ar/go/spf
|
||||
blitiri.com.ar/go/spf/internal/dnstest
|
||||
# github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a
|
||||
## explicit
|
||||
github.com/cention-sany/utf7
|
||||
@@ -127,7 +131,7 @@ gitlab.com/etke.cc/go/secgen
|
||||
# gitlab.com/etke.cc/go/trysmtp v1.0.0
|
||||
## explicit; go 1.18
|
||||
gitlab.com/etke.cc/go/trysmtp
|
||||
# gitlab.com/etke.cc/go/validator v1.0.4
|
||||
# gitlab.com/etke.cc/go/validator v1.0.5
|
||||
## explicit; go 1.18
|
||||
gitlab.com/etke.cc/go/validator
|
||||
# gitlab.com/etke.cc/linkpearl v0.0.0-20221116205701-65547c5608e6
|
||||
|
||||
Reference in New Issue
Block a user