1package smtp
2
3import (
4 "bufio"
5 "bytes"
6 "errors"
7 "io"
8)
9
10var ErrCRLF = errors.New("invalid bare carriage return or newline")
11
12var errMissingCRLF = errors.New("missing crlf at end of message")
13
14// DataWrite reads data (a mail message) from r, and writes it to smtp
15// connection w with dot stuffing, as required by the SMTP data command.
16//
17// Messages with bare carriage returns or bare newlines result in an error.
18func DataWrite(w io.Writer, r io.Reader) error {
19 // ../rfc/5321:2003
20
21 var prevlast, last byte = '\r', '\n' // Start on a new line, so we insert a dot if the first byte is a dot.
22 // todo: at least for smtp submission we should probably set a max line length, eg 1000 octects including crlf. ../rfc/5321:3512
23 buf := make([]byte, 8*1024)
24 for {
25 nr, err := r.Read(buf)
26 if nr > 0 {
27 // Process buf by writing a line at a time, and checking if the next character
28 // after the line starts with a dot. Insert an extra dot if so.
29 p := buf[:nr]
30 for len(p) > 0 {
31 if p[0] == '.' && prevlast == '\r' && last == '\n' {
32 if _, err := w.Write([]byte{'.'}); err != nil {
33 return err
34 }
35 }
36 // Look for the next newline, or end of buffer.
37 n := 0
38 firstcr := -1
39 for n < len(p) {
40 c := p[n]
41 if c == '\n' {
42 if firstcr < 0 {
43 if n > 0 || last != '\r' {
44 // Bare newline.
45 return ErrCRLF
46 }
47 } else if firstcr != n-1 {
48 // Bare carriage return.
49 return ErrCRLF
50 }
51 n++
52 break
53 } else if c == '\r' && firstcr < 0 {
54 firstcr = n
55 }
56 n++
57 }
58
59 if _, err := w.Write(p[:n]); err != nil {
60 return err
61 }
62 // Keep track of the last two bytes we've written.
63 if n == 1 {
64 prevlast, last = last, p[0]
65 } else {
66 prevlast, last = p[n-2], p[n-1]
67 }
68 p = p[n:]
69 }
70 }
71 if err == io.EOF {
72 break
73 } else if err != nil {
74 return err
75 }
76 }
77 if prevlast != '\r' || last != '\n' {
78 return errMissingCRLF
79 }
80 if _, err := w.Write(dotcrlf); err != nil {
81 return err
82 }
83 return nil
84}
85
86var dotcrlf = []byte(".\r\n")
87
88// DataReader is an io.Reader that reads data from an SMTP DATA command, doing dot
89// unstuffing and returning io.EOF when a bare dot is received. Use NewDataReader.
90//
91// Bare carriage returns, and the sequences "[^\r]\n." and "\n.\n" result in an
92// error.
93type DataReader struct {
94 // ../rfc/5321:2003
95 r *bufio.Reader
96 plast, last byte
97 buf []byte // From previous read.
98 err error // Read error, for after r.buf is exhausted.
99
100 // When we see invalid combinations of CR and LF, we keep reading, and report an
101 // error at the final "\r\n.\r\n". We cannot just stop reading and return an error,
102 // the SMTP protocol would become out of sync.
103 badcrlf bool
104}
105
106// NewDataReader returns an initialized DataReader.
107func NewDataReader(r *bufio.Reader) *DataReader {
108 return &DataReader{
109 r: r,
110 // Set up initial state to accept a message that is only "." and CRLF.
111 plast: '\r',
112 last: '\n',
113 }
114}
115
116// Read implements io.Reader.
117func (r *DataReader) Read(p []byte) (int, error) {
118 wrote := 0
119 for len(p) > 0 {
120 // Read until newline as long as it fits in the buffer.
121 if len(r.buf) == 0 {
122 if r.err != nil {
123 break
124 }
125 // todo: set a max length, eg 1000 octets including crlf excluding potential leading dot. ../rfc/5321:3512
126 r.buf, r.err = r.r.ReadSlice('\n')
127 if r.err == bufio.ErrBufferFull {
128 r.err = nil
129 } else if r.err == io.EOF {
130 // Mark EOF as bad for now. If we see the ending dotcrlf below, err becomes regular
131 // io.EOF again.
132 r.err = io.ErrUnexpectedEOF
133 }
134 }
135 if len(r.buf) > 0 {
136 // Reject bare \r.
137 for i, c := range r.buf {
138 if c == '\r' && (i == len(r.buf) || r.buf[i+1] != '\n') {
139 r.badcrlf = true
140 }
141 }
142
143 // We require crlf. A bare LF is not a line ending for the end of the SMTP
144 // transaction. ../rfc/5321:2032
145 // Bare newlines are accepted as message data, unless around a bare dot. The SMTP
146 // server adds missing carriage returns. We don't reject bare newlines outright,
147 // real-world messages like that occur.
148 if r.plast == '\r' && r.last == '\n' {
149 if bytes.Equal(r.buf, dotcrlf) {
150 r.buf = nil
151 r.err = io.EOF
152 if r.badcrlf {
153 r.err = ErrCRLF
154 }
155 break
156 } else if r.buf[0] == '.' {
157 // Reject "\r\n.\n".
158 if len(r.buf) >= 2 && r.buf[1] == '\n' {
159 r.badcrlf = true
160 }
161 r.buf = r.buf[1:]
162 }
163 } else if r.last == '\n' && (bytes.HasPrefix(r.buf, []byte(".\n")) || bytes.HasPrefix(r.buf, []byte(".\r\n"))) {
164 // Reject "[^\r]\n.\n" and "[^\r]\n.\r\n"
165 r.badcrlf = true
166 }
167 n := len(r.buf)
168 if n > len(p) {
169 n = len(p)
170 }
171 copy(p, r.buf[:n])
172 if n == 1 {
173 r.plast, r.last = r.last, r.buf[0]
174 } else if n > 1 {
175 r.plast, r.last = r.buf[n-2], r.buf[n-1]
176 }
177 p = p[n:]
178 r.buf = r.buf[n:]
179 wrote += n
180 }
181 }
182 return wrote, r.err
183}
184