diff --git a/go.mod b/go.mod
index 846fd8c9..9def7be7 100644
--- a/go.mod
+++ b/go.mod
@@ -60,7 +60,7 @@ require (
github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62
github.com/unrolled/secure v1.13.0
github.com/wagslane/go-password-validator v0.3.0
- github.com/xhit/go-simple-mail/v2 v2.11.0
+ github.com/xhit/go-simple-mail/v2 v2.12.0
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a
go.etcd.io/bbolt v1.3.6
go.uber.org/automaxprocs v1.5.1
@@ -68,16 +68,16 @@ require (
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7
golang.org/x/net v0.0.0-20220923203811-8be639271d50
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
- golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25
+ golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af
- google.golang.org/api v0.97.0
+ google.golang.org/api v0.98.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)
require (
cloud.google.com/go v0.104.0 // indirect
cloud.google.com/go/compute v1.10.0 // indirect
- cloud.google.com/go/iam v0.4.0 // indirect
+ cloud.google.com/go/iam v0.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect
@@ -156,7 +156,7 @@ require (
golang.org/x/tools v0.1.12 // 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-20220923205249-dd2d53f1fffc // indirect
+ google.golang.org/genproto v0.0.0-20220927151529-dcaddaf36704 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/go.sum b/go.sum
index 845ba4e0..a4aac07a 100644
--- a/go.sum
+++ b/go.sum
@@ -55,8 +55,8 @@ cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx
cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
-cloud.google.com/go/iam v0.4.0 h1:YBYU00SCDzZJdHqVc4I5d6lsklcYIjQZa1YmEz4jlSE=
-cloud.google.com/go/iam v0.4.0/go.mod h1:cbaZxyScUhxl7ZAkNWiALgihfP75wS/fUsVNaa1r3vA=
+cloud.google.com/go/iam v0.5.0 h1:fz9X5zyTWBmamZsqvqZqD7khbifcZF/q+Z1J8pfhIUg=
+cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI=
cloud.google.com/go/kms v1.4.0 h1:iElbfoE61VeLhnZcGOltqL8HIly8Nhbe5t6JlH9GXjo=
cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
@@ -774,8 +774,8 @@ github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFs
github.com/unrolled/secure v1.13.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
-github.com/xhit/go-simple-mail/v2 v2.11.0 h1:o/056V50zfkO3Mm5tVdo9rG3ryg4ZmJ2XW5GMinHfVs=
-github.com/xhit/go-simple-mail/v2 v2.11.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
+github.com/xhit/go-simple-mail/v2 v2.12.0 h1:KweA6NO8Z6fZyeckMPNpvElU6QDIyBShlpce1sYUZgg=
+github.com/xhit/go-simple-mail/v2 v2.12.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a h1:XfF01GyP+0eWCaVp0y6rNN+kFp7pt9Da4UUYrJ5XPWA=
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a/go.mod h1:aXb8yZQEWo1XHGMf1qQfnb83GR/EJ2EBlwtUgAaNBoE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -981,8 +981,8 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 h1:nwzwVf0l2Y/lkov/+IYgMMbFyI+QypZDds9RxlSmsFQ=
-golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1124,8 +1124,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
-google.golang.org/api v0.97.0 h1:x/vEL1XDF/2V4xzdNgFPaKHluRESo2aTsL7QzHnBtGQ=
-google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.98.0 h1:yxZrcxXESimy6r6mdL5Q6EnZwmewDJK2dVg3g75s5Dg=
+google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1230,8 +1230,8 @@ google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
-google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc h1:saaNe2+SBQxandnzcD/qB1JEBQ2Pqew+KlFLLdA/XcM=
-google.golang.org/genproto v0.0.0-20220923205249-dd2d53f1fffc/go.mod h1:yEEpwVWKMZZzo81NwRgyEJnA2fQvpXAYPVisv8EgDVs=
+google.golang.org/genproto v0.0.0-20220927151529-dcaddaf36704 h1:H1AcWFV69NFCMeBJ8nVLtv8uHZZ5Ozcgoq012hHEFuU=
+google.golang.org/genproto v0.0.0-20220927151529-dcaddaf36704/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
diff --git a/internal/common/dataretention.go b/internal/common/dataretention.go
index bde900cc..b58768d2 100644
--- a/internal/common/dataretention.go
+++ b/internal/common/dataretention.go
@@ -29,6 +29,8 @@ import (
"sync"
"time"
+ mail "github.com/xhit/go-simple-mail/v2"
+
"github.com/drakkan/sftpgo/v2/internal/command"
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
"github.com/drakkan/sftpgo/v2/internal/httpclient"
@@ -350,41 +352,41 @@ func (c *RetentionCheck) sendNotifications(elapsed time.Duration, err error) {
for _, notification := range c.Notifications {
switch notification {
case RetentionCheckNotificationEmail:
- c.sendEmailNotification(elapsed, err) //nolint:errcheck
+ c.sendEmailNotification(err) //nolint:errcheck
case RetentionCheckNotificationHook:
c.sendHookNotification(elapsed, err) //nolint:errcheck
}
}
}
-func (c *RetentionCheck) sendEmailNotification(elapsed time.Duration, errCheck error) error {
- body := new(bytes.Buffer)
- data := make(map[string]any)
- data["Results"] = c.results
- totalDeletedFiles := 0
- totalDeletedSize := int64(0)
- for _, result := range c.results {
- totalDeletedFiles += result.DeletedFiles
- totalDeletedSize += result.DeletedSize
+func (c *RetentionCheck) sendEmailNotification(errCheck error) error {
+ params := EventParams{}
+ if len(c.results) > 0 || errCheck != nil {
+ params.retentionChecks = append(params.retentionChecks, executedRetentionCheck{
+ Username: c.conn.User.Username,
+ ActionName: "Retention check",
+ Results: c.results,
+ })
}
- data["HumanizeSize"] = util.ByteCountIEC
- data["TotalFiles"] = totalDeletedFiles
- data["TotalSize"] = totalDeletedSize
- data["Elapsed"] = elapsed
- data["Username"] = c.conn.User.Username
- data["StartTime"] = util.GetTimeFromMsecSinceEpoch(c.StartTime)
- if errCheck == nil {
- data["Status"] = "Succeeded"
- } else {
- data["Status"] = "Failed"
- }
- if err := smtp.RenderRetentionReportTemplate(body, data); err != nil {
- c.conn.Log(logger.LevelError, "unable to render retention check template: %v", err)
+ var files []mail.File
+ f, err := params.getRetentionReportsAsMailAttachment()
+ if err != nil {
+ c.conn.Log(logger.LevelError, "unable to get retention report as mail attachment: %v", err)
return err
}
+ f.Name = "retention-report.zip"
+ files = append(files, f)
+
startTime := time.Now()
- subject := fmt.Sprintf("Retention check completed for user %#v", c.conn.User.Username)
- if err := smtp.SendEmail([]string{c.Email}, subject, body.String(), smtp.EmailContentTypeTextHTML); err != nil {
+ var subject string
+ if errCheck == nil {
+ subject = fmt.Sprintf("Successful retention check for user %q", c.conn.User.Username)
+ } else {
+ subject = fmt.Sprintf("Retention check failed for user %q", c.conn.User.Username)
+ }
+ body := "Further details attached."
+ err = smtp.SendEmail([]string{c.Email}, subject, body, smtp.EmailContentTypeTextPlain, files...)
+ if err != nil {
c.conn.Log(logger.LevelError, "unable to notify retention check result via email: %v, elapsed: %v", err,
time.Since(startTime))
return err
diff --git a/internal/common/dataretention_test.go b/internal/common/dataretention_test.go
index 8ee10b04..3b7dc9a3 100644
--- a/internal/common/dataretention_test.go
+++ b/internal/common/dataretention_test.go
@@ -146,21 +146,36 @@ func TestRetentionEmailNotifications(t *testing.T) {
conn.ID = fmt.Sprintf("data_retention_%v", user.Username)
check.conn = conn
check.sendNotifications(1*time.Second, nil)
- err = check.sendEmailNotification(1*time.Second, nil)
+ err = check.sendEmailNotification(nil)
assert.NoError(t, err)
- err = check.sendEmailNotification(1*time.Second, errors.New("test error"))
+ err = check.sendEmailNotification(errors.New("test error"))
assert.NoError(t, err)
+ check.results = nil
+ err = check.sendEmailNotification(nil)
+ if assert.Error(t, err) {
+ assert.Contains(t, err.Error(), "no data retention report available")
+ }
+
smtpCfg.Port = 2626
err = smtpCfg.Initialize(configDir)
require.NoError(t, err)
- err = check.sendEmailNotification(1*time.Second, nil)
+ err = check.sendEmailNotification(nil)
assert.Error(t, err)
+ check.results = []folderRetentionCheckResult{
+ {
+ Path: "/",
+ Retention: 24,
+ DeletedFiles: 20,
+ DeletedSize: 456789,
+ Elapsed: 12 * time.Second,
+ },
+ }
smtpCfg = smtp.Config{}
err = smtpCfg.Initialize(configDir)
require.NoError(t, err)
- err = check.sendEmailNotification(1*time.Second, nil)
+ err = check.sendEmailNotification(nil)
assert.Error(t, err)
}
diff --git a/internal/smtp/smtp.go b/internal/smtp/smtp.go
index 617a102f..f99c2b85 100644
--- a/internal/smtp/smtp.go
+++ b/internal/smtp/smtp.go
@@ -43,9 +43,8 @@ const (
)
const (
- templateEmailDir = "email"
- templateRetentionCheckResult = "retention-check-report.html"
- templatePasswordReset = "reset-password.html"
+ templateEmailDir = "email"
+ templatePasswordReset = "reset-password.html"
)
var (
@@ -156,24 +155,13 @@ func (c *Config) getAuthType() mail.AuthType {
func loadTemplates(templatesPath string) {
logger.Debug(logSender, "", "loading templates from %#v", templatesPath)
- retentionCheckPath := filepath.Join(templatesPath, templateRetentionCheckResult)
- retentionTmpl := util.LoadTemplate(nil, retentionCheckPath)
passwordResetPath := filepath.Join(templatesPath, templatePasswordReset)
pwdResetTmpl := util.LoadTemplate(nil, passwordResetPath)
- emailTemplates[templateRetentionCheckResult] = retentionTmpl
emailTemplates[templatePasswordReset] = pwdResetTmpl
}
-// RenderRetentionReportTemplate executes the retention report template
-func RenderRetentionReportTemplate(buf *bytes.Buffer, data any) error {
- if smtpServer == nil {
- return errors.New("smtp: not configured")
- }
- return emailTemplates[templateRetentionCheckResult].Execute(buf, data)
-}
-
// RenderPasswordResetTemplate executes the password reset template
func RenderPasswordResetTemplate(buf *bytes.Buffer, data any) error {
if smtpServer == nil {
diff --git a/pkgs/build.sh b/pkgs/build.sh
index 1f7a948e..44ca30c7 100755
--- a/pkgs/build.sh
+++ b/pkgs/build.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-NFPM_VERSION=2.18.1
+NFPM_VERSION=2.19.1
NFPM_ARCH=${NFPM_ARCH:-amd64}
if [ -z ${SFTPGO_VERSION} ]
then
diff --git a/templates/email/retention-check-report.html b/templates/email/retention-check-report.html
deleted file mode 100644
index a0aa5b68..00000000
--- a/templates/email/retention-check-report.html
+++ /dev/null
@@ -1,46 +0,0 @@
-
-Retention check report for user "{{.Username}}"
-
-Status: {{.Status}}
-
-Start time: {{.StartTime}}
-
-Total files deleted: {{.TotalFiles}}
-
-Total size deleted: {{call .HumanizeSize .TotalSize}}
-
-Elapsed: {{.Elapsed}}
-
-{{range .Results -}}
-
- Path: {{.Path}}
- {{- if .Error}}
-
- Error: {{.Error}}
- {{- end}}
- {{- if .Info}}
-
- Info: {{.Info}}
- {{- end}}
-
- Retention: {{.Retention}} hours
-
- Files deleted: {{.DeletedFiles}}
-
- Size deleted: {{call $.HumanizeSize .DeletedSize}}
-