Parcourir la source

add the smtp code from the go 1.14.1 release

tags/v2.1.0-rc1
Shivaram Lingamneni il y a 4 ans
Parent
révision
6e630a0b5c
3 fichiers modifiés avec 567 ajouts et 0 suppressions
  1. 27
    0
      irc/smtp/LICENSE
  2. 110
    0
      irc/smtp/auth.go
  3. 430
    0
      irc/smtp/smtp.go

+ 27
- 0
irc/smtp/LICENSE Voir le fichier

@@ -0,0 +1,27 @@
1
+Copyright (c) 2009 The Go Authors. All rights reserved.
2
+
3
+Redistribution and use in source and binary forms, with or without
4
+modification, are permitted provided that the following conditions are
5
+met:
6
+
7
+   * Redistributions of source code must retain the above copyright
8
+notice, this list of conditions and the following disclaimer.
9
+   * Redistributions in binary form must reproduce the above
10
+copyright notice, this list of conditions and the following disclaimer
11
+in the documentation and/or other materials provided with the
12
+distribution.
13
+   * Neither the name of Google Inc. nor the names of its
14
+contributors may be used to endorse or promote products derived from
15
+this software without specific prior written permission.
16
+
17
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 110
- 0
irc/smtp/auth.go Voir le fichier

@@ -0,0 +1,110 @@
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
6
+
7
+import (
8
+	"crypto/hmac"
9
+	"crypto/md5"
10
+	"errors"
11
+	"fmt"
12
+)
13
+
14
+// Auth is implemented by an SMTP authentication mechanism.
15
+type Auth interface {
16
+	// Start begins an authentication with a server.
17
+	// It returns the name of the authentication protocol
18
+	// and optionally data to include in the initial AUTH message
19
+	// sent to the server. It can return proto == "" to indicate
20
+	// that the authentication should be skipped.
21
+	// If it returns a non-nil error, the SMTP client aborts
22
+	// the authentication attempt and closes the connection.
23
+	Start(server *ServerInfo) (proto string, toServer []byte, err error)
24
+
25
+	// Next continues the authentication. The server has just sent
26
+	// the fromServer data. If more is true, the server expects a
27
+	// response, which Next should return as toServer; otherwise
28
+	// Next should return toServer == nil.
29
+	// If Next returns a non-nil error, the SMTP client aborts
30
+	// the authentication attempt and closes the connection.
31
+	Next(fromServer []byte, more bool) (toServer []byte, err error)
32
+}
33
+
34
+// ServerInfo records information about an SMTP server.
35
+type ServerInfo struct {
36
+	Name string   // SMTP server name
37
+	TLS  bool     // using TLS, with valid certificate for Name
38
+	Auth []string // advertised authentication mechanisms
39
+}
40
+
41
+type plainAuth struct {
42
+	identity, username, password string
43
+	host                         string
44
+}
45
+
46
+// PlainAuth returns an Auth that implements the PLAIN authentication
47
+// mechanism as defined in RFC 4616. The returned Auth uses the given
48
+// username and password to authenticate to host and act as identity.
49
+// Usually identity should be the empty string, to act as username.
50
+//
51
+// PlainAuth will only send the credentials if the connection is using TLS
52
+// or is connected to localhost. Otherwise authentication will fail with an
53
+// error, without sending the credentials.
54
+func PlainAuth(identity, username, password, host string) Auth {
55
+	return &plainAuth{identity, username, password, host}
56
+}
57
+
58
+func isLocalhost(name string) bool {
59
+	return name == "localhost" || name == "127.0.0.1" || name == "::1"
60
+}
61
+
62
+func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) {
63
+	// Must have TLS, or else localhost server.
64
+	// Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo.
65
+	// In particular, it doesn't matter if the server advertises PLAIN auth.
66
+	// That might just be the attacker saying
67
+	// "it's ok, you can trust me with your password."
68
+	if !server.TLS && !isLocalhost(server.Name) {
69
+		return "", nil, errors.New("unencrypted connection")
70
+	}
71
+	if server.Name != a.host {
72
+		return "", nil, errors.New("wrong host name")
73
+	}
74
+	resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
75
+	return "PLAIN", resp, nil
76
+}
77
+
78
+func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
79
+	if more {
80
+		// We've already sent everything.
81
+		return nil, errors.New("unexpected server challenge")
82
+	}
83
+	return nil, nil
84
+}
85
+
86
+type cramMD5Auth struct {
87
+	username, secret string
88
+}
89
+
90
+// CRAMMD5Auth returns an Auth that implements the CRAM-MD5 authentication
91
+// mechanism as defined in RFC 2195.
92
+// The returned Auth uses the given username and secret to authenticate
93
+// to the server using the challenge-response mechanism.
94
+func CRAMMD5Auth(username, secret string) Auth {
95
+	return &cramMD5Auth{username, secret}
96
+}
97
+
98
+func (a *cramMD5Auth) Start(server *ServerInfo) (string, []byte, error) {
99
+	return "CRAM-MD5", nil, nil
100
+}
101
+
102
+func (a *cramMD5Auth) Next(fromServer []byte, more bool) ([]byte, error) {
103
+	if more {
104
+		d := hmac.New(md5.New, []byte(a.secret))
105
+		d.Write(fromServer)
106
+		s := make([]byte, 0, d.Size())
107
+		return []byte(fmt.Sprintf("%s %x", a.username, d.Sum(s))), nil
108
+	}
109
+	return nil, nil
110
+}

