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