diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 0af74fc3..e056a846 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -457,6 +457,7 @@ The configuration file contains the following sections: - `encryption`, integer. 0 means no encryption, 1 means `TLS`, 2 means `STARTTLS`. Default: `0`. - `domain`, string. Domain to use for `HELO` command, if empty `localhost` will be used. Default: blank. - `templates_path`, string. Path to the email templates. This can be an absolute path or a path relative to the config dir. Templates are searched within a subdirectory named "email" in the specified path. You can customize the email templates by simply specifying an alternate path and putting your custom templates there. + - `debug`, integer. Set to `1` to enable SMTP debug. Default: `0`.
Plugins diff --git a/go.mod b/go.mod index 5a1846d4..487c348a 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 github.com/fclairamb/ftpserverlib v0.21.0 github.com/fclairamb/go-log v0.4.1 - github.com/go-acme/lego/v4 v4.11.0 + github.com/go-acme/lego/v4 v4.12.0 github.com/go-chi/chi/v5 v5.0.8 github.com/go-chi/jwtauth/v5 v5.1.0 github.com/go-chi/render v1.0.2 @@ -145,7 +145,7 @@ require ( github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.10.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/spf13/cast v1.5.1 // indirect @@ -160,9 +160,9 @@ require ( golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230526015343-6ee61e4f9d5f // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230526015343-6ee61e4f9d5f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230526015343-6ee61e4f9d5f // indirect + google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/grpc v1.55.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index c04931de..3b0a16dc 100644 --- a/go.sum +++ b/go.sum @@ -951,8 +951,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= -github.com/go-acme/lego/v4 v4.11.0 h1:oIPoU7zBJoTfoVrbqk62+/2NsGCSgCVK1JtZSZZ28SU= -github.com/go-acme/lego/v4 v4.11.0/go.mod h1:dENL0J3/WughN2NLy0T35otK5k1EWCmXTwCw0+X5ZaE= +github.com/go-acme/lego/v4 v4.12.0 h1:jox3II6YRjt1EXvrymSQuSNgEUOcbUkF2je0kyuv6YM= +github.com/go-acme/lego/v4 v4.12.0/go.mod h1:UZoOlhVmUYP/N0z4tEbfUjoCNHRZNObzqWZtT76DIsc= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/jwtauth/v5 v5.1.0 h1:wJyf2YZ/ohPvNJBwPOzZaQbyzwgMZZceE1m8FOzXLeA= @@ -1799,8 +1799,8 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.10.0 h1:UkG7GPYkO4UZyLnyXjaWYcgOSONqwdBqFUT95ugmt6I= -github.com/prometheus/procfs v0.10.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prometheus/prometheus v0.35.0/go.mod h1:7HaLx5kEPKJ0GDgbODG0fZgXbQ8K/XjZNJXQmbmgQlY= github.com/prometheus/prometheus v0.42.0/go.mod h1:Pfqb/MLnnR2KK+0vchiaH39jXxvLMBk+3lnIGP4N7Vk= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -2816,12 +2816,12 @@ google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230526015343-6ee61e4f9d5f h1:DwRdHa3+SynqBR2tx3LVtzJrGooL9hg1OCAfBdQAk1A= -google.golang.org/genproto v0.0.0-20230526015343-6ee61e4f9d5f/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto/googleapis/api v0.0.0-20230526015343-6ee61e4f9d5f h1:dJhNU2ZodW2tHjMhmDOrcRSahqR0wgfOEBs8nSmVx5Y= -google.golang.org/genproto/googleapis/api v0.0.0-20230526015343-6ee61e4f9d5f/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230526015343-6ee61e4f9d5f h1:QNVuVEP2S7NNxLdNdOq0RiW3c9pW4gIpUUd+GAOjk1Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230526015343-6ee61e4f9d5f/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e h1:AZX1ra8YbFMSb7+1pI8S9v4rrgRR7jU1FmuFSSjTVcQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= diff --git a/internal/cmd/smtptest.go b/internal/cmd/smtptest.go index 40915bcb..12da6e5a 100644 --- a/internal/cmd/smtptest.go +++ b/internal/cmd/smtptest.go @@ -50,6 +50,7 @@ If the SMTP configuration is correct you should receive this email.`, os.Exit(1) } smtpConfig := config.GetSMTPConfig() + smtpConfig.Debug = 1 err = smtpConfig.Initialize(configDir, false) if err != nil { logger.ErrorToConsole("unable to initialize SMTP configuration: %v", err) diff --git a/internal/dataprovider/configs.go b/internal/dataprovider/configs.go index 74067d61..9ffe1854 100644 --- a/internal/dataprovider/configs.go +++ b/internal/dataprovider/configs.go @@ -152,6 +152,7 @@ type SMTPConfigs struct { AuthType int `json:"auth_type,omitempty"` Encryption int `json:"encryption,omitempty"` Domain string `json:"domain,omitempty"` + Debug int `json:"debug,omitempty"` } func (c *SMTPConfigs) isEmpty() bool { @@ -215,6 +216,7 @@ func (c *SMTPConfigs) getACopy() *SMTPConfigs { AuthType: c.AuthType, Encryption: c.Encryption, Domain: c.Domain, + Debug: c.Debug, } } diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index 12ab8aeb..441f2adc 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -2635,6 +2635,10 @@ func getSMTPConfigsFromPostFields(r *http.Request) *dataprovider.SMTPConfigs { if err != nil { encryption = 0 } + debug := 0 + if r.Form.Get("smtp_debug") != "" { + debug = 1 + } return &dataprovider.SMTPConfigs{ Host: r.Form.Get("smtp_host"), Port: port, @@ -2644,6 +2648,7 @@ func getSMTPConfigsFromPostFields(r *http.Request) *dataprovider.SMTPConfigs { AuthType: authType, Encryption: encryption, Domain: r.Form.Get("smtp_domain"), + Debug: debug, } } diff --git a/internal/logger/mail.go b/internal/logger/mail.go new file mode 100644 index 00000000..3a81eaf8 --- /dev/null +++ b/internal/logger/mail.go @@ -0,0 +1,48 @@ +// Copyright (C) 2019-2023 Nicola Murino +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package logger + +const ( + mailLogSender = "smtpclient" +) + +// MailAdapter is an adapter for mail.Logger +type MailAdapter struct { + ConnectionID string +} + +// Errorf emits a log at Error level +func (l *MailAdapter) Errorf(format string, v ...any) { + ErrorToConsole(format, v...) + Log(LevelError, mailLogSender, l.ConnectionID, format, v...) +} + +// Warnf emits a log at Warn level +func (l *MailAdapter) Warnf(format string, v ...any) { + WarnToConsole(format, v...) + Log(LevelWarn, mailLogSender, l.ConnectionID, format, v...) +} + +// Infof emits a log at Info level +func (l *MailAdapter) Infof(format string, v ...any) { + InfoToConsole(format, v...) + Log(LevelInfo, mailLogSender, l.ConnectionID, format, v...) +} + +// Debugf emits a log at Debug level +func (l *MailAdapter) Debugf(format string, v ...any) { + DebugToConsole(format, v...) + Log(LevelDebug, mailLogSender, l.ConnectionID, format, v...) +} diff --git a/internal/smtp/smtp.go b/internal/smtp/smtp.go index 05af0e7e..6858f717 100644 --- a/internal/smtp/smtp.go +++ b/internal/smtp/smtp.go @@ -25,6 +25,7 @@ import ( "sync" "time" + "github.com/rs/xid" "github.com/wneessen/go-mail" "github.com/drakkan/sftpgo/v2/internal/dataprovider" @@ -83,6 +84,7 @@ func (c *activeConfig) Set(cfg *dataprovider.SMTPConfigs) { AuthType: cfg.AuthType, Encryption: cfg.Encryption, Domain: cfg.Domain, + Debug: cfg.Debug, } } @@ -167,6 +169,8 @@ type Config struct { // Path to the email templates. This can be an absolute path or a path relative to the config dir. // Templates are searched within a subdirectory named "email" in the specified path TemplatesPath string `json:"templates_path" mapstructure:"templates_path"` + // Set to 1 to enable debug logs + Debug int `json:"debug" mapstructure:"debug"` } func (c *Config) isEqual(other *Config) bool { @@ -194,6 +198,9 @@ func (c *Config) isEqual(other *Config) bool { if c.Domain != other.Domain { return false } + if c.Debug != other.Debug { + return false + } return true } @@ -283,6 +290,13 @@ func (c *Config) getMailClientOptions() []mail.Option { if c.Domain != "" { options = append(options, mail.WithHELO(c.Domain)) } + if c.Debug > 0 { + options = append(options, + mail.WithLogger(&logger.MailAdapter{ + ConnectionID: xid.New().String(), + }), + mail.WithDebugLog()) + } return options } diff --git a/sftpgo.json b/sftpgo.json index 18172766..36ddb787 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -406,7 +406,8 @@ "auth_type": 0, "encryption": 0, "domain": "", - "templates_path": "templates" + "templates_path": "templates", + "debug": 0 }, "plugins": [] } \ No newline at end of file diff --git a/templates/webadmin/configs.html b/templates/webadmin/configs.html index c9505070..d522ae39 100644 --- a/templates/webadmin/configs.html +++ b/templates/webadmin/configs.html @@ -275,6 +275,14 @@ along with this program. If not, see . +
+
+ + +
+
+
@@ -352,6 +360,10 @@ along with this program. If not, see . $('#smtpTestResultModal').modal('show'); return; } + let debug = 0; + if ($('#idSMTPDebug').is(':checked')){ + debug = 1; + } $('#smtpSuccessMsg').hide(); $('#smtpErrorMsg').hide(); showSpinner(); @@ -360,7 +372,7 @@ along with this program. If not, see . url: "{{.ConfigsURL}}/smtp/test", type: 'POST', headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'}, - data: JSON.stringify({"host": $('#idSMTPHost').val(),"port": parseInt($('#idSMTPPort').val()),"from": $('#idSMTPFrom').val(),"user": $('#idSMTPUsername').val(),"password": $('#idSMTPPassword').val(),"auth_type": parseInt($('#idSMTPAuth').val()),"encryption": parseInt($('#idSMTPEncryption').val()), "domain": $('#idSMTPDomain').val(),"recipient": recipient}), + data: JSON.stringify({"host": $('#idSMTPHost').val(),"port": parseInt($('#idSMTPPort').val()),"from": $('#idSMTPFrom').val(),"user": $('#idSMTPUsername').val(),"password": $('#idSMTPPassword').val(),"auth_type": parseInt($('#idSMTPAuth').val()),"encryption": parseInt($('#idSMTPEncryption').val()), "domain": $('#idSMTPDomain').val(),"debug": debug, "recipient": recipient}), dataType: 'json', contentType: 'application/json; charset=utf-8', timeout: 15000,