Skip to content

Commit a887672

Browse files
committed
dkim: Implement minimal DKIM verfication
See emersion/go-msgauth#10 for improvements that *should be* made to this initial implementation.
1 parent 4e1425f commit a887672

File tree

7 files changed

+195
-38
lines changed

7 files changed

+195
-38
lines changed

check/action.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type FailAction struct {
1313
ScoreAdjust int
1414
}
1515

16-
func failActionDirective(m *config.Map, node *config.Node) (interface{}, error) {
16+
func FailActionDirective(m *config.Map, node *config.Node) (interface{}, error) {
1717
if len(node.Args) == 0 {
1818
return nil, m.MatchErr("expected at least 1 argument")
1919
}

check/dkim/dkim.go

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package dkim
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"io"
7+
8+
"github.com/emersion/go-message/textproto"
9+
"github.com/emersion/go-msgauth/authres"
10+
"github.com/emersion/go-msgauth/dkim"
11+
"github.com/emersion/go-smtp"
12+
"github.com/foxcpp/maddy/buffer"
13+
"github.com/foxcpp/maddy/check"
14+
"github.com/foxcpp/maddy/config"
15+
"github.com/foxcpp/maddy/log"
16+
"github.com/foxcpp/maddy/module"
17+
"github.com/foxcpp/maddy/target"
18+
)
19+
20+
type Check struct {
21+
instName string
22+
log log.Logger
23+
24+
brokenSigAction check.FailAction
25+
noSigAction check.FailAction
26+
}
27+
28+
func New(_, instName string, _ []string) (module.Module, error) {
29+
return &Check{
30+
instName: instName,
31+
log: log.Logger{Name: "verify_dkim"},
32+
}, nil
33+
}
34+
35+
func (c *Check) Init(cfg *config.Map) error {
36+
cfg.Bool("debug", true, false, &c.log.Debug)
37+
cfg.Custom("broken_sig_action", false, false,
38+
func() (interface{}, error) {
39+
return check.FailAction{}, nil
40+
}, check.FailActionDirective, &c.brokenSigAction)
41+
cfg.Custom("no_sig_action", false, false,
42+
func() (interface{}, error) {
43+
return check.FailAction{}, nil
44+
}, check.FailActionDirective, &c.noSigAction)
45+
_, err := cfg.Process()
46+
if err != nil {
47+
return err
48+
}
49+
return nil
50+
}
51+
52+
func (c *Check) Name() string {
53+
return "verify_dkim"
54+
}
55+
56+
func (c *Check) InstanceName() string {
57+
return c.instName
58+
}
59+
60+
type dkimCheckState struct {
61+
c *Check
62+
msgMeta *module.MsgMetadata
63+
log log.Logger
64+
}
65+
66+
func (d dkimCheckState) CheckConnection(ctx context.Context) module.CheckResult {
67+
return module.CheckResult{}
68+
}
69+
70+
func (d dkimCheckState) CheckSender(ctx context.Context, mailFrom string) module.CheckResult {
71+
return module.CheckResult{}
72+
}
73+
74+
func (d dkimCheckState) CheckRcpt(ctx context.Context, rcptTo string) module.CheckResult {
75+
return module.CheckResult{}
76+
}
77+
78+
func (d dkimCheckState) CheckBody(ctx context.Context, header textproto.Header, body buffer.Buffer) module.CheckResult {
79+
if !header.Has("DKIM-Signature") {
80+
return d.c.noSigAction.Apply(module.CheckResult{
81+
RejectErr: &smtp.SMTPError{
82+
Code: 550,
83+
EnhancedCode: smtp.EnhancedCode{5, 7, 20},
84+
Message: "No DKIM signatures present",
85+
},
86+
AuthResult: []authres.Result{
87+
&authres.DKIMResult{
88+
Value: authres.ResultNone,
89+
},
90+
},
91+
})
92+
}
93+
94+
// TODO: Optimize that so we can avoid serializing header once more.
95+
// https://github.com/emersion/go-msgauth/issues/10.
96+
b := bytes.Buffer{}
97+
textproto.WriteHeader(&b, header)
98+
bodyRdr, err := body.Open()
99+
if err != nil {
100+
return module.CheckResult{RejectErr: err}
101+
}
102+
103+
verifications, err := dkim.Verify(io.MultiReader(&b, bodyRdr))
104+
if err != nil {
105+
return module.CheckResult{RejectErr: err}
106+
}
107+
108+
brokenSigs := false
109+
110+
res := module.CheckResult{AuthResult: make([]authres.Result, 0, len(verifications))}
111+
for _, verif := range verifications {
112+
var val authres.ResultValue
113+
if verif.Err == nil {
114+
val = authres.ResultPass
115+
} else {
116+
if dkim.IsPermFail(err) {
117+
brokenSigs = true
118+
}
119+
val = authres.ResultFail
120+
}
121+
122+
res.AuthResult = append(res.AuthResult, &authres.DKIMResult{
123+
Value: val,
124+
Domain: verif.Domain,
125+
Identifier: verif.Identifier,
126+
})
127+
}
128+
129+
if brokenSigs {
130+
d.log.Println("broken signatures present")
131+
return d.c.brokenSigAction.Apply(res)
132+
}
133+
return res
134+
}
135+
136+
func (d dkimCheckState) Close() error {
137+
return nil
138+
}
139+
140+
func (c *Check) CheckStateForMsg(msgMeta *module.MsgMetadata) (module.CheckState, error) {
141+
return dkimCheckState{
142+
c: c,
143+
msgMeta: msgMeta,
144+
log: target.DeliveryLogger(c.log, msgMeta),
145+
}, nil
146+
}
147+
148+
func init() {
149+
module.Register("verify_dkim", New)
150+
module.RegisterInstance(&Check{
151+
instName: "verify_dkim",
152+
log: log.Logger{Name: "verify_dkim"},
153+
}, &config.Map{Block: &config.Node{}})
154+
}

check/dkim/msgauth.go

-36
This file was deleted.

check/stateless_check.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func (c *statelessCheck) Init(cfg *config.Map) error {
130130
cfg.Custom("fail_action", false, false,
131131
func() (interface{}, error) {
132132
return c.defaultFailAction, nil
133-
}, failActionDirective, &c.failAction)
133+
}, FailActionDirective, &c.failAction)
134134
_, err := cfg.Process()
135135
return err
136136
}

