From df4035ef1e0f52181254776eecb6b5cc6ef1cc42 Mon Sep 17 00:00:00 2001 From: Thomas Maurice Date: Mon, 12 Feb 2024 17:37:00 +0100 Subject: [PATCH 1/7] fix(ci): remove unused code --- .goreleaser.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 848db8e..fc9e92c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -18,8 +18,6 @@ builds: -X git.maurice.fr/thomas/mailout/pkg/version.Version={{ .Version }} -X git.maurice.fr/thomas/mailout/pkg/version.Commit={{ .Commit }} -X git.maurice.fr/thomas/mailout/pkg/version.BuildTime={{ .Date }} - ignore: - - goarch: "386" archives: - format: tar.gz name_template: >- @@ -65,15 +63,6 @@ changelog: order: 400 - title: Other miscellaneous work order: 500 -# nfpms: -# - maintainer: Carlos A Becker -# description: Sample project. -# homepage: https://github.com/caarlos0/tasktimer -# license: MIT -# formats: -# - deb -# - rpm -# - apk release: prerelease: auto gitea_urls: From 837db0c6ec8212f7956614daeca950b8c8f1b352 Mon Sep 17 00:00:00 2001 From: Thomas Maurice Date: Mon, 12 Feb 2024 17:43:24 +0100 Subject: [PATCH 2/7] feat(ci): updates goreleaser action to v5 --- .gitea/workflows/on_tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/on_tag.yml b/.gitea/workflows/on_tag.yml index 2c793a7..1e36a2a 100644 --- a/.gitea/workflows/on_tag.yml +++ b/.gitea/workflows/on_tag.yml @@ -18,7 +18,7 @@ jobs: - run: go mod tidy - run: go test -v ./... - run: git reset --hard - - uses: goreleaser/goreleaser-action@v2 + - uses: goreleaser/goreleaser-action@v5 if: success() && startsWith(github.ref, 'refs/tags/') with: version: latest From 3ccd9366016bfe224a4b6db6b1488e5268f6cb15 Mon Sep 17 00:00:00 2001 From: Thomas Maurice Date: Mon, 12 Feb 2024 18:00:54 +0100 Subject: [PATCH 3/7] imp(deps): upgrade deps --- go.mod | 18 ++++++++++-------- go.sum | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 40d47c5..d6cfcc6 100644 --- a/go.mod +++ b/go.mod @@ -4,36 +4,38 @@ go 1.22.0 require ( github.com/google/uuid v1.6.0 + github.com/ovh/go-ovh v1.4.3 github.com/pkg/errors v0.9.1 github.com/pterm/pterm v0.12.79 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.19.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.5.6 - gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde + gorm.io/gorm v1.25.7 ) require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect - github.com/containerd/console v1.0.3 // indirect + github.com/containerd/console v1.0.4 // indirect github.com/gookit/color v1.5.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgx/v5 v5.5.3 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/ovh/go-ovh v1.4.3 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 7386b25..5261150 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYew github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= +github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -29,8 +31,14 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s= +github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -63,6 +71,8 @@ github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IG github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -84,6 +94,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -93,6 +105,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -104,6 +118,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -114,6 +129,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -142,3 +159,5 @@ gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU= gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg= gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= From 09d3149932c7581d15fdc1d90545fbf1d1675dc9 Mon Sep 17 00:00:00 2001 From: Thomas Maurice Date: Tue, 13 Feb 2024 18:30:54 +0100 Subject: [PATCH 4/7] feat(mail): adds a test subcomand --- pkg/cmd/root.go | 12 ++++++- pkg/cmd/test.go | 85 ++++++++++++++++++++++++++++++++++++++++++++ pkg/config/config.go | 5 +++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 pkg/cmd/test.go diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index ce86c25..bd5029b 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -1,6 +1,9 @@ package cmd import ( + "os" + "path" + "git.maurice.fr/thomas/mailout/pkg/config" "github.com/spf13/cobra" ) @@ -32,6 +35,13 @@ func InitRootCmd() { RootCmd.AddCommand(InitDBCmd) RootCmd.AddCommand(UserCmd) RootCmd.AddCommand(DKIMKeyCmd) + RootCmd.AddCommand(TestCmd) - RootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "mailout.yml", "Configuration file") + homeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + defaultConfigFile := path.Join(homeDir, ".mailout.yml") + + RootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", defaultConfigFile, "Configuration file") } diff --git a/pkg/cmd/test.go b/pkg/cmd/test.go new file mode 100644 index 0000000..6d99200 --- /dev/null +++ b/pkg/cmd/test.go @@ -0,0 +1,85 @@ +package cmd + +import ( + "crypto/tls" + "fmt" + "log" + "net/smtp" + "strings" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var TestCmd = &cobra.Command{ + Use: "test", + Short: "sends an email through the configured server", + Long: ``, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + if cfg.Test == nil { + logrus.Fatal("you need to specify a `test` config block") + } + + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: strings.Split(cfg.Test.Address, ":")[0], + } + + conn, err := tls.Dial("tcp", cfg.Test.Address, tlsConfig) + if err != nil { + log.Panic(err) + } + + c, err := smtp.NewClient(conn, strings.Split(cfg.Test.Address, ":")[0]) + if err != nil { + log.Panic(err) + } + + headers := make(map[string]string) + headers["From"] = cfg.Test.Username + headers["To"] = args[0] + headers["Subject"] = "This is a test email from the command line" + + message := "" + for k, v := range headers { + message += fmt.Sprintf("%s: %s\r\n", k, v) + } + message += "\r\n" + + message += "This is a test email message sent through the command line utility." + + auth := smtp.PlainAuth("", cfg.Test.Username, cfg.Test.Password, strings.Split(cfg.Test.Address, ":")[0]) + + if err = c.Auth(auth); err != nil { + logrus.WithError(err).Fatal("could not authenticate to server") + } + + if err = c.Mail(cfg.Test.Username); err != nil { + logrus.WithError(err).Fatal("could not create email") + } + + if err = c.Rcpt(args[0]); err != nil { + logrus.WithError(err).Fatal("could not set email destination") + } + + w, err := c.Data() + if err != nil { + logrus.WithError(err).Fatal("could not set email data") + } + + _, err = w.Write([]byte(message)) + if err != nil { + logrus.WithError(err).Fatal("could not set email data") + } + + err = w.Close() + if err != nil { + logrus.WithError(err).Fatal("close email") + } + + c.Quit() + + logrus.Info("sent test email") + }, +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 15811e8..a282700 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -20,6 +20,11 @@ type Config struct { Providers struct { OVH *providerConfigs.OVHConfig `yaml:"ovh"` } `yaml:"providers"` + Test *struct { + Address string `yaml:"address"` + Username string `yaml:"username"` + Password string `yaml:"password"` + } `yaml:"test"` } func LoadConfig(path string) (*Config, error) { From d08f82ba66883f0c2434c8d21cad1ddaa646488c Mon Sep 17 00:00:00 2001 From: Thomas Maurice Date: Wed, 14 Feb 2024 18:50:35 +0100 Subject: [PATCH 5/7] feat(misc): allows user edition, templating of the home, and more --- README.md | 11 ++++- docker-compose.yml | 2 +- mailout.yml.sample | 7 ++- pkg/cmd/dkimkey.go | 8 ++-- pkg/cmd/root.go | 1 + pkg/cmd/test.go | 14 +++++- pkg/cmd/user.go | 109 +++++++++++++++++++++++++++++++++++++++++-- pkg/config/config.go | 7 ++- 8 files changed, 144 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index dd1a208..f6feb64 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,9 @@ For authentication, you want to change your `/etc/dovecot/dovecot-sql.conf.ext` ``` driver = pgsql connect = host=YOURHOST port=5432 user=YOURUSER password=APASSWORD dbname=DBNAME -password_query = SELECT password, username AS user FROM users WHERE username = '%n' AND domain = '%d' -user_query = SELECT maildir, 1000 AS uid, 1000 AS gid FROM users WHERE username = '%n' AND domain = '%d' AND active = '1' + +password_query = SELECT concat(username, '@', domain) AS user, password FROM users WHERE username = '%n' AND domain = '%d' AND active = true +user_query = SELECT home, 1000 AS uid, 1000 AS gid FROM users WHERE username = '%n' AND domain = '%d' AND active = true ``` ### OpenDKIM @@ -60,6 +61,12 @@ postgres: user: postgres password: postgres123 sslmode: disable +defaults: + provider: ovh + # this is used to create the `home` field of the user. + # in systems with virtual mail this corresponds to the physical + # location of the vmail directory on your host + homeTemplate: "/var/lib/vmail/{{ .Domain }}/{{ .Username }}" providers: ovh: application_key: diff --git a/docker-compose.yml b/docker-compose.yml index a4628df..a3c0e92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,4 +12,4 @@ services: volumes: - postgres:/var/lib/postgresql/15/main volumes: - postgres: {} \ No newline at end of file + postgres: {} diff --git a/mailout.yml.sample b/mailout.yml.sample index 038cb20..445a136 100644 --- a/mailout.yml.sample +++ b/mailout.yml.sample @@ -5,7 +5,12 @@ postgres: user: postgres password: postgres123 sslmode: disable -defaultProvider: ovh +defaults: + provider: ovh + # this is used to create the `home` field of the user. + # in systems with virtual mail this corresponds to the physical + # location of the vmail directory on your host + homeTemplate: "/var/lib/vmail/{{ .Domain }}/{{ .Username }}" providers: ovh: application_key: diff --git a/pkg/cmd/dkimkey.go b/pkg/cmd/dkimkey.go index 1be46b7..caddd4a 100644 --- a/pkg/cmd/dkimkey.go +++ b/pkg/cmd/dkimkey.go @@ -27,10 +27,10 @@ var DKIMKeyCmd = &cobra.Command{ Short: "manages DKIM keys", PreRunE: func(cmd *cobra.Command, args []string) error { if flagProvider == "" { - if cfg.DefaultProvider == "" { + if cfg.Defaults.Provider == "" { logrus.Fatal("no provider specified and no default provider in config, aborting") } - flagProvider = cfg.DefaultProvider + flagProvider = cfg.Defaults.Provider } return nil @@ -240,7 +240,7 @@ var DKIMKeyPublishCmd = &cobra.Command{ pv := flagProvider if flagProvider == "" { - pv = cfg.DefaultProvider + pv = cfg.Defaults.Provider if pv == "" { logrus.Fatal("no provider specified") } @@ -299,7 +299,7 @@ var DKIMKeyUnpublishCmd = &cobra.Command{ pv := flagProvider if flagProvider == "" { - pv = cfg.DefaultProvider + pv = cfg.Defaults.Provider if pv == "" { logrus.Fatal("no provider specified") } diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index bd5029b..4ce4124 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -30,6 +30,7 @@ var RootCmd = &cobra.Command{ func InitRootCmd() { InitUserCmd() InitDKIMKeyCmd() + InitTestCmd() RootCmd.AddCommand(VersionCmd) RootCmd.AddCommand(InitDBCmd) diff --git a/pkg/cmd/test.go b/pkg/cmd/test.go index 6d99200..f173ace 100644 --- a/pkg/cmd/test.go +++ b/pkg/cmd/test.go @@ -11,6 +11,11 @@ import ( "github.com/spf13/cobra" ) +var ( + flagTestMessage string + flagTestMessageSubject string +) + var TestCmd = &cobra.Command{ Use: "test", Short: "sends an email through the configured server", @@ -39,7 +44,7 @@ var TestCmd = &cobra.Command{ headers := make(map[string]string) headers["From"] = cfg.Test.Username headers["To"] = args[0] - headers["Subject"] = "This is a test email from the command line" + headers["Subject"] = flagTestMessageSubject message := "" for k, v := range headers { @@ -47,7 +52,7 @@ var TestCmd = &cobra.Command{ } message += "\r\n" - message += "This is a test email message sent through the command line utility." + message += flagTestMessage auth := smtp.PlainAuth("", cfg.Test.Username, cfg.Test.Password, strings.Split(cfg.Test.Address, ":")[0]) @@ -83,3 +88,8 @@ var TestCmd = &cobra.Command{ logrus.Info("sent test email") }, } + +func InitTestCmd() { + TestCmd.PersistentFlags().StringVarP(&flagTestMessage, "message", "m", "This is a test email message sent through the command line utility.", "What to send as a test message") + TestCmd.PersistentFlags().StringVarP(&flagTestMessageSubject, "subject", "s", "This is a test email from the command line", "What to send as a test message subject") +} diff --git a/pkg/cmd/user.go b/pkg/cmd/user.go index 4cb794e..dbd72f7 100644 --- a/pkg/cmd/user.go +++ b/pkg/cmd/user.go @@ -1,7 +1,9 @@ package cmd import ( + "bytes" "fmt" + "text/template" "git.maurice.fr/thomas/mailout/pkg/database" "git.maurice.fr/thomas/mailout/pkg/models" @@ -12,7 +14,12 @@ import ( ) var ( - flagUserActive bool + flagUserActive bool + flagUserHome string + flagUserQuota int64 + flagUserPassword string + flagUserGID int + flagUserUID int ) var UserCmd = &cobra.Command{ @@ -73,6 +80,24 @@ var UserAddCmd = &cobra.Command{ Active: flagUserActive, Password: fmt.Sprintf("{BLF-CRYPT}%s", string(passwordHash)), } + + if flagUserHome == "" { + flagUserHome = cfg.Defaults.HomeTemplate + } + + tmpl, err := template.New("").Parse(flagUserHome) + if err != nil { + logrus.WithError(err).Fatal("could not parse the default home template") + } + + buf := bytes.NewBufferString("") + err = tmpl.Execute(buf, user) + if err != nil { + logrus.WithError(err).Fatal("could not render template") + } + + user.Home = buf.String() + err = db.Save(&user).Error if err != nil { @@ -100,10 +125,10 @@ var UserListCmd = &cobra.Command{ } tData := pterm.TableData{ - {"id", "username", "domain", "active", "uid", "gid", "created_at", "updated_at"}, + {"id", "username", "domain", "active", "uid", "gid", "home"}, } for _, u := range qRes { - tData = append(tData, []string{u.ID.String(), u.Username, u.Domain, fmt.Sprintf("%v", u.Active), fmt.Sprintf("%d", u.UID), fmt.Sprintf("%d", u.GID), u.CreatedAt.String(), u.UpdatedAt.String()}) + tData = append(tData, []string{u.ID.String(), u.Username, u.Domain, fmt.Sprintf("%v", u.Active), fmt.Sprintf("%d", u.UID), fmt.Sprintf("%d", u.GID), u.Home}) } pterm.DefaultTable.WithHasHeader().WithData(tData).Render() @@ -182,12 +207,90 @@ var UserDeactivateCmd = &cobra.Command{ }, } +var UserEditCmd = &cobra.Command{ + Use: "edit", + Short: "edites a user", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + db, err := database.NewDB(cfg) + if err != nil { + logrus.WithError(err).Fatal("could not connect to the database") + } + + userQuery, err := buildUserQuery(args[0]) + if err != nil { + logrus.WithError(err).Fatal("unable to determine user") + } + + qRes := make([]models.User, 0) + + err = db.Model(&models.User{}).Find(&qRes, userQuery).Error + if err != nil { + logrus.WithError(err).Fatal("could not preload user") + } + + if len(qRes) == 0 { + logrus.Fatal("no such user") + } + + user := qRes[0] + + if flagUserHome != "" { + tmpl, err := template.New("").Parse(flagUserHome) + if err != nil { + logrus.WithError(err).Fatal("could not parse the default home template") + } + + buf := bytes.NewBufferString("") + err = tmpl.Execute(buf, user) + if err != nil { + logrus.WithError(err).Fatal("could not render home template") + } + + user.Home = buf.String() + } + + if flagUserPassword != "" { + passwordHash, err := bcrypt.GenerateFromPassword([]byte(args[1]), bcrypt.DefaultCost) + if err != nil { + logrus.WithError(err).Fatal("could not compute user password hash") + } + + user.Password = fmt.Sprintf("{BLF-CRYPT}%s", string(passwordHash)) + } + + if flagUserGID != -1 { + user.GID = flagUserGID + } + + if flagUserUID != -1 { + user.UID = flagUserUID + } + + err = db.Save(&user).Error + + if err != nil { + logrus.WithError(err).Fatal("could not update user") + } + + logrus.Infof("edited user %s", args[0]) + }, +} + func InitUserCmd() { UserCmd.AddCommand(UserAddCmd) + UserCmd.AddCommand(UserEditCmd) UserCmd.AddCommand(UserListCmd) UserCmd.AddCommand(UserDeleteCmd) UserCmd.AddCommand(UserActivateCmd) UserCmd.AddCommand(UserDeactivateCmd) UserAddCmd.PersistentFlags().BoolVarP(&flagUserActive, "active", "a", true, "whether or not the created user is active") + UserAddCmd.PersistentFlags().StringVarP(&flagUserHome, "home", "", "", "template to use for user's home directories") + + UserEditCmd.PersistentFlags().StringVarP(&flagUserPassword, "password", "p", "", "User password") + UserEditCmd.PersistentFlags().StringVarP(&flagUserHome, "home", "", "", "home (user's mailbox)") + UserEditCmd.PersistentFlags().Int64VarP(&flagUserQuota, "quota", "q", -1, "Quota in bytes for the user") + UserEditCmd.PersistentFlags().IntVarP(&flagUserUID, "uid", "u", -1, "user's uid") + UserEditCmd.PersistentFlags().IntVarP(&flagUserGID, "gid", "g", -1, "user's gid") } diff --git a/pkg/config/config.go b/pkg/config/config.go index a282700..eccb9c0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -16,8 +16,11 @@ type Config struct { Database string `yaml:"database"` SSLMode string `yaml:"sslmode"` } `yaml:"postgres"` - DefaultProvider string `yaml:"defaultProvider"` - Providers struct { + Defaults struct { + HomeTemplate string `yaml:"homeTemplate"` + Provider string `yaml:"provider"` + } `yaml:"defaults"` + Providers struct { OVH *providerConfigs.OVHConfig `yaml:"ovh"` } `yaml:"providers"` Test *struct { From 8eb41be04ad022c2ba6fccace748ff34e80ff807 Mon Sep 17 00:00:00 2001 From: Thomas Maurice Date: Fri, 16 Feb 2024 23:39:38 +0100 Subject: [PATCH 6/7] fix(README): update --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index f6feb64..7ace2b8 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,6 @@ The plan is basically: * you have one for the DKIM keys you use * you have a view to expose the relevant key to use for opendkim -## Warning -I halfed assed that shit on an afternoon to suit my own needs, improvements will follow, or not. - ## Features * Create mail users @@ -75,4 +72,4 @@ providers: endpoint: ovh-eu ``` -pointing to your DB/OVH account and you're good \ No newline at end of file +pointing to your DB/OVH account and you're good From ea1c8254b47a89692362edbc46e2afd90b3f9c8b Mon Sep 17 00:00:00 2001 From: Thomas Maurice Date: Fri, 28 Jun 2024 12:04:29 +0200 Subject: [PATCH 7/7] fix(user cmd): fix password update panic --- pkg/cmd/user.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/user.go b/pkg/cmd/user.go index dbd72f7..5828aad 100644 --- a/pkg/cmd/user.go +++ b/pkg/cmd/user.go @@ -208,7 +208,7 @@ var UserDeactivateCmd = &cobra.Command{ } var UserEditCmd = &cobra.Command{ - Use: "edit", + Use: "edit ", Short: "edites a user", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { @@ -251,7 +251,7 @@ var UserEditCmd = &cobra.Command{ } if flagUserPassword != "" { - passwordHash, err := bcrypt.GenerateFromPassword([]byte(args[1]), bcrypt.DefaultCost) + passwordHash, err := bcrypt.GenerateFromPassword([]byte(flagUserPassword), bcrypt.DefaultCost) if err != nil { logrus.WithError(err).Fatal("could not compute user password hash") }