/
mail.go
160 lines (124 loc) · 3.2 KB
/
mail.go
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
// Copyright 2014 Rafael Dantas Justo. All rights reserved.
// Use of this source code is governed by a GPL
// license that can be found in the LICENSE file.
// utils add features to make the test life easier
package utils
import (
"bufio"
"fmt"
"net"
"net/mail"
"strings"
"time"
)
// E-mail server created to test if the messages are being contructed and delivered correctly in the
// notification module. The server was created based on the Go-Guerrilla SMTPd from
// https://github.com/flashmob/go-guerrilla
const (
server = "localhost"
)
func StartMailServer(port int) (chan *mail.Message, chan error, error) {
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", server, port))
if err != nil {
return nil, nil, err
}
messageChannel := make(chan *mail.Message)
errorChannel := make(chan error)
go func() {
for {
conn, err := ln.Accept()
if err != nil {
errorChannel <- err
continue
}
mailMessage, err := handleMailClient(conn)
if err != nil {
errorChannel <- err
} else {
messageChannel <- mailMessage
}
}
}()
// Give some time for the mail server goes up
time.Sleep(1 * time.Second)
return messageChannel, errorChannel, nil
}
func handleMailClient(conn net.Conn) (*mail.Message, error) {
defer conn.Close()
var mailMessage string
processing := true
phase := 0
for processing {
switch phase {
case 0:
response := fmt.Sprintf("220 %s SMTP ShelterMail\r\n", server)
if _, err := conn.Write([]byte(response)); err != nil {
return nil, err
}
phase = 1
case 1:
data, err := readMailClient(conn, false)
if err != nil {
return nil, err
}
data = strings.Trim(data, " \n\r")
cmd := strings.ToUpper(data)
response := ""
switch {
case strings.Index(cmd, "HELO") == 0:
response = fmt.Sprintf("250 %s Hello\r\n", server)
case strings.Index(cmd, "EHLO") == 0:
response = fmt.Sprintf("250-%s Hello %s[%s]\r\n250-SIZE 131072\r\n250 HELP\r\n",
server, data[5:], conn.RemoteAddr().String(),
)
case strings.Index(cmd, "RCPT TO:") == 0:
response = "250 Accepted\r\n"
case strings.Index(cmd, "DATA") == 0:
phase = 2
response = "354 Enter message, ending with \".\" on a line by itself\r\n"
case strings.Index(cmd, "QUIT") == 0:
processing = false
response = "221 Bye\r\n"
default:
response = "250 Ok\r\n"
}
if _, err := conn.Write([]byte(response)); err != nil {
return nil, err
}
case 2:
data, err := readMailClient(conn, true)
if err != nil {
return nil, err
}
mailMessage += data
response := "250 Ok: queued as 12345\r\n"
if _, err := conn.Write([]byte(response)); err != nil {
return nil, err
}
phase = 1
}
}
return mail.ReadMessage(strings.NewReader(mailMessage))
}
func readMailClient(conn net.Conn, isBody bool) (string, error) {
endMark := "\r\n"
if isBody {
endMark = "\r\n.\r\n"
}
data := ""
if err := conn.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
return data, err
}
reader := bufio.NewReader(conn)
for {
partialData, err := reader.ReadString('\n')
if err != nil {
return data, err
}
data += partialData
if strings.HasSuffix(data, endMark) {
break
}
}
return data, nil
}