diff --git a/.gitignore b/.gitignore index e4b215347bd5452cbce73a4bd9397f065745b9a6..de0fc89a3d0577749cdba4cfe211fb15e7db3971 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ dmarc-prometheus-exporter # Output of the go coverage tool, specifically when used with LiteIDE *.out + +# Prometheus report files +*.prom +reports diff --git a/README.md b/README.md index 0ef97482ad8c24879327e2e07bf3cd4c9623906e..6c6a88311e98cea5654569086d73627c591cf2ef 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,14 @@ You can download one of the pre-compiled binaries from the list below. ## Configuration -To avoid providing the MQTT URL via a flag every time, you can define an environment variable named `MQTT_URL` instead. The MQTT topic can also be set via the environment variable `MQTT_TOPIC`. +To avoid providing the MQTT URL via a flag every time, you can define an environment variable named `MQTT_URL` instead. The MQTT topics can also be set via the environment variables `MQTT_TOPIC_RUA` and `MQTT_TOPIC_RUF`. If you prefer you can also set these values in a `.env` file like this: ```env MQTT_URL=mqtts://[username]:[password]@mqtt.example.com:8883 -MQTT_TOPIC=gcf/email/dmarc@example.com +MQTT_TOPIC_RUA=gcf/email/dmarc-rua@example.com +MQTT_TOPIC_RUF=gcf/email/dmarc-ruf@example.com ``` ## Usage @@ -32,3 +33,14 @@ MQTT_TOPIC=gcf/email/dmarc@example.com ``` See the [generated documentation](https://gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/-/blob/main/docs/dmarc-prometheus-exporter.md) for more information about the various commands. + +## Testing + +```bash +> curl -F 'data=@test/google.com!hedenstroem.com!1644364800!1644451199.zip' http://localhost:9100/rua +``` + + +```bash +> curl -X POST -H "Content-Type: application/json" -d @test/rua_email.json https://example.com/emailToMQTT +``` diff --git a/cmd/analyze.go b/cmd/analyze.go new file mode 100644 index 0000000000000000000000000000000000000000..7b720f691cd77336827d45131cac571f963205ea --- /dev/null +++ b/cmd/analyze.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "errors" + "io/ioutil" + "os" + + "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cobra" + "gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/utils" +) + +var reportPath string + +var analyzeCmd = &cobra.Command{ + Use: "analyze", + Short: "Analyze DMARC reports", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("no report file specified") + } + for _, arg := range args { + file, err := os.Open(arg) + if err != nil { + log.Warnf("Open %s: %s", file, err) + continue + } + defer file.Close() + data, err := ioutil.ReadAll(file) + if err != nil { + log.Warnf("Read %s: %s", file, err) + continue + } + r, err := utils.Parse(data) + if err != nil { + log.Warnf("Parse %s: %s", file, err) + continue + } + utils.Analyze(r) + } + return prometheus.WriteToTextfile(reportPath, prometheus.DefaultGatherer) + }, + DisableAutoGenTag: true, +} + +func init() { + RootCmd.AddCommand(analyzeCmd) + analyzeCmd.Flags().StringVarP(&reportPath, "output", "o", "./report.prom", "Report file") +} diff --git a/cmd/root.go b/cmd/root.go index 8251259db3be70edc4b4bbf4611284f0d355db8f..08b3d5cda85d8adc0302ab56acf92867200ce2bf 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,18 +4,26 @@ import ( "os" "github.com/joho/godotenv" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( debug bool + log *logrus.Logger ) var RootCmd = &cobra.Command{ Use: "dmarc-prometheus-exporter", Short: "Export metrics derived from DMARC aggregate reports", DisableAutoGenTag: true, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + log = logrus.New() + if debug { + log.SetLevel(logrus.DebugLevel) + } + }, } func Execute() { diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 0000000000000000000000000000000000000000..66a48ccef3ef04889c148cc79119332b1cf97d7b --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,242 @@ +package cmd + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "os/signal" + "syscall" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/prometheus/common/expfmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/types" + "gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/utils" +) + +var runCmd = &cobra.Command{ + Use: "run", + Short: "Process report emails from MQTT topic or via HTTP uploads", + RunE: func(cmd *cobra.Command, args []string) error { + opts, err := createClientOptions() + if err != nil { + return err + } + client := mqtt.NewClient(opts) + if token := client.Connect(); token.Wait() && token.Error() != nil { + return token.Error() + } + client.Subscribe(viper.GetString("MQTT_TOPIC_RUA"), 0, ruaMQTTHandler) + client.Subscribe(viper.GetString("MQTT_TOPIC_RUF"), 0, rufMQTTHandler) + return <-startHTTPServer() + }, + PreRunE: func(cmd *cobra.Command, args []string) (err error) { + if viper.GetString("BACKUP_FILE") != "" { + log.Infof("Restoring state from %s", viper.GetString("BACKUP_FILE")) + reader, err := os.Open(viper.GetString("BACKUP_FILE")) + if err == nil { + defer reader.Close() + var parser expfmt.TextParser + mf, err := parser.TextToMetricFamilies(reader) + if err == nil { + utils.Restore(mf) + } else { + log.Warnf("Error parsing %s: %s", viper.GetString("BACKUP_FILE"), err) + } + } else { + log.Warnf("Error opening %s: %s", viper.GetString("BACKUP_FILE"), err) + } + } + return + }, + PostRunE: func(cmd *cobra.Command, args []string) (err error) { + if viper.GetString("BACKUP_FILE") != "" { + log.Infof("Backing up state to %s", viper.GetString("BACKUP_FILE")) + err = prometheus.WriteToTextfile(viper.GetString("BACKUP_FILE"), prometheus.DefaultGatherer) + if err != nil { + log.Warnf("Backup error: %s", err) + } + } + return + }, + DisableAutoGenTag: true, +} + +func startHTTPServer() chan error { + + errC := make(chan error, 1) + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) + + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + mux.HandleFunc("/rua", ruaHTTPHandler) + mux.HandleFunc("/ruf", rufHTTPHandler) + + srv := &http.Server{ + Addr: viper.GetString("HTTP_ADDR"), + Handler: mux, + } + + go func() { + <-ctx.Done() + log.Debug("Shutdown signal received") + ctxTimeout, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer func() { + stop() + cancel() + close(errC) + }() + srv.SetKeepAlivesEnabled(false) + if err := srv.Shutdown(ctxTimeout); err != nil { + errC <- err + } + log.Info("Shutdown completed") + }() + + go func() { + log.Infof("Listening and serving at %s", srv.Addr) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + errC <- err + } + }() + + return errC +} + +func ruaHTTPHandler(w http.ResponseWriter, r *http.Request) { + if err := r.ParseMultipartForm(10 << 20); err != nil { + log.Warn(err) + return + } + for _, fileHeaders := range r.MultipartForm.File { + for _, fileHeader := range fileHeaders { + file, _ := fileHeader.Open() + defer file.Close() + body, err := ioutil.ReadAll(file) + if err != nil { + log.Warn(err) + continue + } + log.Debugf("Parsing file upload %s", fileHeader.Filename) + r, err := utils.Parse(body) + if err != nil { + log.Warn(err) + continue + } + log.Debugf("Analyzing file upload %s", fileHeader.Filename) + utils.Analyze(r) + } + } +} + +func rufHTTPHandler(w http.ResponseWriter, r *http.Request) { + if err := r.ParseMultipartForm(10 << 20); err != nil { + log.Warn(err) + return + } + for _, fileHeaders := range r.MultipartForm.File { + for _, fileHeader := range fileHeaders { + log.Debugf("Storing file upload %s", fileHeader.Filename) + src, _ := fileHeader.Open() + defer src.Close() + dst, err := os.Create(fmt.Sprintf("%s/%s", viper.GetString("OUTPUT_PATH"), fileHeader.Filename)) + if err != nil { + log.Warn(err) + continue + } + defer dst.Close() + io.Copy(dst, src) + } + } +} + +func ruaMQTTHandler(client mqtt.Client, msg mqtt.Message) { + var email types.Email + if err := json.Unmarshal(msg.Payload(), &email); err != nil { + log.Warn(err) + return + } + for _, attachment := range email.Attachments { + log.Debugf("Parsing email attachment %s", attachment.Filename) + r, err := utils.Parse(attachment.Content.Data) + if err != nil { + log.Warn(err) + continue + } + log.Debugf("Analyzing email attachment %s", attachment.Filename) + utils.Analyze(r) + } +} + +func rufMQTTHandler(client mqtt.Client, msg mqtt.Message) { + var email types.Email + if err := json.Unmarshal(msg.Payload(), &email); err != nil { + log.Warn(err) + return + } + for _, attachment := range email.Attachments { + log.Debugf("Storing email attachment %s", attachment.Filename) + dst, err := os.Create(fmt.Sprintf("%s/%s", viper.GetString("OUTPUT_PATH"), attachment.Filename)) + if err != nil { + log.Warn(err) + continue + } + defer dst.Close() + _, err = dst.Write(attachment.Content.Data) + if err != nil { + log.Warn(err) + } + } +} + +func createClientOptions() (*mqtt.ClientOptions, error) { + uri, err := url.Parse(viper.GetString("MQTT_URL")) + if err != nil { + return nil, err + } + var server string + switch uri.Scheme { + case "mqtts": + server = fmt.Sprintf("ssl://%s", uri.Host) + case "ws": + server = fmt.Sprintf("ws://%s", uri.Host) + default: + server = fmt.Sprintf("tcp://%s", uri.Host) + } + opts := mqtt.NewClientOptions() + opts.AddBroker(server) + if password, passwordSet := uri.User.Password(); passwordSet { + opts.SetUsername(uri.User.Username()) + opts.SetPassword(password) + } + opts.SetClientID(viper.GetString("MQTT_CLIENT_ID")) + return opts, nil +} + +func init() { + runCmd.Flags().StringP("url", "u", "tcp://127.0.0.1:1883", "MQTT Broker URL") + runCmd.Flags().StringP("topic_rua", "t", "", "MQTT Aggregate Report Topic") + runCmd.Flags().StringP("topic_ruf", "f", "", "MQTT Forensic Report Topic") + runCmd.Flags().StringP("id", "i", "dmarc-prometheus-exporter", "MQTT Client ID") + runCmd.Flags().StringP("addr", "a", ":9100", "TCP network address to listen on") + runCmd.Flags().StringP("output", "o", "reports", "Forensic reports storage path") + runCmd.Flags().StringP("backup", "b", "", "Metrics backup") + viper.BindPFlag("MQTT_URL", runCmd.Flags().Lookup("url")) + viper.BindPFlag("MQTT_TOPIC_RUA", runCmd.Flags().Lookup("topic_rua")) + viper.BindPFlag("MQTT_TOPIC_RUF", runCmd.Flags().Lookup("topic_ruf")) + viper.BindPFlag("MQTT_CLIENT_ID", runCmd.Flags().Lookup("id")) + viper.BindPFlag("HTTP_ADDR", runCmd.Flags().Lookup("addr")) + viper.BindPFlag("OUTPUT_PATH", runCmd.Flags().Lookup("output")) + viper.BindPFlag("BACKUP_FILE", runCmd.Flags().Lookup("backup")) + RootCmd.AddCommand(runCmd) +} diff --git a/cmd/subscribe.go b/cmd/subscribe.go deleted file mode 100644 index 3fa6ed2af0f5e2d7b26d9c044d85afc5dc19c97f..0000000000000000000000000000000000000000 --- a/cmd/subscribe.go +++ /dev/null @@ -1,96 +0,0 @@ -package cmd - -import ( - "encoding/json" - "encoding/xml" - "fmt" - "net/url" - "os" - "os/signal" - "syscall" - - "github.com/davecgh/go-spew/spew" - mqtt "github.com/eclipse/paho.mqtt.golang" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/types" - "gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/utils" -) - -var subscribeCmd = &cobra.Command{ - Use: "subscribe", - Short: "Subscribe to emails on an MQTT topic", - RunE: func(cmd *cobra.Command, args []string) error { - - opts, err := createClientOptions() - if err != nil { - return err - } - client := mqtt.NewClient(opts) - if token := client.Connect(); token.Wait() && token.Error() != nil { - return token.Error() - } - client.Subscribe(viper.GetString("MQTT_TOPIC"), 0, callback) - - keepAlive := make(chan os.Signal) - signal.Notify(keepAlive, os.Interrupt, syscall.SIGTERM) - <-keepAlive - - return nil - }, - DisableAutoGenTag: true, -} - -func callback(client mqtt.Client, msg mqtt.Message) { - var email types.Email - if err := json.Unmarshal(msg.Payload(), &email); err == nil { - for _, attachment := range email.Attachments { - if attachment.ContentType == "application/zip" { - files, _ := utils.UnzipBytes(attachment.Content.Data) - for _, file := range files { - if file.HasSuffix(".xml") { - var feedback types.Feedback - feedback.FromFile = file.Name - if err := xml.Unmarshal(file.Data, &feedback); err == nil { - spew.Dump(feedback) - } - } - } - } - } - } -} - -func createClientOptions() (*mqtt.ClientOptions, error) { - uri, err := url.Parse(viper.GetString("MQTT_URL")) - if err != nil { - return nil, err - } - var server string - switch uri.Scheme { - case "mqtts": - server = fmt.Sprintf("ssl://%s", uri.Host) - case "ws": - server = fmt.Sprintf("ws://%s", uri.Host) - default: - server = fmt.Sprintf("tcp://%s", uri.Host) - } - opts := mqtt.NewClientOptions() - opts.AddBroker(server) - if password, passwordSet := uri.User.Password(); passwordSet { - opts.SetUsername(uri.User.Username()) - opts.SetPassword(password) - } - opts.SetClientID(viper.GetString("MQTT_CLIENT_ID")) - return opts, nil -} - -func init() { - subscribeCmd.Flags().StringP("url", "u", "tcp://127.0.0.1:1883", "MQTT Broker URL") - subscribeCmd.Flags().StringP("topic", "t", "", "MQTT Topic") - subscribeCmd.Flags().StringP("id", "i", "dmarc-prometheus-exporter", "MQTT Client ID") - viper.BindPFlag("MQTT_URL", subscribeCmd.Flags().Lookup("url")) - viper.BindPFlag("MQTT_TOPIC", subscribeCmd.Flags().Lookup("topic")) - viper.BindPFlag("MQTT_CLIENT_ID", subscribeCmd.Flags().Lookup("id")) - RootCmd.AddCommand(subscribeCmd) -} diff --git a/docs/dmarc-prometheus-exporter.md b/docs/dmarc-prometheus-exporter.md index e9b542cd19e273ac9bf546d6f32e7f4bd6b8553e..6ec0b04cba180bb0af172cf531c95fc0b3f809fc 100644 --- a/docs/dmarc-prometheus-exporter.md +++ b/docs/dmarc-prometheus-exporter.md @@ -11,6 +11,8 @@ Export metrics derived from DMARC aggregate reports ### SEE ALSO +* [dmarc-prometheus-exporter analyze](dmarc-prometheus-exporter_analyze.md) - Analyze DMARC reports * [dmarc-prometheus-exporter completion](dmarc-prometheus-exporter_completion.md) - Generate the autocompletion script for the specified shell +* [dmarc-prometheus-exporter run](dmarc-prometheus-exporter_run.md) - Process report emails from MQTT topic or via HTTP uploads * [dmarc-prometheus-exporter version](dmarc-prometheus-exporter_version.md) - Display udm-query version diff --git a/docs/dmarc-prometheus-exporter_analyze.md b/docs/dmarc-prometheus-exporter_analyze.md new file mode 100644 index 0000000000000000000000000000000000000000..909eb8fdf02d613d5f28fdb0847ed467d63bec89 --- /dev/null +++ b/docs/dmarc-prometheus-exporter_analyze.md @@ -0,0 +1,25 @@ +## dmarc-prometheus-exporter analyze + +Analyze DMARC reports + +``` +dmarc-prometheus-exporter analyze [flags] +``` + +### Options + +``` + -h, --help help for analyze + -o, --output string Report file (default "./report.prom") +``` + +### Options inherited from parent commands + +``` + --debug Show debug output +``` + +### SEE ALSO + +* [dmarc-prometheus-exporter](dmarc-prometheus-exporter.md) - Export metrics derived from DMARC aggregate reports + diff --git a/docs/dmarc-prometheus-exporter_run.md b/docs/dmarc-prometheus-exporter_run.md new file mode 100644 index 0000000000000000000000000000000000000000..d5345de9657455a46edb50550f2ed8f0fb79a7a6 --- /dev/null +++ b/docs/dmarc-prometheus-exporter_run.md @@ -0,0 +1,31 @@ +## dmarc-prometheus-exporter run + +Process report emails from MQTT topic or via HTTP uploads + +``` +dmarc-prometheus-exporter run [flags] +``` + +### Options + +``` + -a, --addr string TCP network address to listen on (default ":9100") + -b, --backup string Metrics backup + -h, --help help for run + -i, --id string MQTT Client ID (default "dmarc-prometheus-exporter") + -o, --output string Forensic reports storage path (default "reports") + -t, --topic_rua string MQTT Aggregate Report Topic + -f, --topic_ruf string MQTT Forensic Report Topic + -u, --url string MQTT Broker URL (default "tcp://127.0.0.1:1883") +``` + +### Options inherited from parent commands + +``` + --debug Show debug output +``` + +### SEE ALSO + +* [dmarc-prometheus-exporter](dmarc-prometheus-exporter.md) - Export metrics derived from DMARC aggregate reports + diff --git a/go.mod b/go.mod index 66e0bd7cdc1bb54af7e3fa942150c53e8b23b2e4..b5d432e9c6c5aa876d71b000c5f19b31f006ec88 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,33 @@ module gitlab.hedenstroem.com/go/dmarc-prometheus-exporter go 1.17 require ( - github.com/davecgh/go-spew v1.1.1 github.com/eclipse/paho.mqtt.golang v1.3.5 github.com/joho/godotenv v1.4.0 + github.com/prometheus/client_golang v1.4.0 + github.com/prometheus/client_model v0.2.0 + github.com/prometheus/common v0.9.1 + github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v1.3.0 github.com/spf13/viper v1.10.1 ) require ( + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect github.com/magiconair/properties v1.8.5 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect + github.com/prometheus/procfs v0.0.8 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.4.1 // indirect @@ -28,6 +39,8 @@ require ( golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect golang.org/x/text v0.3.7 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 77ea11eeeb70d33f786bf8f974524f8fe075dd63..3430c45d2df2c6bbde487637779a47ad2a5f8168 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,10 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -62,12 +64,15 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -152,6 +157,7 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -167,6 +173,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -244,6 +251,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -267,6 +275,7 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +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= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -297,15 +306,19 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0 h1:YVIb/fVcOTMSqtqZWSKnHpSLBxu8DKgxq8z6RuBZwqI= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -316,6 +329,7 @@ github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43 github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= @@ -618,6 +632,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -763,7 +778,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/test/google.com!kornfehl.se!1644364800!1644451199.xml.gz b/test/google.com!kornfehl.se!1644364800!1644451199.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..4992e9df3be6869506fa8fd88f64e8ac099242be Binary files /dev/null and b/test/google.com!kornfehl.se!1644364800!1644451199.xml.gz differ diff --git a/test/rua_email.json b/test/rua_email.json new file mode 100644 index 0000000000000000000000000000000000000000..f96756f171cd2b25e778dcd4bceda5c5a17d9148 --- /dev/null +++ b/test/rua_email.json @@ -0,0 +1,907 @@ +{ + "attachments": [ + { + "type": "attachment", + "content": { + "type": "Buffer", + "data": [ + 80, + 75, + 3, + 4, + 10, + 0, + 0, + 0, + 8, + 0, + 64, + 84, + 87, + 84, + 206, + 210, + 135, + 28, + 85, + 2, + 0, + 0, + 134, + 22, + 0, + 0, + 52, + 0, + 0, + 0, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 33, + 104, + 101, + 100, + 101, + 110, + 115, + 116, + 114, + 111, + 101, + 109, + 46, + 99, + 111, + 109, + 33, + 49, + 54, + 52, + 53, + 52, + 56, + 56, + 48, + 48, + 48, + 33, + 49, + 54, + 52, + 53, + 53, + 55, + 52, + 51, + 57, + 57, + 46, + 120, + 109, + 108, + 237, + 88, + 203, + 142, + 163, + 48, + 16, + 188, + 207, + 87, + 68, + 185, + 199, + 224, + 240, + 74, + 86, + 12, + 179, + 167, + 253, + 130, + 221, + 51, + 114, + 76, + 67, + 172, + 1, + 219, + 178, + 205, + 60, + 254, + 126, + 77, + 48, + 132, + 33, + 251, + 208, + 72, + 123, + 88, + 69, + 62, + 5, + 151, + 187, + 219, + 213, + 213, + 101, + 36, + 146, + 63, + 189, + 117, + 237, + 230, + 5, + 148, + 102, + 130, + 63, + 110, + 49, + 10, + 183, + 27, + 224, + 84, + 84, + 140, + 55, + 143, + 219, + 31, + 223, + 191, + 237, + 14, + 219, + 205, + 83, + 241, + 144, + 215, + 0, + 213, + 137, + 208, + 231, + 226, + 97, + 179, + 201, + 21, + 72, + 161, + 76, + 217, + 129, + 33, + 21, + 49, + 100, + 192, + 44, + 42, + 84, + 83, + 114, + 210, + 65, + 209, + 8, + 209, + 180, + 128, + 168, + 232, + 242, + 96, + 6, + 199, + 24, + 232, + 8, + 107, + 11, + 46, + 108, + 133, + 246, + 125, + 87, + 117, + 68, + 209, + 157, + 238, + 229, + 80, + 238, + 235, + 50, + 109, + 140, + 115, + 57, + 111, + 70, + 145, + 146, + 10, + 110, + 8, + 53, + 37, + 227, + 181, + 40, + 206, + 198, + 72, + 253, + 37, + 8, + 92, + 42, + 186, + 166, + 6, + 36, + 32, + 92, + 191, + 130, + 10, + 246, + 113, + 154, + 38, + 135, + 208, + 214, + 186, + 205, + 31, + 11, + 187, + 54, + 88, + 85, + 68, + 217, + 49, + 78, + 195, + 40, + 10, + 143, + 25, + 14, + 15, + 56, + 74, + 51, + 155, + 119, + 221, + 30, + 195, + 109, + 171, + 80, + 42, + 194, + 27, + 215, + 140, + 133, + 78, + 208, + 48, + 94, + 224, + 52, + 78, + 226, + 195, + 33, + 12, + 109, + 210, + 136, + 76, + 251, + 192, + 171, + 203, + 110, + 146, + 197, + 209, + 241, + 104, + 169, + 240, + 169, + 88, + 240, + 177, + 218, + 124, + 218, + 82, + 211, + 92, + 138, + 150, + 209, + 247, + 82, + 246, + 167, + 150, + 233, + 51, + 204, + 68, + 132, + 85, + 135, + 23, + 22, + 0, + 174, + 141, + 18, + 208, + 141, + 162, + 57, + 124, + 12, + 34, + 213, + 51, + 235, + 10, + 149, + 7, + 227, + 131, + 3, + 181, + 172, + 47, + 216, + 240, + 59, + 66, + 210, + 78, + 131, + 67, + 30, + 72, + 183, + 214, + 19, + 160, + 39, + 68, + 82, + 83, + 224, + 161, + 183, + 225, + 225, + 194, + 245, + 87, + 188, + 172, + 154, + 84, + 168, + 137, + 162, + 18, + 175, + 179, + 8, + 90, + 244, + 138, + 66, + 201, + 164, + 173, + 18, + 163, + 40, + 68, + 24, + 225, + 100, + 111, + 15, + 152, + 241, + 41, + 146, + 138, + 158, + 219, + 179, + 242, + 96, + 124, + 152, + 96, + 119, + 28, + 188, + 144, + 182, + 183, + 170, + 85, + 211, + 198, + 32, + 5, + 211, + 82, + 104, + 102, + 172, + 123, + 29, + 237, + 37, + 178, + 136, + 27, + 52, + 144, + 68, + 107, + 27, + 48, + 203, + 225, + 250, + 173, + 221, + 198, + 172, + 201, + 162, + 197, + 213, + 153, + 118, + 76, + 83, + 99, + 57, + 179, + 234, + 27, + 86, + 51, + 123, + 119, + 230, + 180, + 51, + 144, + 10, + 84, + 89, + 43, + 209, + 221, + 142, + 103, + 185, + 233, + 170, + 221, + 212, + 200, + 73, + 111, + 206, + 165, + 2, + 221, + 183, + 230, + 90, + 118, + 69, + 217, + 205, + 249, + 114, + 71, + 16, + 109, + 69, + 95, + 213, + 45, + 81, + 128, + 56, + 152, + 143, + 38, + 112, + 46, + 31, + 138, + 185, + 30, + 221, + 98, + 209, + 62, + 180, + 64, + 141, + 80, + 197, + 62, + 220, + 15, + 51, + 153, + 150, + 179, + 16, + 203, + 179, + 127, + 67, + 228, + 143, + 70, + 252, + 12, + 7, + 141, + 255, + 202, + 96, + 49, + 164, + 127, + 68, + 96, + 49, + 120, + 123, + 49, + 86, + 242, + 15, + 193, + 147, + 173, + 63, + 225, + 240, + 61, + 194, + 217, + 209, + 59, + 220, + 59, + 252, + 142, + 29, + 158, + 120, + 131, + 123, + 131, + 223, + 175, + 193, + 99, + 132, + 113, + 230, + 29, + 238, + 29, + 190, + 230, + 48, + 126, + 107, + 220, + 137, + 203, + 19, + 20, + 165, + 222, + 228, + 222, + 228, + 107, + 14, + 119, + 243, + 26, + 79, + 16, + 14, + 177, + 119, + 184, + 119, + 248, + 154, + 195, + 93, + 189, + 198, + 99, + 20, + 123, + 143, + 123, + 143, + 175, + 57, + 252, + 207, + 111, + 241, + 60, + 184, + 254, + 165, + 252, + 19, + 80, + 75, + 1, + 2, + 10, + 0, + 10, + 0, + 0, + 0, + 8, + 0, + 64, + 84, + 87, + 84, + 206, + 210, + 135, + 28, + 85, + 2, + 0, + 0, + 134, + 22, + 0, + 0, + 52, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 33, + 104, + 101, + 100, + 101, + 110, + 115, + 116, + 114, + 111, + 101, + 109, + 46, + 99, + 111, + 109, + 33, + 49, + 54, + 52, + 53, + 52, + 56, + 56, + 48, + 48, + 48, + 33, + 49, + 54, + 52, + 53, + 53, + 55, + 52, + 51, + 57, + 57, + 46, + 120, + 109, + 108, + 80, + 75, + 5, + 6, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 0, + 98, + 0, + 0, + 0, + 167, + 2, + 0, + 0, + 0, + 0 + ] + }, + "contentType": "application/zip", + "partId": null, + "release": null, + "contentDisposition": "attachment", + "filename": "google.com!hedenstroem.com!1645488000!1645574399.zip", + "headers": {}, + "checksum": "ec791b24e94bd814161b16a875b889f5", + "size": 799 + } + ], + "headers": {}, + "headerLines": [ + { + "key": "received", + "line": "Received: by mail-qk1-f202.google.com with SMTP id w4-20020a05620a094400b0060dd52a1445so2827056qkw.3\r\n for <dmarc-rua@npc.hedenstroem.com>; Wed, 23 Feb 2022 02:56:28 -0800 (PST)" + }, + { + "key": "dkim-signature", + "line": "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=google.com; s=20210112;\r\n h=mime-version:date:message-id:subject:from:to:content-disposition\r\n :content-transfer-encoding;\r\n bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n b=nFD6sQTA/w8rNXXgj3LpFi1k1brhM1E/8v7nwa/DqfmJ6nH3+m7FSVVIiHR9LWc29Y\r\n 4FG8x6jzRBxssNoxNDRb8if2zT2GQq980NUFvZ5rKvNs2ltIoq5UntrJwhxG5OVhWRdy\r\n 8wJN3p9625htJgZnHobWNLengIV/byE328+FQ5tzmBj4dIPZy+BvifXVd8W4dJOevWWi\r\n cHc8ZLbLfH+5v30HrBMt3EmOsBoZ1ZKBNIVEil4FRPm33K8MhsF/TMO4PQVpwCOYeiI6\r\n R4SJdQ7MdIh00v901V8VAlwxPVzdfauEEP7nReIA3QdWoF6DPmdUznj3f7svbY9aPpdX\r\n Inlg==" + }, + { + "key": "x-google-dkim-signature", + "line": "X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=1e100.net; s=20210112;\r\n h=x-gm-message-state:mime-version:date:message-id:subject:from:to\r\n :content-disposition:content-transfer-encoding;\r\n bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n b=HlvrMEFpDsiDF7H75BGO2E842v8CE+oq1W6GO6ke2nmfmJdGUhiM7Q5sPl7CUxCa1y\r\n zABMXEljFClSlVzwipw+5te9T/8fD9Vbtt7f+zaQ3CnrYbJaim2eEnIxFZeIOfORU2R3\r\n EK9601ssdV25lsrK2Wio9Gili36MYOnPySJrsx7oADvJxTXOZAqE2JHMcWSD18LAVMTU\r\n ll0WbLLIcnVkXFOrSCZwYlGHSEl+xN19PKW89KQtgLXW5eHEX/7hM7258Q79JeAONAUh\r\n ZB3UbKWGMZuRZCKb26uq+USTr3Rr25rk8z2n5KrToS6CA8ZhlPQtd7s/W+X/h3i2dGif\r\n uWcg==" + }, + { + "key": "x-gm-message-state", + "line": "X-Gm-Message-State: AOAM531DHTdQpJQxcbv7FL0OKPNEseEmbHaLP4n1DR1O6xhowgJshg0M\r\n\tCuD03b/6SUyxSX6sS5VClg==" + }, + { + "key": "x-google-smtp-source", + "line": "X-Google-Smtp-Source: ABdhPJxhFPEXIoZIZSUvzD2hGuGVfsXAaHE6ZRDR2OKAjx+D9LfTGhwqfbhJMnR1wJoNb1Wr28JMSJk64GUgMg==" + }, + { + "key": "mime-version", + "line": "MIME-Version: 1.0" + }, + { + "key": "x-received", + "line": "X-Received: by 2002:ae9:ed53:0:b0:62c:da57:aa49 with SMTP id\r\n c80-20020ae9ed53000000b0062cda57aa49mr15872807qkg.570.1645613788036; Wed, 23\r\n Feb 2022 02:56:28 -0800 (PST)" + }, + { + "key": "date", + "line": "Date: Tue, 22 Feb 2022 15:59:59 -0800" + }, + { + "key": "message-id", + "line": "Message-ID: <3794603309710813670@google.com>" + }, + { + "key": "subject", + "line": "Subject: Report domain: hedenstroem.com Submitter: google.com Report-ID: 3794603309710813670" + }, + { + "key": "from", + "line": "From: noreply-dmarc-support@google.com" + }, + { + "key": "to", + "line": "To: dmarc-rua@npc.hedenstroem.com" + }, + { + "key": "content-type", + "line": "Content-Type: application/zip; \r\n\tname=\"google.com!hedenstroem.com!1645488000!1645574399.zip\"" + }, + { + "key": "content-disposition", + "line": "Content-Disposition: attachment; \r\n\tfilename=\"google.com!hedenstroem.com!1645488000!1645574399.zip\"" + }, + { + "key": "content-transfer-encoding", + "line": "Content-Transfer-Encoding: base64" + } + ], + "subject": "Report domain: hedenstroem.com Submitter: google.com Report-ID: 3794603309710813670", + "date": "2022-02-22T23:59:59.000Z", + "to": { + "value": [ + { + "address": "dmarc-rua@npc.hedenstroem.com", + "name": "" + } + ], + "html": "<span class=\"mp_address_group\"><a href=\"mailto:dmarc-rua@npc.hedenstroem.com\" class=\"mp_address_email\">dmarc-rua@npc.hedenstroem.com</a></span>", + "text": "dmarc-rua@npc.hedenstroem.com" + }, + "from": { + "value": [ + { + "address": "noreply-dmarc-support@google.com", + "name": "" + } + ], + "html": "<span class=\"mp_address_group\"><a href=\"mailto:noreply-dmarc-support@google.com\" class=\"mp_address_email\">noreply-dmarc-support@google.com</a></span>", + "text": "noreply-dmarc-support@google.com" + }, + "messageId": "<3794603309710813670@google.com>", + "html": false, + "raw": "ARC-Seal: i=1; a=rsa-sha256; t=1645613789; cv=none; d=forwardemail.net;\r\n s=default;\r\n b=Tz1L66qJb40/M0rNspnXXa7Ypl0RPmEdQFWIow8OVsR/rZrIkPq4eIeZTMtDt0Ll0XX5yJtrU\r\n N7xQDtUvgxwWNDzQ298KQkEb2PtEhkS0yQVighQ7IisCk5QekH4MLnXCm64/yodMjBpM/yeueGx\r\n c2pBHHgpyHQ/IRDpOB5p/Ag=\r\nARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=forwardemail.net; h=Content-Transfer-Encoding: Content-Type: To: From:\r\n Subject: Message-ID: Date: MIME-Version; q=dns/txt; s=default;\r\n t=1645613789; bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n b=pzV3MUKKK8uzt13rfC4vyrI+1ILiPE96IFxVc4Mukhc2GKKIkhfWWYK0x4eh6V4/JOEtmWdJx\r\n X/PoFJcWX3zpH8IO/GJLhFrXfZG/KpHqoGiOwHaueNn6aCRGFM6JdL6i2WP1VN1m5Yh144HdWWy\r\n 9RfztAqe6hbkG0tF0bbQkYc=\r\nARC-Authentication-Results: i=1; mx1.forwardemail.net;\r\n dkim=pass header.i=@google.com header.s=20210112 header.a=rsa-sha256 header.b=nFD6sQTA;\r\n spf=pass (mx1.forwardemail.net: domain of noreply-dmarc-support@google.com designates 209.85.222.202 as permitted sender)\r\n smtp.mailfrom=noreply-dmarc-support@google.com smtp.helo=mail-qk1-f202.google.com;\r\n dmarc=pass (p=REJECT arc=none) header.from=google.com header.d=google.com;\r\n bimi=none\r\nReceived-SPF: pass (mx1.forwardemail.net: domain of noreply-dmarc-support@google.com designates 209.85.222.202 as permitted sender) client-ip=209.85.222.202;\r\nAuthentication-Results: mx1.forwardemail.net;\r\n dkim=pass header.i=@google.com header.s=20210112 header.a=rsa-sha256 header.b=nFD6sQTA;\r\n spf=pass (mx1.forwardemail.net: domain of noreply-dmarc-support@google.com designates 209.85.222.202 as permitted sender)\r\n smtp.mailfrom=noreply-dmarc-support@google.com smtp.helo=mail-qk1-f202.google.com;\r\n dmarc=pass (p=REJECT arc=none) header.from=google.com header.d=google.com;\r\n bimi=none\r\nX-ForwardEmail-Sender: rfc822; noreply-dmarc-support@google.com\r\nX-ForwardEmail-Session-ID: roe5td23dh7szuxn\r\nX-ForwardEmail-Version: 8.3.0\r\nReceived: by mail-qk1-f202.google.com with SMTP id w4-20020a05620a094400b0060dd52a1445so2827056qkw.3\r\n for <dmarc-rua@npc.hedenstroem.com>; Wed, 23 Feb 2022 02:56:28 -0800 (PST)\r\nDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=google.com; s=20210112;\r\n h=mime-version:date:message-id:subject:from:to:content-disposition\r\n :content-transfer-encoding;\r\n bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n b=nFD6sQTA/w8rNXXgj3LpFi1k1brhM1E/8v7nwa/DqfmJ6nH3+m7FSVVIiHR9LWc29Y\r\n 4FG8x6jzRBxssNoxNDRb8if2zT2GQq980NUFvZ5rKvNs2ltIoq5UntrJwhxG5OVhWRdy\r\n 8wJN3p9625htJgZnHobWNLengIV/byE328+FQ5tzmBj4dIPZy+BvifXVd8W4dJOevWWi\r\n cHc8ZLbLfH+5v30HrBMt3EmOsBoZ1ZKBNIVEil4FRPm33K8MhsF/TMO4PQVpwCOYeiI6\r\n R4SJdQ7MdIh00v901V8VAlwxPVzdfauEEP7nReIA3QdWoF6DPmdUznj3f7svbY9aPpdX\r\n Inlg==\r\nX-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=1e100.net; s=20210112;\r\n h=x-gm-message-state:mime-version:date:message-id:subject:from:to\r\n :content-disposition:content-transfer-encoding;\r\n bh=Chkxofrq4hlRNCFb1IaHeRlL+/OSVU3OU6gtOKXd+fs=;\r\n b=HlvrMEFpDsiDF7H75BGO2E842v8CE+oq1W6GO6ke2nmfmJdGUhiM7Q5sPl7CUxCa1y\r\n zABMXEljFClSlVzwipw+5te9T/8fD9Vbtt7f+zaQ3CnrYbJaim2eEnIxFZeIOfORU2R3\r\n EK9601ssdV25lsrK2Wio9Gili36MYOnPySJrsx7oADvJxTXOZAqE2JHMcWSD18LAVMTU\r\n ll0WbLLIcnVkXFOrSCZwYlGHSEl+xN19PKW89KQtgLXW5eHEX/7hM7258Q79JeAONAUh\r\n ZB3UbKWGMZuRZCKb26uq+USTr3Rr25rk8z2n5KrToS6CA8ZhlPQtd7s/W+X/h3i2dGif\r\n uWcg==\r\nX-Gm-Message-State: AOAM531DHTdQpJQxcbv7FL0OKPNEseEmbHaLP4n1DR1O6xhowgJshg0M\r\n\tCuD03b/6SUyxSX6sS5VClg==\r\nX-Google-Smtp-Source: ABdhPJxhFPEXIoZIZSUvzD2hGuGVfsXAaHE6ZRDR2OKAjx+D9LfTGhwqfbhJMnR1wJoNb1Wr28JMSJk64GUgMg==\r\nMIME-Version: 1.0\r\nX-Received: by 2002:ae9:ed53:0:b0:62c:da57:aa49 with SMTP id\r\n c80-20020ae9ed53000000b0062cda57aa49mr15872807qkg.570.1645613788036; Wed, 23\r\n Feb 2022 02:56:28 -0800 (PST)\r\nDate: Tue, 22 Feb 2022 15:59:59 -0800\r\nMessage-ID: <3794603309710813670@google.com>\r\nSubject: Report domain: hedenstroem.com Submitter: google.com Report-ID: 3794603309710813670\r\nFrom: noreply-dmarc-support@google.com\r\nTo: dmarc-rua@npc.hedenstroem.com\r\nContent-Type: application/zip; \r\n\tname=\"google.com!hedenstroem.com!1645488000!1645574399.zip\"\r\nContent-Disposition: attachment; \r\n\tfilename=\"google.com!hedenstroem.com!1645488000!1645574399.zip\"\r\nContent-Transfer-Encoding: base64\r\n\r\nUEsDBAoAAAAIAEBUV1TO0occVQIAAIYWAAA0AAAAZ29vZ2xlLmNvbSFoZWRlbnN0cm9lbS5jb20h\r\nMTY0NTQ4ODAwMCExNjQ1NTc0Mzk5LnhtbO1Yy46jMBC8z1dEucfg8EpWDLOn/YLdM3JMQ6wB27LN\r\nPP5+TTCEIfvQSHtYRT4Fl7vb1dVlJJI/vXXt5gWUZoI/bjEKtxvgVFSMN4/bH9+/7Q7bzVPxkNcA\r\n1YnQ5+Jhs8kVSKFM2YEhFTFkwCwqVFNy0kHRCNG0gKjo8mAGxxjoCGsLLmyF9n1XdUTRne7lUO7r\r\nMm2MczlvRpGSCm4INSXjtSjOxkj9JQhcKrqmBiQgXL+CCvZxmiaH0Na6zR8LuzZYVUTZMU7DKAqP\r\nGQ4POEozm3fdHsNtq1AqwhvXjIVO0DBe4DRO4sMhDG3SiEz7wKvLbpLF0fFoqfCpWPCx2nzaUtNc\r\nipbR91L2p5bpM8xEhFWHFxYAro0S0I2iOXwMItUz6wqVB+ODA7WsL9jwO0LSToNDHki31hOgJ0RS\r\nU+Cht+HhwvVXvKyaVKiJohKvswha9IpCyaStEqMoRBjhZG8PmPEpkoqe27PyYHyYYHccvJC2t6pV\r\n08YgBdNSaGasex3tJbKIGzSQRGsbMMvh+q3dxqzJosXVmXZMU2M5s+obVjN7d+a0M5AKVFkr0d2O\r\nZ7npqt3UyElvzqUC3bfmWnZF2c35ckcQbUVf1S1RgDiYjyZwLh+KuR7dYtE+tECNUMU+3A8zmZaz\r\nEMuzf0Pkj0b8DAeN/8pgMaR/RGAxeHsxVvIPwZOtP+HwPcLZ0TvcO/yOHZ54g3uD36/BY4Rx5h3u\r\nHb7mMH5r3InLExSl3uTe5GsOd/MaTxAOsXe4d/iaw129xmMUe497j685/M9v8Ty4/qX8E1BLAQIK\r\nAAoAAAAIAEBUV1TO0occVQIAAIYWAAA0AAAAAAAAAAAAAAAAAAAAAABnb29nbGUuY29tIWhlZGVu\r\nc3Ryb2VtLmNvbSExNjQ1NDg4MDAwITE2NDU1NzQzOTkueG1sUEsFBgAAAAABAAEAYgAAAKcCAAAA\r\nAA==\r\n" +} \ No newline at end of file diff --git a/types/dmarc.go b/types/dmarc.go index 276e7f7d0e351b6f429dfb5c789fe393ec4c1586..88ec2df5ce0944ebed259f5726d4d1d1222c2d43 100644 --- a/types/dmarc.go +++ b/types/dmarc.go @@ -1,149 +1,90 @@ package types import ( - "bytes" - "encoding/xml" - "time" + "net" ) -// Content is the structure for processing data -type Content struct { - From string - Name string - Data *bytes.Buffer +// DateRange time period +type DateRange struct { + Begin int64 `xml:"begin"` + End int64 `xml:"end"` } -// Row is the dmarc row in a report -type Row struct { - SourceIP string - Count int64 - EvalDisposition string - EvalSPFAlign string - EvalDKIMAalign string - Reason string - DKIMDomain string - DKIMResult string - SPFDomain string - SPFResult string - IdentifierHFrom string -} - -// Rows is jus the report and the rows of a report -type Rows struct { - Report Report - Rows []Row -} - -// Report is the content of the report -type Report struct { - ID int64 - ReportBegin time.Time - ReportEnd time.Time - PolicyDomain string - ReportOrg string - ReportID string - ReportEmail string - ReportExtraContactInfo string - PolicyAdkim string - PolicyAspf string - PolicyP string - PolicySP string - PolicyPCT string - Count int64 - DKIMResult string - SPFResult string - Items int -} - -// Reports is the collection of reports -type Reports struct { - Reports []Report - LastPage int - CurPage int - NextPage int - TotalPages int - Pages []int -} - -type dateRange struct { - XMLName xml.Name `xml:"date_range"` - Begin int64 `xml:"begin"` - End int64 `xml:"end"` -} - -type reportMetadata struct { - XMLName xml.Name `xml:"report_metadata"` +// ReportMetadata for the report +type ReportMetadata struct { OrgName string `xml:"org_name"` Email string `xml:"email"` - ExtraContactInfo string `xml:"extra_contact_info,omitempty"` + ExtraContactInfo string `xml:"extra_contact_info"` ReportID string `xml:"report_id"` - DateRange dateRange `xml:"date_range"` + Date DateRange `xml:"date_range"` + Errors []string `xml:"error"` } -type policyPublished struct { - XMLName xml.Name `xml:"policy_published"` - Domain string `xml:"domain"` - ADKIM string `xml:"adkim"` - ASPF string `xml:"aspf"` - P string `xml:"p"` - SP string `xml:"sp"` - PCT string `xml:"pct"` +// PolicyPublished found in DNS +type PolicyPublished struct { + Domain string `xml:"domain"` + ADKIM string `xml:"adkim"` + ASPF string `xml:"aspf"` + P string `xml:"p"` + SP string `xml:"sp"` + Pct int `xml:"pct"` + Fo string `xml:"fo"` } -type reason struct { - XMLName xml.Name `xml:"reason"` - Type string `xml:"type"` - Comment string `xml:"comment"` +// PolicyEvaluated what was evaluated +type PolicyEvaluated struct { + Disposition string `xml:"disposition"` + DKIM string `xml:"dkim"` + SPF string `xml:"spf"` + Reasons []PolicyOverrideReason `xml:"reason,omitempty"` } -type policyEvaluated struct { - XMLName xml.Name `xml:"policy_evaluated"` - Disposition string `xml:"disposition"` - DKIM string `xml:"dkim"` - SPF string `xml:"spf"` - Reasons []reason `xml:"reason"` +// PolicyOverrideReason are the reasons that may affect DMARC disposition +// or execution thereof +type PolicyOverrideReason struct { + Type string `xml:"type"` + Comment string `xml:"comment"` } -type row struct { - XMLName xml.Name `xml:"row"` - SourceIP string `xml:"source_ip"` - Count int64 `xml:"count"` - PolicyEvaluated policyEvaluated `xml:"policy_evaluated"` -} - -type identify struct { - XMLName xml.Name `xml:"identifiers"` - HeaderFrom string `xml:"header_from"` +// Row for each IP address +type Row struct { + SourceIP net.IP `xml:"source_ip"` + Count int `xml:"count"` + Policy PolicyEvaluated `xml:"policy_evaluated"` } -type spf struct { - XMLName xml.Name `xml:"spf"` - Result string `xml:"result"` +// Identifiers headers checked +type Identifiers struct { + HeaderFrom string `xml:"header_from"` + EnvelopeFrom string `xml:"envelope_from"` + EnvelopeTo string `xml:"envelope_to,omitempty"` } -type dkim struct { - XMLName xml.Name `xml:"dkim"` - Result string `xml:"result"` +// Result for each IP +type Result struct { + Domain string `xml:"domain"` + Selector string `xml:"selector"` + Result string `xml:"result"` + HumanResult string `xml:"human_result"` } -type authResult struct { - XMLName xml.Name `xml:"auth_results"` - SPF []spf `xml:"spf"` - DKIM []dkim `xml:"dkim"` +// AuthResults for DKIM/SPF +type AuthResults struct { + DKIM []Result `xml:"dkim,omitempty"` + SPF []Result `xml:"spf,omitempty"` } -type record struct { - XMLName xml.Name `xml:"record"` - Rows []row `xml:"row"` - Identifiers identify `xml:"identifiers"` - AuthResults authResult `xml:"auth_results"` +// Record for each IP +type Record struct { + Row Row `xml:"row"` + Identifiers Identifiers `xml:"identifiers"` + AuthResults AuthResults `xml:"auth_results"` } -// Feedback contains the reports and file information +// Feedback the report itself type Feedback struct { - XMLName xml.Name `xml:"feedback"` - FromFile string - ReportMetadata reportMetadata `xml:"report_metadata"` - PolicyPublished policyPublished `xml:"policy_published"` - Records []record `xml:"record"` + Version float32 `xml:"version"` + Metadata ReportMetadata `xml:"report_metadata"` + Policy PolicyPublished `xml:"policy_published"` + Records []Record `xml:"record"` } diff --git a/utils/dmarc.go b/utils/dmarc.go new file mode 100644 index 0000000000000000000000000000000000000000..ef13526e6b9f0d1fb885917c69a0ad7befb051c7 --- /dev/null +++ b/utils/dmarc.go @@ -0,0 +1,197 @@ +package utils + +import ( + "encoding/xml" + "errors" + "net/http" + + dto "github.com/prometheus/client_model/go" + "github.com/prometheus/common/log" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/types" +) + +var prometheusLabels = []string{ + "reporter", + "from_domain", + "dkim_domain", + "spf_domain", +} + +var TotalCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "dmarc_total", + Help: "Number of reported messages", + }, + prometheusLabels, +) + +var CompliantCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "dmarc_compliant", + Help: "Number of DMARC compliant messages", + }, + prometheusLabels, +) + +var QuarantineCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "dmarc_quarantine", + Help: "Number of quarantined messages", + }, + prometheusLabels, +) + +var RejectCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "dmarc_reject", + Help: "Number of rejected messages", + }, + prometheusLabels, +) + +var SPFAlignedCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "dmarc_spf_aligned", + Help: "Number of SPF aligned messages", + }, + prometheusLabels, +) + +var SPFPassCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "dmarc_spf_pass", + Help: "Number of messages with raw SPF pass", + }, + prometheusLabels, +) + +var DKIMAlignedCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "dmarc_dkim_aligned", + Help: "Number of DKIM aligned messages", + }, + prometheusLabels, +) + +var DKIMPassCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "dmarc_dkim_pass", + Help: "Number of messages with raw DKIM pass", + }, + prometheusLabels, +) + +var counters = map[string]*prometheus.CounterVec{ + "dmarc_total": TotalCounter, + "dmarc_compliant": CompliantCounter, + "dmarc_quarantine": QuarantineCounter, + "dmarc_reject": RejectCounter, + "dmarc_spf_aligned": SPFAlignedCounter, + "dmarc_spf_pass": SPFPassCounter, + "dmarc_dkim_aligned": DKIMAlignedCounter, + "dmarc_dkim_pass": DKIMPassCounter, +} + +func getLabelValues(metric *dto.Metric) ([]string, error) { + var v []string + for _, l := range prometheusLabels { + for _, lv := range metric.Label { + if l == *lv.Name { + v = append(v, *lv.Value) + } + } + } + if len(v) == len(prometheusLabels) { + return v, nil + } + return nil, errors.New("missing labels") +} + +func Restore(mf map[string]*dto.MetricFamily) { + for name, counter := range counters { + if family, found := mf[name]; found { + for _, metric := range family.Metric { + if l, err := getLabelValues(metric); err == nil { + v := *metric.Counter.Value + counter.WithLabelValues(l...).Add(v) + } else { + log.Warn(err) + } + } + } + } +} + +func Parse(body []byte) ([]types.Feedback, error) { + contentType := http.DetectContentType(body) + switch contentType { + case "application/zip": + if files, err := Unzip(body); err == nil { + var reports []types.Feedback + for _, file := range files { + r, err := Parse(file.Data) + if err != nil { + return reports, err + } + reports = append(reports, r...) + } + return reports, nil + } else { + return nil, err + } + case "application/gzip": + case "application/x-gzip": + data, err := GUnzip(body) + if err != nil { + return nil, err + } + return Parse(data) + case "text/xml": + case "text/xml; charset=utf-8": + var feedback types.Feedback + if err := xml.Unmarshal(body, &feedback); err == nil { + return []types.Feedback{feedback}, nil + } else { + return nil, err + + } + } + return nil, errors.New("unsupported content type: " + contentType) +} + +func Analyze(reports []types.Feedback) { + for _, report := range reports { + for _, r := range report.Records { + LabelValues := []string{ + report.Metadata.OrgName, + r.Identifiers.HeaderFrom, + r.AuthResults.DKIM[0].Domain, + r.AuthResults.SPF[0].Domain, + } + TotalCounter.WithLabelValues(LabelValues...).Inc() + switch r.Row.Policy.Disposition { + case "quarantine": + QuarantineCounter.WithLabelValues(LabelValues...).Inc() + case "reject": + RejectCounter.WithLabelValues(LabelValues...).Inc() + case "none": + CompliantCounter.WithLabelValues(LabelValues...).Inc() + } + if r.Row.Policy.SPF == "pass" { + SPFAlignedCounter.WithLabelValues(LabelValues...).Inc() + } + if r.AuthResults.SPF[0].Result == "pass" { + SPFPassCounter.WithLabelValues(LabelValues...).Inc() + } + if r.Row.Policy.DKIM == "pass" { + DKIMAlignedCounter.WithLabelValues(LabelValues...).Inc() + } + if r.AuthResults.DKIM[0].Result == "pass" { + DKIMPassCounter.WithLabelValues(LabelValues...).Inc() + } + } + } +} diff --git a/utils/zip.go b/utils/zip.go index fd60f6f69b734e58bc1a5131d46789a42361cae6..5e4e951991dc81a1fc3cc4c22d72d4b0918be9b3 100644 --- a/utils/zip.go +++ b/utils/zip.go @@ -3,12 +3,29 @@ package utils import ( "archive/zip" "bytes" + "compress/gzip" "io/ioutil" "gitlab.hedenstroem.com/go/dmarc-prometheus-exporter/types" ) -func UnzipBytes(b []byte) ([]types.ZipFile, error) { +func GUnzip(b []byte) ([]byte, error) { + // TODO: prevent zip bombs + reader, err := gzip.NewReader(bytes.NewBuffer(b)) + if err != nil { + return nil, err + } + defer reader.Close() + var buffer bytes.Buffer + _, err = buffer.ReadFrom(reader) + if err != nil { + return nil, err + } + return buffer.Bytes(), nil +} + +func Unzip(b []byte) ([]types.ZipFile, error) { + // TODO: prevent zip bombs zipReader, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) if err != nil { return nil, err