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