1 package main
2
3 import (
4 "bufio"
5 "bytes"
6 "context"
7 "crypto/tls"
8 "flag"
9 "fmt"
10 "log"
11 "net"
12 "net/http"
13 "net/mail"
14 "regexp"
15 "strings"
16 "sync"
17
18 "github.com/letsencrypt/boulder/cmd"
19 blog "github.com/letsencrypt/boulder/log"
20 )
21
22 type mailSrv struct {
23 closeFirst uint
24 allReceivedMail []rcvdMail
25 allMailMutex sync.Mutex
26 connNumber uint
27 connNumberMutex sync.RWMutex
28 logger blog.Logger
29 }
30
31 type rcvdMail struct {
32 From string
33 To string
34 Mail string
35 }
36
37 func expectLine(buf *bufio.Reader, expected string) error {
38 line, _, err := buf.ReadLine()
39 if err != nil {
40 return fmt.Errorf("readline: %v", err)
41 }
42 if string(line) != expected {
43 return fmt.Errorf("Expected %s, got %s", expected, line)
44 }
45 return nil
46 }
47
48 var mailFromRegex = regexp.MustCompile(`^MAIL FROM:<(.*)>\s*BODY=8BITMIME\s*$`)
49 var rcptToRegex = regexp.MustCompile(`^RCPT TO:<(.*)>\s*$`)
50 var smtpErr501 = []byte("501 syntax error in parameters or arguments \r\n")
51 var smtpOk250 = []byte("250 OK \r\n")
52
53 func (srv *mailSrv) handleConn(conn net.Conn) {
54 defer conn.Close()
55 srv.connNumberMutex.Lock()
56 srv.connNumber++
57 srv.connNumberMutex.Unlock()
58 srv.logger.Infof("mail-test-srv: Got connection from %s", conn.RemoteAddr())
59
60 readBuf := bufio.NewReader(conn)
61 conn.Write([]byte("220 smtp.example.com ESMTP\r\n"))
62 err := expectLine(readBuf, "EHLO localhost")
63 if err != nil {
64 log.Printf("mail-test-srv: %s: %v\n", conn.RemoteAddr(), err)
65 return
66 }
67 conn.Write([]byte("250-PIPELINING\r\n"))
68 conn.Write([]byte("250-AUTH PLAIN LOGIN\r\n"))
69 conn.Write([]byte("250 8BITMIME\r\n"))
70
71
72 err = expectLine(readBuf, "AUTH PLAIN AGNlcnQtbWFuYWdlckBleGFtcGxlLmNvbQBwYXNzd29yZA==")
73 if err != nil {
74 log.Printf("mail-test-srv: %s: %v\n", conn.RemoteAddr(), err)
75 return
76 }
77 conn.Write([]byte("235 2.7.0 Authentication successful\r\n"))
78 srv.logger.Infof("mail-test-srv: Successful auth from %s", conn.RemoteAddr())
79
80
81
82
83 var fromAddr string
84 var toAddr []string
85
86 clearState := func() {
87 fromAddr = ""
88 toAddr = nil
89 }
90
91 reader := bufio.NewScanner(readBuf)
92 scan:
93 for reader.Scan() {
94 line := reader.Text()
95 cmdSplit := strings.SplitN(line, " ", 2)
96 cmd := cmdSplit[0]
97 switch cmd {
98 case "QUIT":
99 conn.Write([]byte("221 Bye \r\n"))
100 break scan
101 case "RSET":
102 clearState()
103 conn.Write(smtpOk250)
104 case "NOOP":
105 conn.Write(smtpOk250)
106 case "MAIL":
107 srv.connNumberMutex.RLock()
108 if srv.connNumber <= srv.closeFirst {
109
110
111 if srv.connNumber%2 == 0 {
112 log.Printf(
113 "mail-test-srv: connection # %d < -closeFirst parameter %d, disconnecting client. Bye!\n",
114 srv.connNumber, srv.closeFirst)
115 clearState()
116 conn.Close()
117 } else {
118
119
120
121 log.Printf(
122 "mail-test-srv: connection # %d < -closeFirst parameter %d, disconnecting with 421. Bye!\n",
123 srv.connNumber, srv.closeFirst)
124 clearState()
125 conn.Write([]byte("421 1.2.3 foo.bar.baz Error: timeout exceeded \r\n"))
126 conn.Close()
127 }
128 }
129 srv.connNumberMutex.RUnlock()
130 clearState()
131 matches := mailFromRegex.FindStringSubmatch(line)
132 if matches == nil {
133 log.Panicf("mail-test-srv: %s: MAIL FROM parse error\n", conn.RemoteAddr())
134 }
135 addr, err := mail.ParseAddress(matches[1])
136 if err != nil {
137 log.Panicf("mail-test-srv: %s: addr parse error: %v\n", conn.RemoteAddr(), err)
138 }
139 fromAddr = addr.Address
140 conn.Write(smtpOk250)
141 case "RCPT":
142 matches := rcptToRegex.FindStringSubmatch(line)
143 if matches == nil {
144 conn.Write(smtpErr501)
145 continue
146 }
147 addr, err := mail.ParseAddress(matches[1])
148 if err != nil {
149 log.Panicf("mail-test-srv: %s: addr parse error: %v\n", conn.RemoteAddr(), err)
150 }
151 toAddr = append(toAddr, addr.Address)
152 conn.Write(smtpOk250)
153 case "DATA":
154 conn.Write([]byte("354 Start mail input \r\n"))
155 var msgBuf bytes.Buffer
156
157 for reader.Scan() {
158 line := reader.Text()
159 msgBuf.WriteString(line)
160 msgBuf.WriteString("\r\n")
161 if strings.HasSuffix(msgBuf.String(), "\r\n.\r\n") {
162 break
163 }
164 }
165 if reader.Err() != nil {
166 log.Printf("mail-test-srv: read from %s: %v\n", conn.RemoteAddr(), reader.Err())
167 return
168 }
169
170 mailResult := rcvdMail{
171 From: fromAddr,
172 Mail: msgBuf.String(),
173 }
174 srv.allMailMutex.Lock()
175 for _, rcpt := range toAddr {
176 mailResult.To = rcpt
177 srv.allReceivedMail = append(srv.allReceivedMail, mailResult)
178 log.Printf("mail-test-srv: Got mail: %s -> %s\n", fromAddr, rcpt)
179 }
180 srv.allMailMutex.Unlock()
181 conn.Write([]byte("250 Got mail \r\n"))
182 clearState()
183 }
184 }
185 if reader.Err() != nil {
186 log.Printf("mail-test-srv: read from %s: %s\n", conn.RemoteAddr(), reader.Err())
187 }
188 }
189
190 func (srv *mailSrv) serveSMTP(ctx context.Context, l net.Listener) error {
191 for {
192 conn, err := l.Accept()
193 if err != nil {
194
195
196
197 select {
198 case <-ctx.Done():
199 return nil
200 default:
201 return err
202 }
203 }
204 go srv.handleConn(conn)
205 }
206 }
207
208 func main() {
209 var listenAPI = flag.String("http", "0.0.0.0:9381", "http port to listen on")
210 var listenSMTP = flag.String("smtp", "0.0.0.0:9380", "smtp port to listen on")
211 var certFilename = flag.String("cert", "", "certificate to serve")
212 var privKeyFilename = flag.String("key", "", "private key for certificate")
213 var closeFirst = flag.Uint("closeFirst", 0, "close first n connections after MAIL for reconnection tests")
214
215 flag.Parse()
216
217 cert, err := tls.LoadX509KeyPair(*certFilename, *privKeyFilename)
218 if err != nil {
219 log.Fatal(err)
220 }
221 l, err := tls.Listen("tcp", *listenSMTP, &tls.Config{
222 Certificates: []tls.Certificate{cert},
223 })
224 if err != nil {
225 log.Fatalf("Couldn't bind %q for SMTP: %s", *listenSMTP, err)
226 }
227 defer l.Close()
228
229 srv := mailSrv{
230 closeFirst: *closeFirst,
231 logger: cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 7}),
232 }
233
234 srv.setupHTTP(http.DefaultServeMux)
235 go func() {
236
237
238
239 err := http.ListenAndServe(*listenAPI, http.DefaultServeMux)
240 if err != nil {
241 log.Fatalln("Couldn't start HTTP server", err)
242 }
243 }()
244
245 ctx, cancel := context.WithCancel(context.Background())
246 defer cancel()
247
248 go cmd.FailOnError(srv.serveSMTP(ctx, l), "Failed to accept connection")
249
250 cmd.WaitForSignal()
251 }
252
View as plain text