1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in https://raw.githubusercontent.com/golang/go/master/LICENSE
4
5// Package mail implements the Simple Mail Transfer Protocol as defined in RFC 5321.
6// It also implements the following extensions:
7//	8BITMIME  RFC 1652
8//	SMTPUTF8  RFC 6531
9//	AUTH      RFC 2554
10//	STARTTLS  RFC 3207
11//	SIZE      RFC 1870
12// Additional extensions may be handled by clients using smtp.go in golang source code or pull request Go Simple Mail
13
14// smtp.go file is a modification of smtp golang package what is frozen and is not accepting new features.
15
16package mail
17
18import (
19	"crypto/tls"
20	"encoding/base64"
21	"errors"
22	"fmt"
23	"io"
24	"net"
25	"net/textproto"
26	"strings"
27)
28
29// A Client represents a client connection to an SMTP server.
30type smtpClient struct {
31	// Text is the textproto.Conn used by the Client.
32	text *textproto.Conn
33	// keep a reference to the connection so it can be used to create a TLS
34	// connection later
35	conn net.Conn
36	// whether the Client is using TLS
37	tls        bool
38	serverName string
39	// map of supported extensions
40	ext map[string]string
41	// supported auth mechanisms
42	a          []string
43	localName  string // the name to use in HELO/EHLO
44	didHello   bool   // whether we've said HELO/EHLO
45	helloError error  // the error from the hello
46}
47
48// newClient returns a new smtpClient using an existing connection and host as a
49// server name to be used when authenticating.
50func newClient(conn net.Conn, host string) (*smtpClient, error) {
51	text := textproto.NewConn(conn)
52	_, _, err := text.ReadResponse(220)
53	if err != nil {
54		text.Close()
55		return nil, err
56	}
57	c := &smtpClient{text: text, conn: conn, serverName: host, localName: "localhost"}
58	_, c.tls = conn.(*tls.Conn)
59	return c, nil
60}
61
62// Close closes the connection.
63func (c *smtpClient) close() error {
64	return c.text.Close()
65}
66
67// hello runs a hello exchange if needed.
68func (c *smtpClient) hello() error {
69	if !c.didHello {
70		c.didHello = true
71		err := c.ehlo()
72		if err != nil {
73			c.helloError = c.helo()
74		}
75	}
76	return c.helloError
77}
78
79// hi sends a HELO or EHLO to the server as the given host name.
80// Calling this method is only necessary if the client needs control
81// over the host name used. The client will introduce itself as "localhost"
82// automatically otherwise. If Hello is called, it must be called before
83// any of the other methods.
84func (c *smtpClient) hi(localName string) error {
85	if err := validateLine(localName); err != nil {
86		return err
87	}
88	if c.didHello {
89		return errors.New("smtp: Hello called after other methods")
90	}
91	c.localName = localName
92	return c.hello()
93}
94
95// cmd is a convenience function that sends a command and returns the response
96func (c *smtpClient) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
97	id, err := c.text.Cmd(format, args...)
98	if err != nil {
99		return 0, "", err
100	}
101	c.text.StartResponse(id)
102	defer c.text.EndResponse(id)
103	code, msg, err := c.text.ReadResponse(expectCode)
104	return code, msg, err
105}
106
107// helo sends the HELO greeting to the server. It should be used only when the
108// server does not support ehlo.
109func (c *smtpClient) helo() error {
110	c.ext = nil
111	_, _, err := c.cmd(250, "HELO %s", c.localName)
112	return err
113}
114
115// ehlo sends the EHLO (extended hello) greeting to the server. It
116// should be the preferred greeting for servers that support it.
117func (c *smtpClient) ehlo() error {
118	_, msg, err := c.cmd(250, "EHLO %s", c.localName)
119	if err != nil {
120		return err
121	}
122	ext := make(map[string]string)
123	extList := strings.Split(msg, "\n")
124	if len(extList) > 1 {
125		extList = extList[1:]
126		for _, line := range extList {
127			args := strings.SplitN(line, " ", 2)
128			if len(args) > 1 {
129				ext[args[0]] = args[1]
130			} else {
131				ext[args[0]] = ""
132			}
133		}
134	}
135	if mechs, ok := ext["AUTH"]; ok {
136		c.a = strings.Split(mechs, " ")
137	}
138	c.ext = ext
139	return err
140}
141
142// startTLS sends the STARTTLS command and encrypts all further communication.
143// Only servers that advertise the STARTTLS extension support this function.
144func (c *smtpClient) startTLS(config *tls.Config) error {
145	if err := c.hello(); err != nil {
146		return err
147	}
148	_, _, err := c.cmd(220, "STARTTLS")
149	if err != nil {
150		return err
151	}
152	c.conn = tls.Client(c.conn, config)
153	c.text = textproto.NewConn(c.conn)
154	c.tls = true
155	return c.ehlo()
156}
157
158// authenticate authenticates a client using the provided authentication mechanism.
159// A failed authentication closes the connection.
160// Only servers that advertise the AUTH extension support this function.
161func (c *smtpClient) authenticate(a auth) error {
162	if err := c.hello(); err != nil {
163		return err
164	}
165	encoding := base64.StdEncoding
166	mech, resp, err := a.start(&serverInfo{c.serverName, c.tls, c.a})
167	if err != nil {
168		c.quit()
169		return err
170	}
171	resp64 := make([]byte, encoding.EncodedLen(len(resp)))
172	encoding.Encode(resp64, resp)
173	code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
174	for err == nil {
175		var msg []byte
176		switch code {
177		case 334:
178			msg, err = encoding.DecodeString(msg64)
179		case 235:
180			// the last message isn't base64 because it isn't a challenge
181			msg = []byte(msg64)
182		default:
183			err = &textproto.Error{Code: code, Msg: msg64}
184		}
185		if err == nil {
186			resp, err = a.next(msg, code == 334)
187		}
188		if err != nil {
189			// abort the AUTH
190			c.cmd(501, "*")
191			c.quit()
192			break
193		}
194		if resp == nil {
195			break
196		}
197		resp64 = make([]byte, encoding.EncodedLen(len(resp)))
198		encoding.Encode(resp64, resp)
199		code, msg64, err = c.cmd(0, string(resp64))
200	}
201	return err
202}
203
204// mail issues a MAIL command to the server using the provided email address.
205// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
206// parameter.
207// If the server supports the SMTPUTF8 extension, Mail adds the
208// SMTPUTF8 parameter.
209// This initiates a mail transaction and is followed by one or more Rcpt calls.
210func (c *smtpClient) mail(from string, extArgs ...map[string]string) error {
211	var args []interface{}
212	var extMap map[string]string
213
214	if len(extArgs) > 0 {
215		extMap = extArgs[0]
216	}
217
218	if err := validateLine(from); err != nil {
219		return err
220	}
221	if err := c.hello(); err != nil {
222		return err
223	}
224	cmdStr := "MAIL FROM:<%s>"
225	if c.ext != nil {
226		if _, ok := c.ext["8BITMIME"]; ok {
227			cmdStr += " BODY=8BITMIME"
228		}
229		if _, ok := c.ext["SMTPUTF8"]; ok {
230			cmdStr += " SMTPUTF8"
231		}
232		if _, ok := c.ext["SIZE"]; ok {
233			if extMap["SIZE"] != "" {
234				cmdStr += " SIZE=%s"
235				args = append(args, extMap["SIZE"])
236			}
237		}
238	}
239	args = append([]interface{}{from}, args...)
240	_, _, err := c.cmd(250, cmdStr, args...)
241	return err
242}
243
244// rcpt issues a RCPT command to the server using the provided email address.
245// A call to Rcpt must be preceded by a call to Mail and may be followed by
246// a Data call or another Rcpt call.
247func (c *smtpClient) rcpt(to string) error {
248	if err := validateLine(to); err != nil {
249		return err
250	}
251	_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
252	return err
253}
254
255type dataCloser struct {
256	c *smtpClient
257	io.WriteCloser
258}
259
260func (d *dataCloser) Close() error {
261	d.WriteCloser.Close()
262	_, _, err := d.c.text.ReadResponse(250)
263	return err
264}
265
266// data issues a DATA command to the server and returns a writer that
267// can be used to write the mail headers and body. The caller should
268// close the writer before calling any more methods on c. A call to
269// Data must be preceded by one or more calls to Rcpt.
270func (c *smtpClient) data() (io.WriteCloser, error) {
271	_, _, err := c.cmd(354, "DATA")
272	if err != nil {
273		return nil, err
274	}
275	return &dataCloser{c, c.text.DotWriter()}, nil
276}
277
278// extension reports whether an extension is support by the server.
279// The extension name is case-insensitive. If the extension is supported,
280// extension also returns a string that contains any parameters the
281// server specifies for the extension.
282func (c *smtpClient) extension(ext string) (bool, string) {
283	if err := c.hello(); err != nil {
284		return false, ""
285	}
286	if c.ext == nil {
287		return false, ""
288	}
289	ext = strings.ToUpper(ext)
290	param, ok := c.ext[ext]
291	return ok, param
292}
293
294// reset sends the RSET command to the server, aborting the current mail
295// transaction.
296func (c *smtpClient) reset() error {
297	if err := c.hello(); err != nil {
298		return err
299	}
300	_, _, err := c.cmd(250, "RSET")
301	return err
302}
303
304// noop sends the NOOP command to the server. It does nothing but check
305// that the connection to the server is okay.
306func (c *smtpClient) noop() error {
307	if err := c.hello(); err != nil {
308		return err
309	}
310	_, _, err := c.cmd(250, "NOOP")
311	return err
312}
313
314// quit sends the QUIT command and closes the connection to the server.
315func (c *smtpClient) quit() error {
316	if err := c.hello(); err != nil {
317		return err
318	}
319	_, _, err := c.cmd(221, "QUIT")
320	if err != nil {
321		return err
322	}
323	return c.text.Close()
324}
325
326// validateLine checks to see if a line has CR or LF as per RFC 5321
327func validateLine(line string) error {
328	if strings.ContainsAny(line, "\n\r") {
329		return errors.New("smtp: A line must not contain CR or LF")
330	}
331	return nil
332}
333