From 82a1e4597f1244ac39c10052408e2dd2b802a6dc Mon Sep 17 00:00:00 2001 From: Alberto Bertogli Date: Thu, 13 Jul 2017 21:11:27 +0100 Subject: [PATCH] mda-lmtp: Add a very basic MDA that uses LMTP to do the mail delivery. mda-lmtp is a very basic MDA that uses LMTP to do the mail delivery. It takes command line arguments similar to maildrop or procmail, reads an email via standard input, and sends it over the given LMTP server. Supports connecting to LMTP servers over UNIX sockets and TCP. Since chasquid does not support direct LMTP local delivery, this can be used as a workaround instead. Example of use: $ mda-lmtp --addr localhost:1234 -f juan@casa -d jose < email --- .gitignore | 2 + Makefile | 11 ++- cmd/mda-lmtp/.gitignore | 2 + cmd/mda-lmtp/mda-lmtp.go | 125 +++++++++++++++++++++++++++++ cmd/mda-lmtp/test.sh | 21 +++++ cmd/mda-lmtp/test_tcp_success.cmy | 32 ++++++++ cmd/mda-lmtp/test_unix_failure.cmy | 31 +++++++ cmd/mda-lmtp/test_unix_success.cmy | 33 ++++++++ 8 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 cmd/mda-lmtp/.gitignore create mode 100644 cmd/mda-lmtp/mda-lmtp.go create mode 100755 cmd/mda-lmtp/test.sh create mode 100644 cmd/mda-lmtp/test_tcp_success.cmy create mode 100644 cmd/mda-lmtp/test_unix_failure.cmy create mode 100644 cmd/mda-lmtp/test_unix_success.cmy diff --git a/.gitignore b/.gitignore index ee7de93..02ae02d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,11 @@ /chasquid-util /smtp-check /spf-check +/mda-lmtp cmd/chasquid-util/chasquid-util cmd/smtp-check/smtp-check cmd/spf-check/spf-check +cmd/mda-lmtp/mda-lmtp # Exclude any .pem files, to prevent accidentally including test keys and # certificates. diff --git a/Makefile b/Makefile index 844a377..b1fd20e 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ endif default: chasquid -all: chasquid chasquid-util smtp-check spf-check +all: chasquid chasquid-util smtp-check spf-check mda-lmtp chasquid: @@ -30,16 +30,19 @@ smtp-check: spf-check: go build ${GOFLAGS} ./cmd/spf-check/ +mda-lmtp: + go build ${GOFLAGS} ./cmd/mda-lmtp/ test: go test ${GOFLAGS} ./... setsid -w ./test/run.sh setsid -w ./cmd/chasquid-util/test.sh + setsid -w ./cmd/mda-lmtp/test.sh -install-binaries: chasquid chasquid-util smtp-check +install-binaries: chasquid chasquid-util smtp-check mda-lmtp mkdir -p /usr/local/bin/ - cp -a chasquid chasquid-util smtp-check /usr/local/bin/ + cp -a chasquid chasquid-util smtp-check mda-lmtp /usr/local/bin/ install-config-skeleton: if ! [ -d /etc/chasquid ] ; then cp -arv etc / ; fi @@ -51,4 +54,4 @@ install-config-skeleton: fi -.PHONY: chasquid chasquid-util smtp-check spf-check test +.PHONY: chasquid chasquid-util smtp-check spf-check mda-lmtp test diff --git a/cmd/mda-lmtp/.gitignore b/cmd/mda-lmtp/.gitignore new file mode 100644 index 0000000..9e9a4ad --- /dev/null +++ b/cmd/mda-lmtp/.gitignore @@ -0,0 +1,2 @@ +mda-lmtp +*.log diff --git a/cmd/mda-lmtp/mda-lmtp.go b/cmd/mda-lmtp/mda-lmtp.go new file mode 100644 index 0000000..cee639a --- /dev/null +++ b/cmd/mda-lmtp/mda-lmtp.go @@ -0,0 +1,125 @@ +// mda-lmtp is a very basic MDA that uses LMTP to do the delivery. +// +// See the usage below for details. +package main + +import ( + "flag" + "fmt" + "io" + "net" + "net/textproto" + "os" + "strings" +) + +// Command-line flags +var ( + fromwhom = flag.String("f", "", "Whom the message is from") + recipient = flag.String("d", "", "Recipient") + + addrNetwork = flag.String("addr_network", "", + "Network of the LMTP address (e.g. unix or tcp)") + addr = flag.String("addr", "", "LMTP server address") +) + +func usage() { + fmt.Fprintf(os.Stderr, ` +mda-lmtp is a very basic MDA that uses LMTP to do the mail delivery. + +It takes command line arguments similar to maildrop or procmail, reads an +email via standard input, and sends it over the given LMTP server. +Supports connecting to LMTP servers over UNIX sockets and TCP. + +It can be used when your mail server does not support LMTP directly. + +Example of use: +$ mda-lmtp --addr localhost:1234 -f juan@casa -d jose < email + +Flags: +`) + flag.PrintDefaults() +} + +// Exit with EX_TEMPFAIL. +func tempExit(format string, args ...interface{}) { + fmt.Printf(format+"\n", args...) + // 75 = EX_TEMPFAIL "temporary failure" exit code (sysexits.h). + os.Exit(75) +} + +func main() { + flag.Usage = usage + flag.Parse() + + if *addr == "" { + fmt.Printf("No LMTP server address given (use --addr)\n") + os.Exit(2) + } + + // Try to autodetect the network if it's missing. + if *addrNetwork == "" { + *addrNetwork = "tcp" + if strings.HasPrefix(*addr, "/") { + *addrNetwork = "unix" + } + } + + conn, err := net.Dial(*addrNetwork, *addr) + if err != nil { + tempExit("Error connecting to (%s, %s): %v", + *addrNetwork, *addr, err) + } + + tc := textproto.NewConn(conn) + + // Expect the hello from the server. + _, _, err = tc.ReadResponse(220) + if err != nil { + tempExit("Server greeting error: %v", err) + } + + hostname, err := os.Hostname() + if err != nil { + tempExit("Could not get hostname: %v", err) + } + + cmd(tc, 250, "LHLO %s", hostname) + cmd(tc, 250, "MAIL FROM:<%s>", *fromwhom) + cmd(tc, 250, "RCPT TO:<%s>", *recipient) + cmd(tc, 354, "DATA") + + w := tc.DotWriter() + _, err = io.Copy(w, os.Stdin) + w.Close() + if err != nil { + tempExit("Error writing DATA: %v", err) + } + + // This differs from SMTP: here we get one reply per recipient, with the + // result of the delivery. Since we deliver to only one recipient, read + // one code. + _, _, err = tc.ReadResponse(250) + if err != nil { + tempExit("Delivery failed remotely: %v", err) + } + + cmd(tc, 221, "QUIT") + + tc.Close() +} + +// cmd sends a command and checks it matched the expected code. +func cmd(conn *textproto.Conn, expectCode int, format string, args ...interface{}) { + id, err := conn.Cmd(format, args...) + if err != nil { + tempExit("Sent %q, got %v", fmt.Sprintf(format, args...), err) + } + conn.StartResponse(id) + defer conn.EndResponse(id) + + _, _, err = conn.ReadResponse(expectCode) + if err != nil { + tempExit("Sent %q, got %v", fmt.Sprintf(format, args...), err) + } +} diff --git a/cmd/mda-lmtp/test.sh b/cmd/mda-lmtp/test.sh new file mode 100755 index 0000000..26f4364 --- /dev/null +++ b/cmd/mda-lmtp/test.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e +. $(dirname ${0})/../../test/util/lib.sh + +init + +# Build the binary once, so we can use it and launch it in chamuyero scripts. +# Otherwise, we not only spend time rebuilding it over and over, but also "go +# run" masks the exit code, which is something we care about. +go build + +for i in *.cmy; do + if ! chamuyero $i > $i.log 2>&1 ; then + echo "# Test $i failed, log follows" + cat $i.log + exit 1 + fi +done + +success diff --git a/cmd/mda-lmtp/test_tcp_success.cmy b/cmd/mda-lmtp/test_tcp_success.cmy new file mode 100644 index 0000000..f3b3256 --- /dev/null +++ b/cmd/mda-lmtp/test_tcp_success.cmy @@ -0,0 +1,32 @@ + +nc tcp_listen localhost:14932 + +mda |= ./mda-lmtp --addr=localhost:14932 -f from -d to < .data + +nc -> 220 Hola desde expect + +nc <~ LHLO .* +nc -> 250-Bienvenido! +nc -> 250 Contame... + +nc <- MAIL FROM: +nc -> 250 Aja + +nc <- RCPT TO: +nc -> 250 Aja + +nc <- DATA +nc -> 354 Dale + +nc <- Subject: test +nc <- +nc <- This is a test. +nc <- . + +nc -> 250 Recibido + +nc <- QUIT +nc -> 221 Chauchas + +mda wait 0 + diff --git a/cmd/mda-lmtp/test_unix_failure.cmy b/cmd/mda-lmtp/test_unix_failure.cmy new file mode 100644 index 0000000..0bb8c26 --- /dev/null +++ b/cmd/mda-lmtp/test_unix_failure.cmy @@ -0,0 +1,31 @@ + +nc unix_listen .test-sock + +mda = ./mda-lmtp --addr=.test-sock --addr_network=unix \ + -f from -d to < .data + +nc -> 220 Hola desde expect + +nc <~ LHLO .* +nc -> 250-Bienvenido! +nc -> 250 Contame... + +nc <- MAIL FROM: +nc -> 250 Aja + +nc <- RCPT TO: +nc -> 250 Aja + +nc <- DATA +nc -> 354 Dale + +nc <- Subject: test +nc <- +nc <- This is a test. +nc <- . + +nc -> 452 Nananana + +mda <- Delivery failed remotely: 452 Nananana +mda wait 75 + diff --git a/cmd/mda-lmtp/test_unix_success.cmy b/cmd/mda-lmtp/test_unix_success.cmy new file mode 100644 index 0000000..37f3597 --- /dev/null +++ b/cmd/mda-lmtp/test_unix_success.cmy @@ -0,0 +1,33 @@ + +nc unix_listen .test-sock + +mda |= ./mda-lmtp --addr=.test-sock --addr_network=unix \ + -f from -d to < .data + +nc -> 220 Hola desde expect + +nc <~ LHLO .* +nc -> 250-Bienvenido! +nc -> 250 Contame... + +nc <- MAIL FROM: +nc -> 250 Aja + +nc <- RCPT TO: +nc -> 250 Aja + +nc <- DATA +nc -> 354 Dale + +nc <- Subject: test +nc <- +nc <- This is a test. +nc <- . + +nc -> 250 Recibido + +nc <- QUIT +nc -> 221 Chauchas + +mda wait 0 +