Skip to content

Sunil Nimmagadda

Stub filter

Filters support for smtpd(8) landed in OpenBSD tree. gilles@ published a Python bridge and a case study using it with rspamd. Don't miss updates and other interesting entries from his blog.

A stub filter, as the name suggests, registers for the hooks it's interested in and lets the SMTP session go through unaltered. A version from an earlier iteration was instrumental in learning how filters fit in. Writing a similar one with the current code is even more simpler. Filters are standalone programs that smtpd(8) fork/execs and the filter communicates with smtpd(8) via its stdin/stdout. With this basic /etc/mail/smtpd.conf…

table aliases file:/etc/mail/aliases
filter stub proc-exec "/usr/local/bin/stub"
listen on lo0 filter stub
action "local" mbox alias <aliases>;
action "relay" relay
match for local action "local"
match for any action "relay"

build the following golang code and place the executable in /usr/local/bin as stub…

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	hooks := []string{
		"connect",
		"ehlo",
		"mail-from",
		"rcpt-to",
		"data",
		"data-line",
		"quit",
	}
	for _, hook := range hooks {
		fmt.Printf("register|filter|smtp-in|%s\n", hook)
	}
	fmt.Println("register|ready")
	stdin := bufio.NewScanner(os.Stdin)
	for stdin.Scan() {
		fields := strings.Split(stdin.Text(), "|")
		event, token, session_id := fields[4], fields[5], fields[6]
		switch event {
		case "data-line":
			fmt.Printf("filter-dataline|%s|%s|%s\n",
				token, session_id,
				strings.Join(fields[7:], "|"))
		default:
			fmt.Printf("filter-result|%s|%s|proceed\n",
				token, session_id)
		}
	}
}

The stub registration, its ready notification and communicating with smtpd(8) during an SMTP session appear like this…

client - smtpd              smtpd - filter
--------------              --------------
                            F: register|filter|smtp-in|connect
                            F: register|filter|smtp-in|ehlo
                            F: register|filter|smtp-in|mail-from
                            F: register|filter|smtp-in|rcpt-to
                            F: register|filter|smtp-in|data
                            F: register|filter|smtp-in|data-line
                            F: register|filter|smtp-in|quit
                            F: register|ready
C: nc localhost 25
                            S: filter|1|15455|smtp...
                            F: filter-result|9405c|49974f|proceed
S: 220 foo ESMTP OpenSMTPD
C: EHLO localhost
                            S: filter|1|15455|smtp-in|ehlo|9405c|49974f|localhost
                            F: filter-result|9405c1f7|d9d49974f|proceed
S: 250-foo Hello localhost [127.0.0.1], pleased to meet you
S: 250-8BITMIME
S: 250-ENHANCEDSTATUSCODES
S: 250-SIZE 36700160
S: 250-DSN
S: 250 HELP
...
...

Where C = client, S = smtpd, F = Stub filter