maddy.conf

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ smtp smtp://0.0.0.0:25 {
2929

3030
# Verify that domain in MAIL FROM does have a MX record.
3131
require_mx_record
32+
33+
# Verify DKIM signatures in incoming messages.
34+
verify_dkim
3235
}
3336

3437
# All messages for the recipients at example.org should be

maddy.conf.5.scd

+35
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,41 @@ Check that domain in MAIL FROM command does have a MX record.
10481048
Check that source server IP does have a PTR record point to the domain
10491049
specified in EHLO/HELO command.
10501050

1051+
# VERIFY_DKIM MODULE
1052+
1053+
This is the check module that performs verification of the DKIM signatures
1054+
present on the incoming messages.
1055+
1056+
It got an implicit configuration block defined like CHECK MODULES above.
1057+
1058+
1059+
```
1060+
check {
1061+
...
1062+
verify_dkim
1063+
}
1064+
```
1065+
1066+
Valid directives:
1067+
1068+
## debug [yes/no]
1069+
1070+
Toggle debug logging only for this module.
1071+
1072+
## no_sig_action ...
1073+
1074+
Action to take when message without any signature is received.
1075+
Default is 'ignore', as recommended by RFC 6376.
1076+
1077+
Valid arguments are same as for CHECK MODULES above.
1078+
1079+
## broken_sig_action ...
1080+
1081+
Action to take when message with invalid signature is received.
1082+
Default is 'ignore', as recommended by RFC 6376.
1083+
1084+
Valid arguments are same as for CHECK MODULES above.
1085+
10511086
# DUMMY MODULE
10521087

10531088
No-op module. It doesn't need to be configured explicitly and can be referenced

maddy.go

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
_ "github.com/foxcpp/maddy/auth/external"
1616
_ "github.com/foxcpp/maddy/auth/pam"
1717
_ "github.com/foxcpp/maddy/auth/shadow"
18+
_ "github.com/foxcpp/maddy/check/dkim"
1819
_ "github.com/foxcpp/maddy/check/dns"
1920
_ "github.com/foxcpp/maddy/endpoint/imap"
2021
_ "github.com/foxcpp/maddy/endpoint/smtp"

0 commit comments

Comments
 (0)