1package mtasts
2
3import (
4 "reflect"
5 "testing"
6
7 "github.com/mjl-/mox/dns"
8)
9
10func TestRecord(t *testing.T) {
11 good := func(txt string, want Record) {
12 t.Helper()
13 r, _, err := ParseRecord(txt)
14 if err != nil {
15 t.Fatalf("parse: %s", err)
16 }
17 if !reflect.DeepEqual(r, &want) {
18 t.Fatalf("want %#v, got %#v", want, *r)
19 }
20 }
21
22 bad := func(txt string) {
23 t.Helper()
24 r, _, err := ParseRecord(txt)
25 if err == nil {
26 t.Fatalf("parse, expected error, got record %v", r)
27 }
28 }
29
30 good("v=STSv1; id=20160831085700Z;", Record{Version: "STSv1", ID: "20160831085700Z"})
31 good("v=STSv1; \t id=20160831085700Z \t;", Record{Version: "STSv1", ID: "20160831085700Z"})
32 good("v=STSv1; id=a", Record{Version: "STSv1", ID: "a"})
33 good("v=STSv1; id=a; more=a; ext=2", Record{Version: "STSv1", ID: "a", Extensions: []Pair{{"more", "a"}, {"ext", "2"}}})
34
35 bad("v=STSv0")
36 bad("v=STSv10")
37 bad("v=STSv2")
38 bad("v=STSv1") // missing id
39 bad("v=STSv1;") // missing id
40 bad("v=STSv1; ext=1") // missing id
41 bad("v=STSv1; id=") // empty id
42 bad("v=STSv1; id=012345678901234567890123456789012") // id too long
43 bad("v=STSv1; id=test-123") // invalid id
44 bad("v=STSv1; id=a; more=") // empty value in extension
45 bad("v=STSv1; id=a; a12345678901234567890123456789012=1") // extension name too long
46 bad("v=STSv1; id=a; 1%=a") // invalid extension name
47 bad("v=STSv1; id=a; test==") // invalid extension name
48 bad("v=STSv1; id=a;;") // additional semicolon
49
50 const want = `v=STSv1; id=a; more=a; ext=2`
51 record := Record{Version: "STSv1", ID: "a", Extensions: []Pair{{"more", "a"}, {"ext", "2"}}}
52 got := record.String()
53 if got != want {
54 t.Fatalf("record string, got %q, want %q", got, want)
55 }
56}
57
58func TestParsePolicy(t *testing.T) {
59 good := func(s string, want Policy) {
60 t.Helper()
61 p, err := ParsePolicy(s)
62 if err != nil {
63 t.Fatalf("parse policy: %s", err)
64 }
65 if !reflect.DeepEqual(p, &want) {
66 t.Fatalf("want %v, got %v", want, p)
67 }
68 }
69
70 good(`version: STSv1
71mode: testing
72mx: mx1.example.com
73mx: mx2.example.com
74mx: mx.backup-example.com
75max_age: 1296000
76`,
77 Policy{
78 Version: "STSv1",
79 Mode: ModeTesting,
80 MX: []MX{
81 {Domain: dns.Domain{ASCII: "mx1.example.com"}},
82 {Domain: dns.Domain{ASCII: "mx2.example.com"}},
83 {Domain: dns.Domain{ASCII: "mx.backup-example.com"}},
84 },
85 MaxAgeSeconds: 1296000,
86 },
87 )
88 good("version: STSv1\nmode: enforce \nmx: *.example.com \nmax_age: 0 \n",
89 Policy{
90 Version: "STSv1",
91 Mode: ModeEnforce,
92 MX: []MX{
93 {Wildcard: true, Domain: dns.Domain{ASCII: "example.com"}},
94 },
95 MaxAgeSeconds: 0,
96 },
97 )
98 good("version:STSv1\r\nmode:\tenforce\r\nmx: \t\t *.example.com\nmax_age: 1\nmore:ext e ns ion",
99 Policy{
100 Version: "STSv1",
101 Mode: ModeEnforce,
102 MX: []MX{
103 {Wildcard: true, Domain: dns.Domain{ASCII: "example.com"}},
104 },
105 MaxAgeSeconds: 1,
106 Extensions: []Pair{{"more", "ext e ns ion"}},
107 },
108 )
109
110 bad := func(s string) {
111 t.Helper()
112 p, err := ParsePolicy(s)
113 if err == nil {
114 t.Fatalf("parsing policy did not fail: %v", p)
115 }
116 }
117
118 bad("") // missing version
119 bad("version:STSv0\nmode:none\nmax_age:0") // bad version
120 bad("version:STSv10\nmode:none\nmax_age:0") // bad version
121 bad("version:STSv2\nmode:none\nmax_age:0") // bad version
122 bad("version:STSv1\nmax_age:0\nmx:example.com") // missing mode
123 bad("version:STSv1\nmode:none") // missing max_age
124 bad("version:STSv1\nmax_age:0\nmode:enforce") // missing mx for mode
125 bad("version:STSv1\nmax_age:0\nmode:testing") // missing mx for mode
126 bad("max_age:0\nmode:none") // missing version
127 bad("version:STSv1\nmode:none\nmax_age:01234567890") // max_age too long
128 bad("version:STSv1\nmode:bad\nmax_age:1") // bad mode
129 bad("version:STSv1\nmode:none\nmax_age:a") // bad max_age
130 bad("version:STSv1\nmode:enforce\nmax_age:0\nmx:") // missing value
131 bad("version:STSv1\nmode:enforce\nmax_age:0\nmx:*.*.example") // bad mx
132 bad("version:STSv1\nmode:enforce\nmax_age:0\nmx:**.example") // bad mx
133 bad("version:STSv1\nmode:enforce\nmax_age:0\nmx:**.example-") // bad mx
134 bad("version:STSv1\nmode:enforce\nmax_age:0\nmx:test.example-") // bad mx
135 bad("version:STSv1\nmode:none\nmax_age:0\next:") // empty extension
136 bad("version:STSv1\nmode:none\nmax_age:0\na12345678901234567890123456789012:123") // long extension name
137 bad("version:STSv1\nmode:none\nmax_age:0\n_bad:test") // bad ext name
138 bad("version:STSv1\nmode:none\nmax_age:0\nmx: møx.example") // invalid u-label in mx
139
140 policy := Policy{
141 Version: "STSv1",
142 Mode: ModeTesting,
143 MX: []MX{
144 {Domain: dns.Domain{ASCII: "mx1.example.com"}},
145 {Domain: dns.Domain{ASCII: "mx2.example.com"}},
146 {Domain: dns.Domain{ASCII: "mx.backup-example.com"}},
147 },
148 MaxAgeSeconds: 1296000,
149 }
150 want := `version: STSv1
151mode: testing
152max_age: 1296000
153mx: mx1.example.com
154mx: mx2.example.com
155mx: mx.backup-example.com
156`
157 got := policy.String()
158 if got != want {
159 t.Fatalf("policy string, got %q, want %q", got, want)
160 }
161}
162
163func FuzzParseRecord(f *testing.F) {
164 f.Add("v=STSv1; id=20160831085700Z;")
165 f.Add("v=STSv1; \t id=20160831085700Z \t;")
166 f.Add("v=STSv1; id=a")
167 f.Add("v=STSv1; id=a; more=a; ext=2")
168
169 f.Add("v=STSv0")
170 f.Add("v=STSv10")
171 f.Add("v=STSv2")
172 f.Add("v=STSv1") // missing id
173 f.Add("v=STSv1;") // missing id
174 f.Add("v=STSv1; ext=1") // missing id
175 f.Add("v=STSv1; id=") // empty id
176 f.Add("v=STSv1; id=012345678901234567890123456789012") // id too long
177 f.Add("v=STSv1; id=test-123") // invalid id
178 f.Add("v=STSv1; id=a; more=") // empty value in extension
179 f.Add("v=STSv1; id=a; a12345678901234567890123456789012=1") // extension name too long
180 f.Add("v=STSv1; id=a; 1%=a") // invalid extension name
181 f.Add("v=STSv1; id=a; test==") // invalid extension name
182 f.Add("v=STSv1; id=a;;") // additional semicolon
183
184 f.Fuzz(func(t *testing.T, s string) {
185 r, _, err := ParseRecord(s)
186 if err == nil {
187 _ = r.String()
188 }
189 })
190}
191
192func FuzzParsePolicy(f *testing.F) {
193 f.Add(`version: STSv1
194mode: testing
195mx: mx1.example.com
196mx: mx2.example.com
197mx: mx.backup-example.com
198max_age: 1296000
199`)
200 f.Add(`version: STSv1
201mode: enforce
202mx: *.example.com
203max_age: 0
204`)
205 f.Add("version:STSv1\r\nmode:\tenforce\r\nmx: \t\t *.example.com\nmax_age: 1\nmore:ext e ns ion")
206
207 f.Add("") // missing version
208 f.Add("version:STSv0\nmode:none\nmax_age:0") // bad version
209 f.Add("version:STSv10\nmode:none\nmax_age:0") // bad version
210 f.Add("version:STSv2\nmode:none\nmax_age:0") // bad version
211 f.Add("version:STSv1\nmax_age:0\nmx:example.com") // missing mode
212 f.Add("version:STSv1\nmode:none") // missing max_age
213 f.Add("version:STSv1\nmax_age:0\nmode:enforce") // missing mx for mode
214 f.Add("version:STSv1\nmax_age:0\nmode:testing") // missing mx for mode
215 f.Add("max_age:0\nmode:none") // missing version
216 f.Add("version:STSv1\nmode:none\nmax_age:0 ") // trailing whitespace
217 f.Add("version:STSv1\nmode:none\nmax_age:01234567890") // max_age too long
218 f.Add("version:STSv1\nmode:bad\nmax_age:1") // bad mode
219 f.Add("version:STSv1\nmode:none\nmax_age:a") // bad max_age
220 f.Add("version:STSv1\nmode:enforce\nmax_age:0\nmx:") // missing value
221 f.Add("version:STSv1\nmode:enforce\nmax_age:0\nmx:*.*.example") // bad mx
222 f.Add("version:STSv1\nmode:enforce\nmax_age:0\nmx:**.example") // bad mx
223 f.Add("version:STSv1\nmode:enforce\nmax_age:0\nmx:**.example-") // bad mx
224 f.Add("version:STSv1\nmode:enforce\nmax_age:0\nmx:test.example-") // bad mx
225 f.Add("version:STSv1\nmode:none\nmax_age:0\next:") // empty extension
226 f.Add("version:STSv1\nmode:none\nmax_age:0\next:abc ") // trailing space
227 f.Add("version:STSv1\nmode:none\nmax_age:0\next:a\t") // invalid char
228 f.Add("version:STSv1\nmode:none\nmax_age:0\na12345678901234567890123456789012:123") // long extension name
229 f.Add("version:STSv1\nmode:none\nmax_age:0\n_bad:test") // bad ext name
230
231 f.Fuzz(func(t *testing.T, s string) {
232 r, err := ParsePolicy(s)
233 if err == nil {
234 _ = r.String()
235 }
236 })
237}
238