-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathrfc_violation_test.go
More file actions
222 lines (213 loc) · 7.32 KB
/
rfc_violation_test.go
File metadata and controls
222 lines (213 loc) · 7.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package warp
import (
"bytes"
"testing"
"time"
)
// TestRFCViolation tests behavior with RFC 5321 violating SMTP commands and email addresses
// References:
// - RFC 5321 Section 2.4: Command verbs are case-insensitive
// - RFC 5321 Section 3.3: "spaces are not permitted on either side of the colon"
// - https://www.docomo.ne.jp/service/docomo_mail/rfc_add/
// - https://www.sonoko.co.jp/user_data/oshirase10.php
//
// The proxy accepts RFC-violating commands to collect metadata, but logs warnings for spacing violations.
func TestRFCViolation(t *testing.T) {
tests := []struct {
name string
command []byte
expectAddr []byte
expectServer []byte
shouldMatch bool
expectWarning bool
description string
}{
// === RFC Compliant Cases ===
{
name: "RFC compliant: MAIL FROM:<address>",
command: []byte("MAIL FROM:<alice@example.com>\r\n"),
expectAddr: []byte("alice@example.com"),
shouldMatch: true,
expectWarning: false,
description: "Standard RFC-compliant MAIL FROM",
},
{
name: "RFC compliant: RCPT TO:<address>",
command: []byte("RCPT TO:<bob@example.com>\r\n"),
expectAddr: []byte("bob@example.com"),
expectServer: []byte("example.com"),
shouldMatch: true,
expectWarning: false,
description: "Standard RFC-compliant RCPT TO",
},
// === RFC Violation: Command Spacing ===
{
name: "RFC violation: MAIL FROM: <address> (space after colon)",
command: []byte("MAIL FROM: <alice@example.com>\r\n"),
expectAddr: []byte("alice@example.com"),
shouldMatch: true,
expectWarning: true,
description: "Space after colon violates RFC 5321 Section 3.3",
},
{
name: "RFC violation: MAIL FROM : <address> (spaces around colon)",
command: []byte("MAIL FROM : <alice@example.com>\r\n"),
expectAddr: []byte("alice@example.com"),
shouldMatch: true,
expectWarning: true,
description: "Spaces before and after colon",
},
{
name: "RFC violation: MAIL FROM:<address> (double space)",
command: []byte("MAIL FROM:<alice@example.com>\r\n"),
expectAddr: []byte("alice@example.com"),
shouldMatch: true,
expectWarning: true,
description: "Double space between MAIL and FROM",
},
{
name: "RFC violation: RCPT TO: <address> (space after colon)",
command: []byte("RCPT TO: <bob@example.com>\r\n"),
expectAddr: []byte("bob@example.com"),
expectServer: []byte("example.com"),
shouldMatch: true,
expectWarning: true,
description: "Space after colon in RCPT TO",
},
// === RFC Violation: Email Address Local Part (Carrier Patterns) ===
{
name: "Carrier pattern: consecutive dots",
command: []byte("MAIL FROM:<user..name@example.com>\r\n"),
expectAddr: []byte("user..name@example.com"),
shouldMatch: true,
expectWarning: false,
description: "Two consecutive dots in local part",
},
{
name: "Carrier pattern: triple consecutive dots",
command: []byte("MAIL FROM:<user...name@example.com>\r\n"),
expectAddr: []byte("user...name@example.com"),
shouldMatch: true,
expectWarning: false,
description: "Three consecutive dots in local part",
},
{
name: "Carrier pattern: dot before @",
command: []byte("MAIL FROM:<username.@example.com>\r\n"),
expectAddr: []byte("username.@example.com"),
shouldMatch: true,
expectWarning: false,
description: "Dot immediately before @ symbol",
},
{
name: "Carrier pattern: hyphen at start",
command: []byte("MAIL FROM:<-username@example.com>\r\n"),
expectAddr: []byte("-username@example.com"),
shouldMatch: true,
expectWarning: false,
description: "Hyphen at the start of local part",
},
{
name: "Carrier pattern: dot at start",
command: []byte("MAIL FROM:<.username@example.com>\r\n"),
expectAddr: []byte(".username@example.com"),
shouldMatch: true,
expectWarning: false,
description: "Dot at the start of local part",
},
{
name: "Carrier pattern: consecutive hyphens",
command: []byte("MAIL FROM:<user--name@example.com>\r\n"),
expectAddr: []byte("user--name@example.com"),
shouldMatch: true,
expectWarning: false,
description: "Two consecutive hyphens in local part",
},
{
name: "Carrier pattern: multiple violations",
command: []byte("MAIL FROM:<-user..name.@example.com>\r\n"),
expectAddr: []byte("-user..name.@example.com"),
shouldMatch: true,
expectWarning: false,
description: "Hyphen at start, consecutive dots, dot before @",
},
// === RCPT TO with Carrier Patterns ===
{
name: "RCPT TO: carrier pattern consecutive dots",
command: []byte("RCPT TO:<user..name@example.com>\r\n"),
expectAddr: []byte("user..name@example.com"),
expectServer: []byte("example.com"),
shouldMatch: true,
expectWarning: false,
description: "Consecutive dots in RCPT TO",
},
{
name: "RCPT TO: carrier pattern hyphen at start",
command: []byte("RCPT TO:<-username@example.com>\r\n"),
expectAddr: []byte("-username@example.com"),
expectServer: []byte("example.com"),
shouldMatch: true,
expectWarning: false,
description: "Hyphen at start in RCPT TO",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
loggedMsg := make(chan []byte, 1)
pipe := &Pipe{afterCommHook: func(b Data, to Direction) {
// Send logged message to channel (non-blocking)
select {
case loggedMsg <- b:
default:
}
}}
// Determine command type and execute appropriate function
isRCPT := len(tt.expectServer) > 0
if isRCPT {
// Test RCPT TO
pipe.setReceiverMailAddressAndServerName(tt.command)
if tt.shouldMatch {
if string(tt.expectAddr) != string(pipe.rMailAddr) {
t.Errorf("%s: expected address %q, got %q", tt.description, tt.expectAddr, pipe.rMailAddr)
}
if string(tt.expectServer) != string(pipe.rServerName) {
t.Errorf("%s: expected server %q, got %q", tt.description, tt.expectServer, pipe.rServerName)
}
} else {
if len(pipe.rMailAddr) > 0 {
t.Errorf("%s: expected no match, but got address %q", tt.description, pipe.rMailAddr)
}
}
} else {
// Test MAIL FROM
pipe.setSenderMailAddress(tt.command)
if tt.shouldMatch {
if string(tt.expectAddr) != string(pipe.sMailAddr) {
t.Errorf("%s: expected address %q, got %q", tt.description, tt.expectAddr, pipe.sMailAddr)
}
} else {
if len(pipe.sMailAddr) > 0 {
t.Errorf("%s: expected no match, but got address %q", tt.description, pipe.sMailAddr)
}
}
}
// Wait for goroutine to log (with timeout)
var warningLogged bool
select {
case msg := <-loggedMsg:
if bytes.Contains(msg, []byte("RFC 5321 violation")) {
warningLogged = true
}
case <-time.After(50 * time.Millisecond):
// Timeout - no message received
}
// Check if RFC violation warning was logged as expected
if tt.expectWarning && !warningLogged {
t.Errorf("%s: expected RFC violation warning to be logged, but none was found", tt.description)
}
if !tt.expectWarning && warningLogged {
t.Errorf("%s: unexpected RFC violation warning logged", tt.description)
}
})
}
}