+ 430
- 0
irc/smtp/smtp.go Voir le fichier

@@ -0,0 +1,430 @@
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
+//
12
+// The smtp package is frozen and is not accepting new features.
13
+// Some external packages provide more functionality. See:
14
+//
15
+//   https://godoc.org/?q=smtp
16
+package smtp
17
+
18
+import (
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.
30
+type Client struct {
31
+	// Text is the textproto.Conn used by the Client. It is exported to allow for
32
+	// clients to add extensions.
33
+	Text *textproto.Conn
34
+	// keep a reference to the connection so it can be used to create a TLS
35
+	// connection later
36
+	conn net.Conn
37
+	// whether the Client is using TLS
38
+	tls        bool
39
+	serverName string
40
+	// map of supported extensions
41
+	ext map[string]string
42
+	// supported auth mechanisms
43
+	auth       []string
44
+	localName  string // the name to use in HELO/EHLO
45
+	didHello   bool   // whether we've said HELO/EHLO
46
+	helloError error  // the error from the hello
47
+}
48
+
49
+// Dial returns a new Client connected to an SMTP server at addr.
50
+// The addr must include a port, as in "mail.example.com:smtp".
51
+func Dial(addr string) (*Client, error) {
52
+	conn, err := net.Dial("tcp", addr)
53
+	if err != nil {
54
+		return nil, err
55
+	}
56
+	host, _, _ := net.SplitHostPort(addr)
57
+	return NewClient(conn, host)
58
+}
59
+
60
+// NewClient returns a new Client using an existing connection and host as a
61
+// server name to be used when authenticating.
62
+func NewClient(conn net.Conn, host string) (*Client, error) {
63
+	text := textproto.NewConn(conn)
64
+	_, _, err := text.ReadResponse(220)
65
+	if err != nil {
66
+		text.Close()
67
+		return nil, err
68
+	}
69
+	c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
70
+	_, c.tls = conn.(*tls.Conn)
71
+	return c, nil
72
+}
73
+
74
+// Close closes the connection.
75
+func (c *Client) Close() error {
76
+	return c.Text.Close()
77
+}
78
+
79
+// hello runs a hello exchange if needed.
80
+func (c *Client) hello() error {
81
+	if !c.didHello {
82
+		c.didHello = true
83
+		err := c.ehlo()
84
+		if err != nil {
85
+			c.helloError = c.helo()
86
+		}
87
+	}
88
+	return c.helloError
89
+}
90
+
91
+// Hello sends a HELO or EHLO to the server as the given host name.
92
+// Calling this method is only necessary if the client needs control
93
+// over the host name used. The client will introduce itself as "localhost"
94
+// automatically otherwise. If Hello is called, it must be called before
95
+// any of the other methods.
96
+func (c *Client) Hello(localName string) error {
97
+	if err := validateLine(localName); err != nil {
98
+		return err
99
+	}
100
+	if c.didHello {
101
+		return errors.New("smtp: Hello called after other methods")
102
+	}
103
+	c.localName = localName
104
+	return c.hello()
105
+}
106
+
107
+// cmd is a convenience function that sends a command and returns the response
108
+func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
109
+	id, err := c.Text.Cmd(format, args...)
110
+	if err != nil {
111
+		return 0, "", err
112
+	}
113
+	c.Text.StartResponse(id)
114
+	defer c.Text.EndResponse(id)
115
+	code, msg, err := c.Text.ReadResponse(expectCode)
116
+	return code, msg, err
117
+}
118
+
119
+// helo sends the HELO greeting to the server. It should be used only when the
120
+// server does not support ehlo.
121
+func (c *Client) helo() error {
122
+	c.ext = nil
123
+	_, _, err := c.cmd(250, "HELO %s", c.localName)
124
+	return err
125
+}
126
+
127
+// ehlo sends the EHLO (extended hello) greeting to the server. It
128
+// should be the preferred greeting for servers that support it.
129
+func (c *Client) ehlo() error {
130
+	_, msg, err := c.cmd(250, "EHLO %s", c.localName)
131
+	if err != nil {
132
+		return err
133
+	}
134
+	ext := make(map[string]string)
135
+	extList := strings.Split(msg, "\n")
136
+	if len(extList) > 1 {
137
+		extList = extList[1:]
138
+		for _, line := range extList {
139
+			args := strings.SplitN(line, " ", 2)
140
+			if len(args) > 1 {
141
+				ext[args[0]] = args[1]
142
+			} else {
143
+				ext[args[0]] = ""
144
+			}
145
+		}
146
+	}
147
+	if mechs, ok := ext["AUTH"]; ok {
148
+		c.auth = strings.Split(mechs, " ")
149
+	}
150
+	c.ext = ext
151
+	return err
152
+}
153
+
154
+// StartTLS sends the STARTTLS command and encrypts all further communication.
155
+// Only servers that advertise the STARTTLS extension support this function.
156
+func (c *Client) StartTLS(config *tls.Config) error {
157
+	if err := c.hello(); err != nil {
158
+		return err
159
+	}
160
+	_, _, err := c.cmd(220, "STARTTLS")
161
+	if err != nil {
162
+		return err
163
+	}
164
+	c.conn = tls.Client(c.conn, config)
165
+	c.Text = textproto.NewConn(c.conn)
166
+	c.tls = true
167
+	return c.ehlo()
168
+}
169
+
170
+// TLSConnectionState returns the client's TLS connection state.
171
+// The return values are their zero values if StartTLS did
172
+// not succeed.
173
+func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
174
+	tc, ok := c.conn.(*tls.Conn)
175
+	if !ok {
176
+		return
177
+	}
178
+	return tc.ConnectionState(), true
179
+}
180
+
181
+// Verify checks the validity of an email address on the server.
182
+// If Verify returns nil, the address is valid. A non-nil return
183
+// does not necessarily indicate an invalid address. Many servers
184
+// will not verify addresses for security reasons.
185
+func (c *Client) Verify(addr string) error {
186
+	if err := validateLine(addr); err != nil {
187
+		return err
188
+	}
189
+	if err := c.hello(); err != nil {
190
+		return err
191
+	}
192
+	_, _, err := c.cmd(250, "VRFY %s", addr)
193
+	return err
194
+}
195
+
196
+// Auth authenticates a client using the provided authentication mechanism.
197
+// A failed authentication closes the connection.
198
+// Only servers that advertise the AUTH extension support this function.
199
+func (c *Client) Auth(a Auth) error {
200
+	if err := c.hello(); err != nil {
201
+		return err
202
+	}
203
+	encoding := base64.StdEncoding
204
+	mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
205
+	if err != nil {
206
+		c.Quit()
207
+		return err
208
+	}
209
+	resp64 := make([]byte, encoding.EncodedLen(len(resp)))
210
+	encoding.Encode(resp64, resp)
211
+	code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
212
+	for err == nil {
213
+		var msg []byte
214
+		switch code {
215
+		case 334:
216
+			msg, err = encoding.DecodeString(msg64)
217
+		case 235:
218
+			// the last message isn't base64 because it isn't a challenge
219
+			msg = []byte(msg64)
220
+		default:
221
+			err = &textproto.Error{Code: code, Msg: msg64}
222
+		}
223
+		if err == nil {
224
+			resp, err = a.Next(msg, code == 334)
225
+		}
226
+		if err != nil {
227
+			// abort the AUTH
228
+			c.cmd(501, "*")
229
+			c.Quit()
230
+			break
231
+		}
232
+		if resp == nil {
233
+			break
234
+		}
235
+		resp64 = make([]byte, encoding.EncodedLen(len(resp)))
236
+		encoding.Encode(resp64, resp)
237
+		code, msg64, err = c.cmd(0, string(resp64))
238
+	}
239
+	return err
240
+}
241
+
242
+// Mail issues a MAIL command to the server using the provided email address.
243
+// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
244
+// parameter.
245
+// This initiates a mail transaction and is followed by one or more Rcpt calls.
246
+func (c *Client) Mail(from string) error {
247
+	if err := validateLine(from); err != nil {
248
+		return err
249
+	}
250
+	if err := c.hello(); err != nil {
251
+		return err
252
+	}
253
+	cmdStr := "MAIL FROM:<%s>"
254
+	if c.ext != nil {
255
+		if _, ok := c.ext["8BITMIME"]; ok {
256
+			cmdStr += " BODY=8BITMIME"
257
+		}
258
+	}
259
+	_, _, err := c.cmd(250, cmdStr, from)
260
+	return err
261
+}
262
+
263
+// Rcpt issues a RCPT command to the server using the provided email address.
264
+// A call to Rcpt must be preceded by a call to Mail and may be followed by
265
+// a Data call or another Rcpt call.
266
+func (c *Client) Rcpt(to string) error {
267
+	if err := validateLine(to); err != nil {
268
+		return err
269
+	}
270
+	_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
271
+	return err
272
+}
273
+
274
+type dataCloser struct {
275
+	c *Client
276
+	io.WriteCloser
277
+}
278
+
279
+func (d *dataCloser) Close() error {
280
+	d.WriteCloser.Close()
281
+	_, _, err := d.c.Text.ReadResponse(250)
282
+	return err
283
+}
284
+
285
+// Data issues a DATA command to the server and returns a writer that
286
+// can be used to write the mail headers and body. The caller should
287
+// close the writer before calling any more methods on c. A call to
288
+// Data must be preceded by one or more calls to Rcpt.
289
+func (c *Client) Data() (io.WriteCloser, error) {
290
+	_, _, err := c.cmd(354, "DATA")
291
+	if err != nil {
292
+		return nil, err
293
+	}
294
+	return &dataCloser{c, c.Text.DotWriter()}, nil
295
+}
296
+
297
+var testHookStartTLS func(*tls.Config) // nil, except for tests
298
+
299
+// SendMail connects to the server at addr, switches to TLS if
300
+// possible, authenticates with the optional mechanism a if possible,
301
+// and then sends an email from address from, to addresses to, with
302
+// message msg.
303
+// The addr must include a port, as in "mail.example.com:smtp".
304
+//
305
+// The addresses in the to parameter are the SMTP RCPT addresses.
306
+//
307
+// The msg parameter should be an RFC 822-style email with headers
308
+// first, a blank line, and then the message body. The lines of msg
309
+// should be CRLF terminated. The msg headers should usually include
310
+// fields such as "From", "To", "Subject", and "Cc".  Sending "Bcc"
311
+// messages is accomplished by including an email address in the to
312
+// parameter but not including it in the msg headers.
313
+//
314
+// The SendMail function and the net/smtp package are low-level
315
+// mechanisms and provide no support for DKIM signing, MIME
316
+// attachments (see the mime/multipart package), or other mail
317
+// functionality. Higher-level packages exist outside of the standard
318
+// library.
319
+func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
320
+	if err := validateLine(from); err != nil {
321
+		return err
322
+	}
323
+	for _, recp := range to {
324
+		if err := validateLine(recp); err != nil {
325
+			return err
326
+		}
327
+	}
328
+	c, err := Dial(addr)
329
+	if err != nil {
330
+		return err
331
+	}
332
+	defer c.Close()
333
+	if err = c.hello(); err != nil {
334
+		return err
335
+	}
336
+	if ok, _ := c.Extension("STARTTLS"); ok {
337
+		config := &tls.Config{ServerName: c.serverName}
338
+		if testHookStartTLS != nil {
339
+			testHookStartTLS(config)
340
+		}
341
+		if err = c.StartTLS(config); err != nil {
342
+			return err
343
+		}
344
+	}
345
+	if a != nil && c.ext != nil {
346
+		if _, ok := c.ext["AUTH"]; !ok {
347
+			return errors.New("smtp: server doesn't support AUTH")
348
+		}
349
+		if err = c.Auth(a); err != nil {
350
+			return err
351
+		}
352
+	}
353
+	if err = c.Mail(from); err != nil {
354
+		return err
355
+	}
356
+	for _, addr := range to {
357
+		if err = c.Rcpt(addr); err != nil {
358
+			return err
359
+		}
360
+	}
361
+	w, err := c.Data()
362
+	if err != nil {
363
+		return err
364
+	}
365
+	_, err = w.Write(msg)
366
+	if err != nil {
367
+		return err
368
+	}
369
+	err = w.Close()
370
+	if err != nil {
371
+		return err
372
+	}
373
+	return c.Quit()
374
+}
375
+
376
+// Extension reports whether an extension is support by the server.
377
+// The extension name is case-insensitive. If the extension is supported,
378
+// Extension also returns a string that contains any parameters the
379
+// server specifies for the extension.
380
+func (c *Client) Extension(ext string) (bool, string) {
381
+	if err := c.hello(); err != nil {
382
+		return false, ""
383
+	}
384
+	if c.ext == nil {
385
+		return false, ""
386
+	}
387
+	ext = strings.ToUpper(ext)
388
+	param, ok := c.ext[ext]
389
+	return ok, param
390
+}
391
+
392
+// Reset sends the RSET command to the server, aborting the current mail
393
+// transaction.
394
+func (c *Client) Reset() error {
395
+	if err := c.hello(); err != nil {
396
+		return err
397
+	}
398
+	_, _, err := c.cmd(250, "RSET")
399
+	return err
400
+}
401
+
402
+// Noop sends the NOOP command to the server. It does nothing but check
403
+// that the connection to the server is okay.
404
+func (c *Client) Noop() error {
405
+	if err := c.hello(); err != nil {
406
+		return err
407
+	}
408
+	_, _, err := c.cmd(250, "NOOP")
409
+	return err
410
+}
411
+
412
+// Quit sends the QUIT command and closes the connection to the server.
413
+func (c *Client) Quit() error {
414
+	if err := c.hello(); err != nil {
415
+		return err
416
+	}
417
+	_, _, err := c.cmd(221, "QUIT")
418
+	if err != nil {
419
+		return err
420
+	}
421
+	return c.Text.Close()
422
+}
423
+
424
+// validateLine checks to see if a line has CR or LF as per RFC 5321
425
+func validateLine(line string) error {
426
+	if strings.ContainsAny(line, "\n\r") {
427
+		return errors.New("smtp: A line must not contain CR or LF")
428
+	}
429
+	return nil
430
+}

Chargement…
Annuler
Enregistrer