diff --git a/doc.go b/doc.go index 88e692d..27e996f 100644 --- a/doc.go +++ b/doc.go @@ -50,6 +50,7 @@ low-maintenance self-hosted email. mox dkim lookup selector domain mox dkim txt <$selector._domainkey.$domain.key.pkcs8.pem mox dkim verify message + mox dkim sign message mox dmarc lookup domain mox dmarc parsereportmsg message ... mox dmarc verify remoteip mailfromaddress helodomain < message @@ -503,6 +504,16 @@ that was passed. usage: mox dkim verify message +# mox dkim sign + +Sign a message, adding DKIM-Signature headers based on the domain in the From header. + +The message is parsed, the domain looked up in the configuration files, and +DKIM-Signature headers generated. The message is printed with the DKIM-Signature +headers prepended. + + usage: mox dkim sign message + # mox dmarc lookup Lookup dmarc policy for domain, a DNS TXT record at _dmarc., validate and print it. diff --git a/main.go b/main.go index 1079376..7c60b65 100644 --- a/main.go +++ b/main.go @@ -116,6 +116,7 @@ var commands = []struct { {"dkim lookup", cmdDKIMLookup}, {"dkim txt", cmdDKIMTXT}, {"dkim verify", cmdDKIMVerify}, + {"dkim sign", cmdDKIMSign}, {"dmarc lookup", cmdDMARCLookup}, {"dmarc parsereportmsg", cmdDMARCParsereportmsg}, {"dmarc verify", cmdDMARCVerify}, @@ -1272,6 +1273,51 @@ that was passed. } } +func cmdDKIMSign(c *cmd) { + c.params = "message" + c.help = `Sign a message, adding DKIM-Signature headers based on the domain in the From header. + +The message is parsed, the domain looked up in the configuration files, and +DKIM-Signature headers generated. The message is printed with the DKIM-Signature +headers prepended. +` + args := c.Parse() + if len(args) != 1 { + c.Usage() + } + + msgf, err := os.Open(args[0]) + xcheckf(err, "open message") + defer msgf.Close() + + p, err := message.Parse(msgf) + xcheckf(err, "parsing message") + + if len(p.Envelope.From) != 1 { + log.Fatalf("found %d from headers, need exactly 1", len(p.Envelope.From)) + } + localpart := smtp.Localpart(p.Envelope.From[0].User) + dom, err := dns.ParseDomain(p.Envelope.From[0].Host) + xcheckf(err, "parsing domain in from header") + + mustLoadConfig() + + domConf, ok := mox.Conf.Domain(dom) + if !ok { + log.Fatalf("domain %s not configured", dom) + } + + headers, err := dkim.Sign(context.Background(), localpart, dom, domConf.DKIM, false, msgf) + xcheckf(err, "signing message with dkim") + if headers == "" { + log.Fatalf("no DKIM configured for domain %s", dom) + } + _, err = fmt.Fprint(os.Stdout, headers) + xcheckf(err, "write headers") + _, err = io.Copy(os.Stdout, msgf) + xcheckf(err, "write message") +} + func cmdDKIMLookup(c *cmd) { c.params = "selector domain" c.help = "Lookup and print the DKIM record for the selector at the domain."