From 3947d9e6717f0abb7602ed43188150b319cbbb9b Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Sat, 21 Mar 2020 13:03:56 +0100 Subject: [PATCH] Google Safe Browsing API support --- README.md | 5 ++++- config.go | 1 + config_test.go | 4 ++++ forms.go | 2 +- go.mod | 2 +- go.sum | 15 ++++++++++----- spamcheck.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 spamcheck.go diff --git a/README.md b/README.md index 81e292c..0c61400 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ To run the server, you must set a few environment variables from the list below. | **`ALLOWED_TO`** | required | - | All allowed recipients (separated by `,`) | | **`PORT`** | optional | `8080` | The port on which the server should listen | | **`HONEYPORTS`** | optional | `_t_email` | Honeypot form fields (separated by `,`) | +| **`GOOGLE_API_KEY`** | optional | - | Google API Key for the [Google Safe Browsing API](https://developers.google.com/safe-browsing/v4/) | ## Special form fields @@ -42,10 +43,12 @@ You can find a sample form in the `form.html` file. Only fields whose name do no | **`_formName`** | optional | - | Name of the form, hidden | | **`_t_email`** | optional | - | (Default) "Honeypot" field, not hidden, advised (see notice below) | -## Honeypot +## Spam protection MailyGo offers the option to use a [Honeypot](https://en.wikipedia.org/wiki/Honeypot\_(computing)) field, which is basically another input, but it's hidden to the user with either a CSS rule or some JavaScript. It is very likely, that your public form will get the attention of some bots some day and then the spam starts. But bots try to fill every possible input field and will also fill the honeypot field. MailyGo won't send mails of form submissions where a honeypot field is filled. So you should definitely use it. +If a Google Safe Browsing API key is set, submitted URLs will also get checked for threats. + ## License MailyGo is licensed under the MIT license, so you can do basically everything with it, but nevertheless, please contribute your improvements to make MailyGo better for everyone. See the LICENSE file. \ No newline at end of file diff --git a/config.go b/config.go index 2ad726a..fd83d7d 100644 --- a/config.go +++ b/config.go @@ -15,6 +15,7 @@ type config struct { SmtpPassword string `env:"SMTP_PASS"` SmtpHost string `env:"SMTP_HOST"` SmtpPort int `env:"SMTP_PORT" envDefault:"587"` + GoogleApiKey string `env:"GOOGLE_API_KEY"` } func parseConfig() (config, error) { diff --git a/config_test.go b/config_test.go index 8111460..3dfdf3e 100644 --- a/config_test.go +++ b/config_test.go @@ -35,6 +35,7 @@ func Test_parseConfig(t *testing.T) { _ = os.Setenv("SMTP_PASS", "secret") _ = os.Setenv("SMTP_HOST", "smtp.example.com") _ = os.Setenv("SMTP_PORT", "100") + _ = os.Setenv("GOOGLE_API_KEY", "abc") cfg, err := parseConfig() if err != nil { t.Error() @@ -67,6 +68,9 @@ func Test_parseConfig(t *testing.T) { if !reflect.DeepEqual(cfg.SmtpPort, 100) { t.Error("SMTP port is wrong") } + if !reflect.DeepEqual(cfg.GoogleApiKey, "abc") { + t.Error("Google API Key is wrong") + } }) t.Run("Error when wrong config", func(t *testing.T) { os.Clearenv() diff --git a/forms.go b/forms.go index 3a1272f..4387ac8 100644 --- a/forms.go +++ b/forms.go @@ -53,7 +53,7 @@ func isBot(values FormValues) bool { } } } - return false + return checkValues(values) } func sendResponse(values FormValues, w http.ResponseWriter) { diff --git a/go.mod b/go.mod index b7b08d0..858d2bf 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,6 @@ go 1.14 require ( github.com/caarlos0/env/v6 v6.2.1 + github.com/google/safebrowsing v0.0.0-20190624211811-bbf0d20d26b3 github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 - golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect ) diff --git a/go.sum b/go.sum index 33cb3e2..53513dd 100644 --- a/go.sum +++ b/go.sum @@ -6,21 +6,26 @@ github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27 github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/safebrowsing v0.0.0-20190624211811-bbf0d20d26b3 h1:4SV2fLwScO6iAgUKNqXwIrz9Fq2ykQxbSV4ObXtNCWY= +github.com/google/safebrowsing v0.0.0-20190624211811-bbf0d20d26b3/go.mod h1:hT4r/grkURkgVSWJaWd6PyS4xfAb+vb34DyMDYiOGa8= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 h1:hJde9rA24hlTcAYSwJoXpDUyGtfKQ/jsofw+WaDqGrI= github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rakyll/statik v0.1.5/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/spamcheck.go b/spamcheck.go new file mode 100644 index 0000000..cd4f1f7 --- /dev/null +++ b/spamcheck.go @@ -0,0 +1,49 @@ +package main + +import ( + "github.com/google/safebrowsing" + "net/url" + "strings" +) + +// Returns true when it spam +func checkValues(values FormValues) bool { + var urlsToCheck []string + for _, value := range values { + for _, singleValue := range value { + if strings.Contains(singleValue, "http") { + parsed, e := url.Parse(singleValue) + if parsed != nil && e == nil { + urlsToCheck = append(urlsToCheck, singleValue) + } + } + } + } + return checkUrls(urlsToCheck) +} + +// Only tests when GOOGLE_API_KEY is set +// Returns true when it spam +func checkUrls(urlsToCheck []string) bool { + if len(appConfig.GoogleApiKey) < 1 || len(urlsToCheck) == 0 { + return false + } + sb, err := safebrowsing.NewSafeBrowser(safebrowsing.Config{ + APIKey: appConfig.GoogleApiKey, + ID: "MailyGo", + }) + if err != nil { + return false + } + allThreats, err := sb.LookupURLs(urlsToCheck) + if err != nil { + return false + } + for _, threats := range allThreats { + if len(threats) > 0 { + // Unsafe url, mark as spam + return true + } + } + return false +}