From 3c27fd715b035b2a10db3cc96518f39a8e728567 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 3 Jun 2023 10:34:22 +1200 Subject: [PATCH 1/3] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9c01070..5e068e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /node_modules/ /send +/sendmail/sendmail /server/ui/dist /Makefile /mailpit* From c01f473e79eb6eefa4fd546f558dfc8b88dda3bd Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 3 Jun 2023 10:57:13 +1200 Subject: [PATCH 2/3] Feature: Add `sendmail -bs` functionality Symfony's mailer uses `sendmail -bs` by default. This adds the required internal telnet functionality to connect directly to the SMTP server. --- cmd/sendmail.go | 33 ++++++------ go.mod | 4 +- go.sum | 8 ++- sendmail/cmd/cmd.go | 121 ++++++++++++++++++++++++++++++++------------ 4 files changed, 113 insertions(+), 53 deletions(-) diff --git a/cmd/sendmail.go b/cmd/sendmail.go index 0f3db02..2e9369c 100644 --- a/cmd/sendmail.go +++ b/cmd/sendmail.go @@ -1,23 +1,18 @@ package cmd import ( + "os" + sendmail "github.com/axllent/mailpit/sendmail/cmd" "github.com/spf13/cobra" ) -var ( - smtpAddr = "localhost:1025" - fromAddr string -) - // sendmailCmd represents the sendmail command var sendmailCmd = &cobra.Command{ Use: "sendmail [flags] [recipients]", Short: "A sendmail command replacement for Mailpit", - Long: `A sendmail command replacement for Mailpit. - -You can optionally create a symlink called 'sendmail' to the Mailpit binary.`, Run: func(_ *cobra.Command, _ []string) { + sendmail.Run() }, } @@ -25,13 +20,17 @@ You can optionally create a symlink called 'sendmail' to the Mailpit binary.`, func init() { rootCmd.AddCommand(sendmailCmd) - // these are simply repeated for cli consistency - sendmailCmd.Flags().StringVarP(&fromAddr, "from", "f", fromAddr, "SMTP sender") - sendmailCmd.Flags().StringVarP(&smtpAddr, "smtp-addr", "S", smtpAddr, "SMTP server address") - sendmailCmd.Flags().BoolVarP(&sendmail.Verbose, "verbose", "v", false, "Verbose mode (sends debug output to stderr)") - sendmailCmd.Flags().BoolP("long-b", "b", false, "Ignored. This flag exists for sendmail compatibility.") - sendmailCmd.Flags().BoolP("long-i", "i", false, "Ignored. This flag exists for sendmail compatibility.") - sendmailCmd.Flags().BoolP("long-o", "o", false, "Ignored. This flag exists for sendmail compatibility.") - sendmailCmd.Flags().BoolP("long-s", "s", false, "Ignored. This flag exists for sendmail compatibility.") - sendmailCmd.Flags().BoolP("long-t", "t", false, "Ignored. This flag exists for sendmail compatibility.") + // print out manual help screen + rootCmd.SetHelpTemplate(sendmail.HelpTemplate(os.Args[0:2])) + + // these are simply repeated for cli consistency as cobra/viper does not allow + // multi-letter single-dash variables (-bs) + sendmailCmd.Flags().StringVarP(&sendmail.FromAddr, "from", "f", sendmail.FromAddr, "SMTP sender") + sendmailCmd.Flags().StringVarP(&sendmail.SMTPAddr, "smtp-addr", "S", sendmail.SMTPAddr, "SMTP server address") + sendmailCmd.Flags().BoolVarP(&sendmail.UseB, "long-b", "b", false, "Handle SMTP commands on standard input (use as -bs)") + sendmailCmd.Flags().BoolVarP(&sendmail.UseS, "long-s", "s", false, "Handle SMTP commands on standard input (use as -bs)") + sendmailCmd.Flags().BoolP("verbose", "v", false, "Verbose mode (sends debug output to stderr)") + sendmailCmd.Flags().Bool("i", false, "Ignored") + sendmailCmd.Flags().Bool("o", false, "Ignored") + sendmailCmd.Flags().Bool("t", false, "Ignored") } diff --git a/go.mod b/go.mod index 7455dce..1a2f602 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/leporo/sqlf v1.4.0 github.com/mattn/go-shellwords v1.0.12 github.com/mhale/smtpd v0.8.0 + github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.9.2 github.com/spf13/cobra v1.7.0 @@ -40,6 +41,7 @@ require ( github.com/mattn/go-runewidth v0.0.14 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/reiver/go-oi v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect @@ -49,7 +51,7 @@ require ( golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/sys v0.8.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.9.3 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect lukechampine.com/uint128 v1.3.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect diff --git a/go.sum b/go.sum index 30e3582..68ce3d8 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM= +github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI= +github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas= +github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -175,8 +179,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/sendmail/cmd/cmd.go b/sendmail/cmd/cmd.go index ffa19f1..951e57a 100644 --- a/sendmail/cmd/cmd.go +++ b/sendmail/cmd/cmd.go @@ -3,8 +3,16 @@ package cmd /** * Bare bones sendmail drop-in replacement borrowed from MailHog + * + * It uses a bit of a hack for flag parsing in order to be compatible + * with the cobra sendmail subcommand, as sendmail uses `-bc` which + * is not POSIX compatible. + * + * The -bs command-line switch causes sendmail to run a single SMTP session in the + * foreground over its standard input and output, and then exit. The SMTP session + * is exactly like a network SMTP session. Usually, one or more messages are + * submitted to sendmail for delivery. */ - import ( "bytes" "fmt" @@ -13,21 +21,27 @@ import ( "net/smtp" "os" "os/user" + "strings" "github.com/axllent/mailpit/config" "github.com/axllent/mailpit/utils/logger" + "github.com/reiver/go-telnet" flag "github.com/spf13/pflag" ) var ( - // Verbose flag - Verbose bool + // SMTPAddr address + SMTPAddr = "localhost:1025" + // FromAddr email address + FromAddr string - fromAddr string + // UseB - used to set from `-bs` + UseB bool + // UseS - used to set from `-bs` + UseS bool ) -// Run the Mailpit sendmail replacement. -func Run() { +func init() { host, err := os.Hostname() if err != nil { host = "localhost" @@ -39,47 +53,68 @@ func Run() { username = user.Username } - if fromAddr == "" { - fromAddr = username + "@" + host + if FromAddr == "" { + FromAddr = username + "@" + host } +} - smtpAddr := "localhost:1025" - var recip []string +// Run the Mailpit sendmail replacement. +func Run() { + var recipients []string // defaults from envars if provided if len(os.Getenv("MP_SENDMAIL_SMTP_ADDR")) > 0 { - smtpAddr = os.Getenv("MP_SENDMAIL_SMTP_ADDR") + SMTPAddr = os.Getenv("MP_SENDMAIL_SMTP_ADDR") } if len(os.Getenv("MP_SENDMAIL_FROM")) > 0 { - fromAddr = os.Getenv("MP_SENDMAIL_FROM") + FromAddr = os.Getenv("MP_SENDMAIL_FROM") } - // override defaults from cli flags - flag.StringVarP(&fromAddr, "from", "f", fromAddr, "SMTP sender address") - flag.StringVarP(&smtpAddr, "smtp-addr", "S", smtpAddr, "SMTP server address") - flag.BoolVarP(&Verbose, "verbose", "v", false, "Verbose mode (sends debug output to stderr)") - flag.BoolP("long-b", "b", false, "Ignored. This flag exists for sendmail compatibility.") - flag.BoolP("long-i", "i", false, "Ignored. This flag exists for sendmail compatibility.") - flag.BoolP("long-o", "o", false, "Ignored. This flag exists for sendmail compatibility.") - flag.BoolP("long-s", "s", false, "Ignored. This flag exists for sendmail compatibility.") - flag.BoolP("long-t", "t", false, "Ignored. This flag exists for sendmail compatibility.") - flag.CommandLine.SortFlags = false + flag.StringVarP(&FromAddr, "from", "f", FromAddr, "SMTP sender") + flag.StringVarP(&SMTPAddr, "smtp-addr", "S", SMTPAddr, "SMTP server address") + flag.BoolVarP(&UseB, "long-b", "b", false, "Handle SMTP commands on standard input (use as -bs)") + flag.BoolVarP(&UseS, "long-s", "s", false, "Handle SMTP commands on standard input (use as -bs)") + flag.BoolP("verbose", "v", false, "Ignored") + flag.Bool("i", false, "Ignored") + flag.Bool("o", false, "Ignored") + flag.Bool("t", false, "Ignored") // set the default help flag.Usage = func() { - fmt.Printf("A sendmail command replacement for Mailpit (%s).\n\n", config.Version) - fmt.Printf("Usage:\n %s [flags] [recipients]\n", os.Args[0]) - fmt.Println("\nFlags:") - flag.PrintDefaults() + fmt.Println(HelpTemplate(os.Args[0:1])) } + var showHelp bool + // avoid 'pflag: help requested' error + flag.BoolVarP(&showHelp, "help", "h", false, "") + flag.Parse() - // allow recipient to be passed as an argument - recip = flag.Args() + // allow recipients to be passed as an argument + recipients = flag.Args() - if Verbose { - fmt.Fprintln(os.Stdout, smtpAddr, fromAddr) + if showHelp { + flag.Usage() + os.Exit(0) + } + + // ensure -bs is set + if UseB && !UseS || !UseB && UseS { + fmt.Printf("error: use -bs") + os.Exit(1) + } + + // handles `sendmail -bs` + if UseB && UseS { + var caller telnet.Caller = telnet.StandardCaller + + // telnet directly to SMTP + if err := telnet.DialToAndCall(SMTPAddr, caller); err != nil { + fmt.Println(err) + os.Exit(1) + } + + return } body, err := ioutil.ReadAll(os.Stdin) @@ -96,8 +131,8 @@ func Run() { addresses := []string{} - if len(recip) > 0 { - addresses = recip + if len(recipients) > 0 { + addresses = recipients } else { // get all recipients in To, Cc and Bcc if to, err := msg.Header.AddressList("To"); err == nil { @@ -117,9 +152,29 @@ func Run() { } } - err = smtp.SendMail(smtpAddr, nil, fromAddr, addresses, body) + err = smtp.SendMail(SMTPAddr, nil, FromAddr, addresses, body) if err != nil { fmt.Fprintln(os.Stderr, "error sending mail") logger.Log().Fatal(err) } } + +// HelpTemplate returns a string of the help +func HelpTemplate(args []string) string { + fmt.Println(args) + return fmt.Sprintf(`A sendmail command replacement for Mailpit (%s) + +Usage: %s [flags] [recipients] < message + +See: https://github.com/axllent/mailpit + +Flags: + -S string SMTP server address (default "localhost:1025") + -f string Set the envelope sender address (default "%s") + -bs Handle SMTP commands on standard input + -t Ignored + -i Ignored + -o Ignored + -v Ignored +`, config.Version, strings.Join(args, " "), FromAddr) +} From 0b391b5c3795d2d01f1910dbcaefae8575797af4 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Sat, 3 Jun 2023 11:02:53 +1200 Subject: [PATCH 3/3] Fix error return value --- server/smtpd/smtpd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/smtpd/smtpd.go b/server/smtpd/smtpd.go index 9d39833..4460d12 100644 --- a/server/smtpd/smtpd.go +++ b/server/smtpd/smtpd.go @@ -90,7 +90,7 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error { _, err = storage.Store(data) if err != nil { - logger.Log().Errorf("[db] error storing message: %d", err.Error()) + logger.Log().Errorf("[db] error storing message: %s", err.Error()) return err }