diff --git a/README.md b/README.md index 38df0aea42386b1cf35e1049ad682174cfb9b19f..43272f79b4444ed96c4b9fb47bb520c026d88f83 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ reports-collector Proof of concept of an ingestion service for anomaly reports. -Supports receiving reports over HTTPS for the following mechanisms: +Supports receiving reports over HTTPS and SMTP for the following +mechanisms: * [Browser Reporting API (NEL, CSP, etc)](https://www.w3.org/TR/reporting/) * [TLS-RPT](https://tools.ietf.org/html/rfc8460) @@ -17,12 +18,18 @@ The source IP address is not included in the output, but for user reports we add instead the ASN, to allow for some meaningful non-deanonymizing aggregation of the reports. +The SMTP receiver works by running a very simple SMTP server (on the +port specified by *--smtp-addr*), meant to be downstream to your +MTA. This SMTP server does not check the recipient address and just +attempts to parse all received email. + Things that are still to do: +* Implement email ingestion support for TLS-RPT reports * More testing with real-world reports -* Maybe add support for email ingestion Right now the server offers a single intake endpoint at */ingest/v1*, and then looks at the Content-Type of the request to figure out what kind of report it is. It might be just simpler to switch to separate endpoints per report type. + diff --git a/browser.go b/browser.go index 36da13e0b63f0a9295ac23544be6fc433e2fe975..09e506b10ec4b8945ef1a6687ea406e208a5e677 100644 --- a/browser.go +++ b/browser.go @@ -6,6 +6,8 @@ import ( "net/http" "net/url" "time" + + "github.com/jhillyerd/enmime" ) // Generic browser report as per https://www.w3.org/TR/reporting/. @@ -36,6 +38,10 @@ func (h *ReportHandler) Parse(contentType string, req *http.Request) ([]Event, e return events, nil } +func (h *ReportHandler) ParseMIME(*enmime.Part) ([]Event, error) { + return nil, ErrNoMatch +} + func (h *ReportHandler) eventFromReport(req *http.Request, report *report) Event { ts := time.Now().Add(time.Duration(-report.Age) * time.Second) diff --git a/cmd/reports-collector/main.go b/cmd/reports-collector/main.go index da1bd0c753f9ddf51076e1458bfdc08a6fc5a661..5278fc629881730577ae65faab606caad602ab54 100644 --- a/cmd/reports-collector/main.go +++ b/cmd/reports-collector/main.go @@ -4,6 +4,7 @@ import ( "context" "flag" "log" + "net" "net/http" "os" "os/signal" @@ -11,13 +12,17 @@ import ( "time" rc "git.autistici.org/ai3/tools/reports-collector" + "github.com/chrj/smtpd" "github.com/prometheus/client_golang/prometheus/promhttp" + "golang.org/x/sync/errgroup" ) var ( - addr = flag.String("addr", fromEnv("ADDR", ":4890"), "address to listen on") + addr = flag.String("addr", fromEnv("ADDR", ":4890"), "address to listen on (HTTP)") + smtpAddr = flag.String("smtp-addr", fromEnv("SMTP_ADDR", ""), "address to listen on (SMTP), disable incoming SMTP if empty") gracefulShutdownTimeout = 5 * time.Second + maxMessageSize = 20 * 1024 * 1024 ) func fromEnv(name, deflt string) string { @@ -39,42 +44,93 @@ func main() { new(rc.DMARCHandler), ) + // Crete an errgroup.Group with a controlling Context that + // will be canceled if any of our protocol servers fail to + // start. + outerCtx, cancel := context.WithCancel(context.Background()) + g, ctx := errgroup.WithContext(outerCtx) + // Create the http.Server. - mux := http.NewServeMux() - mux.Handle("/ingest/v1", collector) - mux.Handle("/metrics", promhttp.Handler()) - server := &http.Server{ - Addr: *addr, - Handler: mux, - ReadTimeout: 10 * time.Second, - IdleTimeout: 30 * time.Second, - WriteTimeout: 10 * time.Second, + g.Go(func() error { + mux := http.NewServeMux() + mux.Handle("/ingest/v1", collector) + mux.Handle("/metrics", promhttp.Handler()) + server := &http.Server{ + Addr: *addr, + Handler: mux, + ReadTimeout: 10 * time.Second, + IdleTimeout: 30 * time.Second, + WriteTimeout: 10 * time.Second, + } + + go func() { + <-ctx.Done() + if ctx.Err() != context.Canceled { + return + } + // Gracefully terminate, then shut + // down remaining clients. + sctx, scancel := context.WithTimeout( + context.Background(), + gracefulShutdownTimeout) + defer scancel() + if err := server.Shutdown(sctx); err == context.Canceled { + if err := server.Close(); err != nil { + log.Printf("error terminating server: %v", err) + } + } + }() + + log.Printf("starting HTTP server on %s", *addr) + err := server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + return err + } + return nil + }) + + // Create the SMTP server. + if *smtpAddr != "" { + g.Go(func() error { + hostname, _ := os.Hostname() + server := &smtpd.Server{ + Hostname: hostname, + Handler: collector.ServeSMTP, + ReadTimeout: 60 * time.Second, + WriteTimeout: 60 * time.Second, + DataTimeout: 60 * time.Second, + MaxConnections: 100, + MaxMessageSize: maxMessageSize, + MaxRecipients: 1, + } + + // Create our own listener so we can shut down cleanly. + log.Printf("starting SMTP server on %s", *smtpAddr) + l, err := net.Listen("tcp", *smtpAddr) + if err != nil { + log.Fatal(err) + } + go func() { + <-ctx.Done() + l.Close() + }() + + return server.Serve(l) + }) } - done := make(chan struct{}) + // Cancel the outer context if we receive a termination + // signal. This will cause the servers to be stopped. sigCh := make(chan os.Signal, 1) go func() { <-sigCh - log.Printf("terminating") - // Gracefully terminate, then shut down remaining - // clients. - ctx, cancel := context.WithTimeout( - context.Background(), gracefulShutdownTimeout) - defer cancel() - if err := server.Shutdown(ctx); err == context.Canceled { - if err := server.Close(); err != nil { - log.Printf("error terminating server: %v", err) - } - } - close(done) + log.Printf("signal received, terminating") + cancel() }() - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - err := server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatalf("server error: %v", err) + err := g.Wait() + if err != nil && err != context.Canceled { + log.Printf("error: %v", err) } - - <-done } diff --git a/collector.go b/collector.go index b3d1a92025a18f5f3f5f20e3da37897fdb93be84..24e3a9a29f50aa8fe30fa85973651f5b58169ae7 100644 --- a/collector.go +++ b/collector.go @@ -1,12 +1,15 @@ package reportscollector import ( + "bytes" "errors" "fmt" "log" "mime" "net/http" + "github.com/chrj/smtpd" + "github.com/jhillyerd/enmime" "github.com/prometheus/client_golang/prometheus" ) @@ -14,6 +17,7 @@ var ErrNoMatch = errors.New("no match") type Handler interface { Parse(string, *http.Request) ([]Event, error) + ParseMIME(*enmime.Part) ([]Event, error) } type Sink interface { @@ -91,6 +95,50 @@ hloop: } } +func (c *Collector) ServeSMTP(peer smtpd.Peer, env smtpd.Envelope) error { + msgParts, err := enmime.ReadParts(bytes.NewReader(env.Data)) + if err != nil { + log.Printf("smtp: error parsing input: %v", err) + return smtpd.Error{550, "Error parsing input"} + } + + // Find a handler that can successfully parse the request, and + // get a list of Events. + var events []Event + matched := false +hloop: + for _, h := range c.handlers { + var err error + events, err = h.ParseMIME(msgParts) + switch err { + case ErrNoMatch: + continue + case nil: + matched = true + break hloop + default: + log.Printf("smtp: error handling report: %v", err) + // Discard the message. + return nil + } + } + + if !matched { + log.Printf("smtp: no matching handlers") + // Discard the message. + return nil + } + + // Augment the Events with additional information obtained + // from the HTTP request, and send them to the forwarder. + for _, e := range events { + c.sink.Send(e) + reportsByType.WithLabelValues( + e.GetString("type"), e.GetString("domain")).Inc() + } + return nil +} + var ( reportsByType = prometheus.NewCounterVec( prometheus.CounterOpts{ diff --git a/collector_test.go b/collector_test.go index 752fc23eb2ccbf1d788dcce03b63c4c99c63adbc..8103020637b1dd372eb25ebf35673aec367e7655 100644 --- a/collector_test.go +++ b/collector_test.go @@ -1,10 +1,14 @@ package reportscollector import ( + "io/ioutil" "net/http" "net/http/httptest" + "path/filepath" "strings" "testing" + + "github.com/chrj/smtpd" ) type countingSink struct { @@ -15,6 +19,8 @@ func (c *countingSink) Send(e Event) { c.counter++ } +func (c *countingSink) reset() { c.counter = 0 } + func createTestCollector() (*countingSink, string, func()) { sink := new(countingSink) c := NewCollector( @@ -290,6 +296,36 @@ func TestDMARC_IgnoreSuccesses(t *testing.T) { } } +func TestDMARC_Emails(t *testing.T) { + files, err := filepath.Glob("dmarc/email-samples/*.txt") + if err != nil { + t.Skip() + } + + sink := new(countingSink) + c := NewCollector( + sink, + new(DMARCHandler), + ) + + for _, testf := range files { + data, _ := ioutil.ReadFile(testf) + + sink.reset() + err := c.ServeSMTP( + smtpd.Peer{}, + smtpd.Envelope{Data: data}, + ) + if err != nil { + t.Errorf("error parsing %s: %v", testf, err) + continue + } + if sink.counter == 0 { + t.Errorf("nothing parsed from %s", testf) + } + } +} + var cspTestData = `{ "csp-report": { "document-uri": "http://localhost:3000/content", diff --git a/csp.go b/csp.go index 8fe37ecca6a88d546060169b8af57e76e722a30a..698c38eaf93b0131e912071e3711356f6b3071c8 100644 --- a/csp.go +++ b/csp.go @@ -4,6 +4,8 @@ import ( "encoding/json" "net/http" "time" + + "github.com/jhillyerd/enmime" ) type legacyCSPReport struct { @@ -41,6 +43,10 @@ func (h *LegacyCSPHandler) Parse(contentType string, req *http.Request) ([]Event return []Event{h.eventFromReport(req, cnt.Report)}, nil } +func (h *LegacyCSPHandler) ParseMIME(*enmime.Part) ([]Event, error) { + return nil, ErrNoMatch +} + func (h *LegacyCSPHandler) eventFromReport(req *http.Request, report *legacyCSPReport) Event { e := make(Event) if asn, ok := lookupASN(getRemoteIP(req)); ok { diff --git a/dmarc.go b/dmarc.go index e50d7546e82c8d3f518f98d73de3129aff9da745..064036cb13f895a9c238cf23a9591d6a76656b0a 100644 --- a/dmarc.go +++ b/dmarc.go @@ -1,11 +1,17 @@ package reportscollector import ( + "archive/zip" + "bytes" "compress/gzip" "encoding/xml" "io" + "io/ioutil" "net/http" + "strings" "time" + + "github.com/jhillyerd/enmime" ) type dmarcRecord struct { @@ -69,6 +75,21 @@ type dmarcReport struct { type DMARCHandler struct{} +func (h *DMARCHandler) parseDMARC(r io.Reader) ([]Event, error) { + var report dmarcReport + if err := xml.NewDecoder(r).Decode(&report); err != nil { + return nil, err + } + + var events []Event + for _, rec := range report.Records { + if rec.isFailure() { + events = append(events, h.eventFromRecord(&report, rec)) + } + } + return events, nil +} + func (h *DMARCHandler) Parse(contentType string, req *http.Request) ([]Event, error) { var r io.Reader switch contentType { @@ -84,18 +105,42 @@ func (h *DMARCHandler) Parse(contentType string, req *http.Request) ([]Event, er return nil, ErrNoMatch } - var report dmarcReport - if err := xml.NewDecoder(r).Decode(&report); err != nil { - return nil, err + return h.parseDMARC(r) +} + +func (h *DMARCHandler) ParseMIME(msg *enmime.Part) ([]Event, error) { + // Try plain text/xml first. + if part := msg.DepthMatchFirst(func(p *enmime.Part) bool { + return p.ContentType == "text/xml" + }); part != nil { + return h.parseDMARC(bytes.NewReader(part.Content)) } - var events []Event - for _, rec := range report.Records { - if rec.isFailure() { - events = append(events, h.eventFromRecord(&report, rec)) + // Try to decode a gzipped attachment (also detected based on + // the filename). + if part := msg.DepthMatchFirst(func(p *enmime.Part) bool { + return (p.ContentType == "application/gzip" || + strings.HasSuffix(p.FileName, ".xml.gz")) + }); part != nil { + gz, err := gzip.NewReader(bytes.NewReader(part.Content)) + if err != nil { + return nil, err } + return h.parseDMARC(gz) } - return events, nil + + // Try to decode a ZIP attachment. + if part := msg.DepthMatchFirst(func(p *enmime.Part) bool { + return (p.ContentType == "application/zip" || + strings.HasSuffix(p.FileName, ".zip")) + }); part != nil { + data, ok := extractXMLFromZip(part.Content) + if ok { + return h.parseDMARC(bytes.NewReader(data)) + } + } + + return nil, ErrNoMatch } func (h *DMARCHandler) eventFromRecord(report *dmarcReport, rec *dmarcRecord) Event { @@ -114,3 +159,26 @@ func (h *DMARCHandler) eventFromRecord(report *dmarcReport, rec *dmarcRecord) Ev return e } + +// Extract the first file with a .xml extension from a ZIP archive. +func extractXMLFromZip(zipData []byte) ([]byte, bool) { + zr, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) + if err != nil { + return nil, false + } + for _, file := range zr.File { + if strings.HasSuffix(file.Name, ".xml") { + f, err := file.Open() + if err != nil { + return nil, false + } + defer f.Close() + data, err := ioutil.ReadAll(f) + if err != nil { + return nil, false + } + return data, true + } + } + return nil, false +} diff --git a/go.mod b/go.mod index d20925be90215a7d18012ff1de53da57b882983a..b477fe38929b289e00336824eaf4fa2264b02711 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,11 @@ module git.autistici.org/ai3/tools/reports-collector require ( + github.com/chrj/smtpd v0.1.2 + github.com/jhillyerd/enmime v0.8.2 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/oschwald/geoip2-golang v1.4.0 // indirect + github.com/oschwald/geoip2-golang v1.4.0 github.com/prometheus/client_golang v1.8.0 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index aef073a661ac2ed35daa75efcac3d56d19f9e6d5..01e2350aed567c2a1e713535c26a26d0152f1848 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,14 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chrj/smtpd v0.1.2 h1:yWaMOCmnPlcNgJzkak1TBhhkObAfomd+NmZG5epdO88= +github.com/chrj/smtpd v0.1.2/go.mod h1:jt4ydELuZmqhn9hn3YpEPV1dY00aOB+Q1nWXnBDFKeY= +github.com/chrj/smtpd v0.2.0 h1:QGbE4UQz7sKjvXpRgNLuiBOjcWTzBKu/dj0hyDLpD14= +github.com/chrj/smtpd v0.2.0/go.mod h1:1hmG9KbrE10JG1SmvG79Krh4F6713oUrw2+gRp1oSYk= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= @@ -43,6 +49,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eaigner/dkim v0.0.0-20150301120808-6fe4a7ee9cfb/go.mod h1:FSCIHbrqk7D01Mj8y/jW+NS1uoCerr+ad+IckTHTFf4= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -63,10 +70,13 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBUjI5YA3iVeaZ9Tb5PxNrrIP1xs= +github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -124,6 +134,10 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 h1:jTkyeF7NZ5oIr0ESmcrpiDgAfoidCBF4F5kJhjtaRwE= +github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= +github.com/jhillyerd/enmime v0.8.2 h1:F3VbaJL1GfIcyKl7Utxcg4lR+LsM/OObxh+N9sWmp7g= +github.com/jhillyerd/enmime v0.8.2/go.mod h1:MBHs3ugk03NGjMM6PuRynlKf+HA5eSillZ+TRCm73AE= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -149,6 +163,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -175,6 +191,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -199,6 +217,7 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -235,6 +254,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -247,6 +267,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= +github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -301,7 +323,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -310,6 +334,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -334,6 +359,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/tlsrpt.go b/tlsrpt.go index 525fa7ca3849efe6337af16be15a9bcd161097e2..3f169e73673e6e54c1df64f0d9d8ae7fbda33421 100644 --- a/tlsrpt.go +++ b/tlsrpt.go @@ -6,6 +6,8 @@ import ( "io" "net/http" "time" + + "github.com/jhillyerd/enmime" ) type tlsrptFailure struct { @@ -78,6 +80,10 @@ func (h *TLSRPTHandler) Parse(contentType string, req *http.Request) ([]Event, e return events, nil } +func (h *TLSRPTHandler) ParseMIME(*enmime.Part) ([]Event, error) { + return nil, ErrNoMatch +} + func (h *TLSRPTHandler) eventFromFailure(report *tlsrpt, policy *tlsrptPolicy, failure *tlsrptFailure) Event { e := make(Event) e.Set("type", "tlsrpt")