git » chasquid » commit 82a1e45

mda-lmtp: Add a very basic MDA that uses LMTP to do the mail delivery.

author Alberto Bertogli
2017-07-13 20:11:27 UTC
committer Alberto Bertogli
2017-07-13 21:05:45 UTC
parent 10427d7f49d8ce074a702713541658d2d1dbe97e

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 -0
Makefile +7 -4
cmd/mda-lmtp/.gitignore +2 -0
cmd/mda-lmtp/mda-lmtp.go +125 -0
cmd/mda-lmtp/test.sh +21 -0
cmd/mda-lmtp/test_tcp_success.cmy +32 -0
cmd/mda-lmtp/test_unix_failure.cmy +31 -0
cmd/mda-lmtp/test_unix_success.cmy +33 -0

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:<from>
+nc -> 250 Aja
+
+nc <- RCPT TO:<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:<from>
+nc -> 250 Aja
+
+nc <- RCPT TO:<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:<from>
+nc -> 250 Aja
+
+nc <- RCPT TO:<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
+