smtp.go 8.4 KB

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