Initial commit

This commit is contained in:
2018-03-05 12:19:04 +00:00
commit 811b90224f
114 changed files with 16465 additions and 0 deletions

26
vendor/github.com/emersion/go-smtp/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,26 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
/main.go

5
vendor/github.com/emersion/go-smtp/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,5 @@
language: go
go:
- 1.7
script: bash <(curl -sL https://gist.github.com/emersion/49d4dda535497002639626bd9e16480c/raw/codecov-go.sh)
after_script: bash <(curl -s https://codecov.io/bash)

24
vendor/github.com/emersion/go-smtp/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,24 @@
The MIT License (MIT)
Copyright (c) 2010 The Go Authors
Copyright (c) 2014 Gleez Technologies
Copyright (c) 2016 emersion
Copyright (c) 2016 Proton Technologies AG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

125
vendor/github.com/emersion/go-smtp/README.md generated vendored Executable file
View File

@@ -0,0 +1,125 @@
# go-smtp
[![GoDoc](https://godoc.org/github.com/emersion/go-smtp?status.svg)](https://godoc.org/github.com/emersion/go-smtp)
[![Build Status](https://travis-ci.org/emersion/go-smtp.svg?branch=master)](https://travis-ci.org/emersion/go-smtp)
[![codecov](https://codecov.io/gh/emersion/go-smtp/branch/master/graph/badge.svg)](https://codecov.io/gh/emersion/go-smtp)
[![stability-unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)](https://github.com/emersion/stability-badges#unstable)
[![Go Report Card](https://goreportcard.com/badge/github.com/emersion/go-smtp)](https://goreportcard.com/report/github.com/emersion/go-smtp)
An ESMTP client and server library written in Go.
## Features
* ESMTP client & server implementing [RFC 5321](https://tools.ietf.org/html/rfc5321)
* Support for SMTP [AUTH](https://tools.ietf.org/html/rfc4954) and [PIPELINING](https://tools.ietf.org/html/rfc2920)
* UTF-8 support for subject and message
## Usage
### Client
```go
package main
import (
"log"
"strings"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
)
func main() {
// Set up authentication information.
auth := sasl.NewPlainClient("", "user@example.com", "password")
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
to := []string{"recipient@example.net"}
msg := strings.NewReader("To: recipient@example.net\r\n" +
"Subject: discount Gophers!\r\n" +
"\r\n" +
"This is the email body.\r\n")
err := smtp.SendMail("mail.example.com:25", auth, "sender@example.org", to, msg)
if err != nil {
log.Fatal(err)
}
}
```
If you need more control, you can use `Client` instead.
### Server
```go
package main
import (
"errors"
"io/ioutil"
"log"
"github.com/emersion/go-smtp"
)
type Backend struct{}
func (bkd *Backend) Login(username, password string) (smtp.User, error) {
if username != "username" || password != "password" {
return nil, errors.New("Invalid username or password")
}
return &User{}, nil
}
type User struct{}
func (u *User) Send(from string, to []string, r io.Reader) error {
log.Println("Sending message:", from, to)
if b, err := ioutil.ReadAll(r); err != nil {
return err
} else {
log.Println("Data:", string(b))
}
return nil
}
func (u *User) Logout() error {
return nil
}
func main() {
be := &Backend{}
s := smtp.NewServer(be)
s.Addr = ":1025"
s.Domain = "localhost"
s.MaxIdleSeconds = 300
s.MaxMessageBytes = 1024 * 1024
s.MaxRecipients = 50
s.AllowInsecureAuth = true
log.Println("Starting server at", s.Addr)
if err := s.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
```
You can use the server manually with `telnet`:
```
$ telnet localhost 1025
EHLO localhost
AUTH PLAIN
AHVzZXJuYW1lAHBhc3N3b3Jk
MAIL FROM:<root@nsa.gov>
RCPT TO:<root@gchq.gov.uk>
DATA
Hey <3
.
```
## Licence
MIT

19
vendor/github.com/emersion/go-smtp/backend.go generated vendored Normal file
View File

@@ -0,0 +1,19 @@
package smtp
import (
"io"
)
// A SMTP server backend.
type Backend interface {
// Authenticate a user.
Login(username, password string) (User, error)
}
// An authenticated user.
type User interface {
// Send an e-mail.
Send(from string, to []string, r io.Reader) error
// Logout is called when this User will no longer be used.
Logout() error
}

384
vendor/github.com/emersion/go-smtp/client.go generated vendored Normal file
View File

@@ -0,0 +1,384 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package smtp
import (
"crypto/tls"
"encoding/base64"
"errors"
"io"
"net"
"net/textproto"
"strings"
"github.com/emersion/go-sasl"
)
// A Client represents a client connection to an SMTP server.
type Client struct {
// Text is the textproto.Conn used by the Client. It is exported to allow for
// clients to add extensions.
Text *textproto.Conn
// keep a reference to the connection so it can be used to create a TLS
// connection later
conn net.Conn
// whether the Client is using TLS
tls bool
serverName string
// map of supported extensions
ext map[string]string
// supported auth mechanisms
auth []string
localName string // the name to use in HELO/EHLO
didHello bool // whether we've said HELO/EHLO
helloError error // the error from the hello
}
// Dial returns a new Client connected to an SMTP server at addr.
// The addr must include a port, as in "mail.example.com:smtp".
func Dial(addr string) (*Client, error) {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(addr)
return NewClient(conn, host)
}
// NewClient returns a new Client using an existing connection and host as a
// server name to be used when authenticating.
func NewClient(conn net.Conn, host string) (*Client, error) {
text := textproto.NewConn(conn)
_, _, err := text.ReadResponse(220)
if err != nil {
text.Close()
return nil, err
}
c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
return c, nil
}
// Close closes the connection.
func (c *Client) Close() error {
return c.Text.Close()
}
// hello runs a hello exchange if needed.
func (c *Client) hello() error {
if !c.didHello {
c.didHello = true
err := c.ehlo()
if err != nil {
c.helloError = c.helo()
}
}
return c.helloError
}
// Hello sends a HELO or EHLO to the server as the given host name.
// Calling this method is only necessary if the client needs control
// over the host name used. The client will introduce itself as "localhost"
// automatically otherwise. If Hello is called, it must be called before
// any of the other methods.
func (c *Client) Hello(localName string) error {
if c.didHello {
return errors.New("smtp: Hello called after other methods")
}
c.localName = localName
return c.hello()
}
// cmd is a convenience function that sends a command and returns the response
func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
id, err := c.Text.Cmd(format, args...)
if err != nil {
return 0, "", err
}
c.Text.StartResponse(id)
defer c.Text.EndResponse(id)
code, msg, err := c.Text.ReadResponse(expectCode)
return code, msg, err
}
// helo sends the HELO greeting to the server. It should be used only when the
// server does not support ehlo.
func (c *Client) helo() error {
c.ext = nil
_, _, err := c.cmd(250, "HELO %s", c.localName)
return err
}
// ehlo sends the EHLO (extended hello) greeting to the server. It
// should be the preferred greeting for servers that support it.
func (c *Client) ehlo() error {
_, msg, err := c.cmd(250, "EHLO %s", c.localName)
if err != nil {
return err
}
ext := make(map[string]string)
extList := strings.Split(msg, "\n")
if len(extList) > 1 {
extList = extList[1:]
for _, line := range extList {
args := strings.SplitN(line, " ", 2)
if len(args) > 1 {
ext[args[0]] = args[1]
} else {
ext[args[0]] = ""
}
}
}
if mechs, ok := ext["AUTH"]; ok {
c.auth = strings.Split(mechs, " ")
}
c.ext = ext
return err
}
// StartTLS sends the STARTTLS command and encrypts all further communication.
// Only servers that advertise the STARTTLS extension support this function.
func (c *Client) StartTLS(config *tls.Config) error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(220, "STARTTLS")
if err != nil {
return err
}
c.conn = tls.Client(c.conn, config)
c.Text = textproto.NewConn(c.conn)
c.tls = true
return c.ehlo()
}
// TLSConnectionState returns the client's TLS connection state.
// The return values are their zero values if StartTLS did
// not succeed.
func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
tc, ok := c.conn.(*tls.Conn)
if !ok {
return
}
return tc.ConnectionState(), true
}
// Verify checks the validity of an email address on the server.
// If Verify returns nil, the address is valid. A non-nil return
// does not necessarily indicate an invalid address. Many servers
// will not verify addresses for security reasons.
func (c *Client) Verify(addr string) error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "VRFY %s", addr)
return err
}
// Auth authenticates a client using the provided authentication mechanism.
// A failed authentication closes the connection.
// Only servers that advertise the AUTH extension support this function.
func (c *Client) Auth(a sasl.Client) error {
if err := c.hello(); err != nil {
return err
}
encoding := base64.StdEncoding
mech, resp, err := a.Start()
if err != nil {
c.Quit()
return err
}
resp64 := make([]byte, encoding.EncodedLen(len(resp)))
encoding.Encode(resp64, resp)
code, msg64, err := c.cmd(0, "AUTH %s %s", mech, resp64)
for err == nil {
var msg []byte
switch code {
case 334:
msg, err = encoding.DecodeString(msg64)
case 235:
// the last message isn't base64 because it isn't a challenge
msg = []byte(msg64)
default:
err = &textproto.Error{Code: code, Msg: msg64}
}
if err == nil {
if code == 334 {
resp, err = a.Next(msg)
} else {
resp = nil
}
}
if err != nil {
// abort the AUTH
c.cmd(501, "*")
c.Quit()
break
}
if resp == nil {
break
}
resp64 = make([]byte, encoding.EncodedLen(len(resp)))
encoding.Encode(resp64, resp)
code, msg64, err = c.cmd(0, string(resp64))
}
return err
}
// Mail issues a MAIL command to the server using the provided email address.
// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
// parameter.
// This initiates a mail transaction and is followed by one or more Rcpt calls.
func (c *Client) Mail(from string) error {
if err := c.hello(); err != nil {
return err
}
cmdStr := "MAIL FROM:<%s>"
if c.ext != nil {
if _, ok := c.ext["8BITMIME"]; ok {
cmdStr += " BODY=8BITMIME"
}
}
_, _, err := c.cmd(250, cmdStr, from)
return err
}
// Rcpt issues a RCPT command to the server using the provided email address.
// A call to Rcpt must be preceded by a call to Mail and may be followed by
// a Data call or another Rcpt call.
func (c *Client) Rcpt(to string) error {
_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
return err
}
type dataCloser struct {
c *Client
io.WriteCloser
}
func (d *dataCloser) Close() error {
d.WriteCloser.Close()
_, _, err := d.c.Text.ReadResponse(250)
return err
}
// Data issues a DATA command to the server and returns a writer that
// can be used to write the mail headers and body. The caller should
// close the writer before calling any more methods on c. A call to
// Data must be preceded by one or more calls to Rcpt.
func (c *Client) Data() (io.WriteCloser, error) {
_, _, err := c.cmd(354, "DATA")
if err != nil {
return nil, err
}
return &dataCloser{c, c.Text.DotWriter()}, nil
}
var testHookStartTLS func(*tls.Config) // nil, except for tests
// SendMail connects to the server at addr, switches to TLS if
// possible, authenticates with the optional mechanism a if possible,
// and then sends an email from address from, to addresses to, with
// message r.
// The addr must include a port, as in "mail.example.com:smtp".
//
// The addresses in the to parameter are the SMTP RCPT addresses.
//
// The r parameter should be an RFC 822-style email with headers
// first, a blank line, and then the message body. The lines of r
// should be CRLF terminated. The r headers should usually include
// fields such as "From", "To", "Subject", and "Cc". Sending "Bcc"
// messages is accomplished by including an email address in the to
// parameter but not including it in the r headers.
//
// The SendMail function and the the net/smtp package are low-level
// mechanisms and provide no support for DKIM signing, MIME
// attachments (see the mime/multipart package), or other mail
// functionality. Higher-level packages exist outside of the standard
// library.
func SendMail(addr string, a sasl.Client, from string, to []string, r io.Reader) error {
c, err := Dial(addr)
if err != nil {
return err
}
defer c.Close()
if err = c.hello(); err != nil {
return err
}
if ok, _ := c.Extension("STARTTLS"); ok {
config := &tls.Config{ServerName: c.serverName}
if testHookStartTLS != nil {
testHookStartTLS(config)
}
if err = c.StartTLS(config); err != nil {
return err
}
}
if a != nil && c.ext != nil {
if _, ok := c.ext["AUTH"]; ok {
if err = c.Auth(a); err != nil {
return err
}
}
}
if err = c.Mail(from); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = io.Copy(w, r)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}
// Extension reports whether an extension is support by the server.
// The extension name is case-insensitive. If the extension is supported,
// Extension also returns a string that contains any parameters the
// server specifies for the extension.
func (c *Client) Extension(ext string) (bool, string) {
if err := c.hello(); err != nil {
return false, ""
}
if c.ext == nil {
return false, ""
}
ext = strings.ToUpper(ext)
param, ok := c.ext[ext]
return ok, param
}
// Reset sends the RSET command to the server, aborting the current mail
// transaction.
func (c *Client) Reset() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "RSET")
return err
}
// Quit sends the QUIT command and closes the connection to the server.
func (c *Client) Quit() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(221, "QUIT")
if err != nil {
return err
}
return c.Text.Close()
}

432
vendor/github.com/emersion/go-smtp/conn.go generated vendored Normal file
View File

@@ -0,0 +1,432 @@
package smtp
import (
"crypto/tls"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net"
"net/textproto"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
// A SMTP message.
type message struct {
// The message contents.
io.Reader
// The sender e-mail address.
From string
// The recipients e-mail addresses.
To []string
}
type Conn struct {
conn net.Conn
text *textproto.Conn
server *Server
helo string
msg *message
nbrErrors int
user User
locker sync.Mutex
}
func newConn(c net.Conn, s *Server) *Conn {
sc := &Conn{
server: s,
conn: c,
}
sc.init()
return sc
}
func (c *Conn) init() {
var rwc io.ReadWriteCloser = c.conn
if c.server.Debug != nil {
rwc = struct {
io.Reader
io.Writer
io.Closer
}{
io.TeeReader(c.conn, c.server.Debug),
io.MultiWriter(c.conn, c.server.Debug),
c.conn,
}
}
c.text = textproto.NewConn(rwc)
}
// Commands are dispatched to the appropriate handler functions.
func (c *Conn) handle(cmd string, arg string) {
if cmd == "" {
c.WriteResponse(500, "Speak up")
return
}
switch cmd {
case "SEND", "SOML", "SAML", "EXPN", "HELP", "TURN":
// These commands are not implemented in any state
c.WriteResponse(502, fmt.Sprintf("%v command not implemented", cmd))
case "HELO", "EHLO":
c.handleGreet((cmd == "EHLO"), arg)
case "MAIL":
c.handleMail(arg)
case "RCPT":
c.handleRcpt(arg)
case "VRFY":
c.WriteResponse(252, "Cannot VRFY user, but will accept message")
case "NOOP":
c.WriteResponse(250, "I have sucessfully done nothing")
case "RSET": // Reset session
c.reset()
c.WriteResponse(250, "Session reset")
case "DATA":
c.handleData(arg)
case "QUIT":
c.WriteResponse(221, "Goodnight and good luck")
c.Close()
case "AUTH":
c.handleAuth(arg)
case "STARTTLS":
c.handleStartTLS()
default:
c.WriteResponse(500, fmt.Sprintf("Syntax error, %v command unrecognized", cmd))
c.nbrErrors++
if c.nbrErrors > 3 {
c.WriteResponse(500, "Too many unrecognized commands")
c.Close()
}
}
}
func (c *Conn) Server() *Server {
return c.server
}
func (c *Conn) User() User {
c.locker.Lock()
defer c.locker.Unlock()
return c.user
}
func (c *Conn) SetUser(user User) {
c.locker.Lock()
defer c.locker.Unlock()
c.user = user
}
func (c *Conn) Close() error {
if user := c.User(); user != nil {
user.Logout()
}
return c.conn.Close()
}
// Check if this connection is encrypted.
func (c *Conn) IsTLS() bool {
_, ok := c.conn.(*tls.Conn)
return ok
}
// GREET state -> waiting for HELO
func (c *Conn) handleGreet(enhanced bool, arg string) {
if !enhanced {
domain, err := parseHelloArgument(arg)
if err != nil {
c.WriteResponse(501, "Domain/address argument required for HELO")
return
}
c.helo = domain
c.WriteResponse(250, fmt.Sprintf("Hello %s", domain))
} else {
domain, err := parseHelloArgument(arg)
if err != nil {
c.WriteResponse(501, "Domain/address argument required for EHLO")
return
}
c.helo = domain
caps := []string{}
caps = append(caps, c.server.caps...)
if c.server.TLSConfig != nil && !c.IsTLS() {
caps = append(caps, "STARTTLS")
}
if c.IsTLS() || c.server.AllowInsecureAuth {
authCap := "AUTH"
for name, _ := range c.server.auths {
authCap += " " + name
}
caps = append(caps, authCap)
}
if c.server.MaxMessageBytes > 0 {
caps = append(caps, fmt.Sprintf("SIZE %v", c.server.MaxMessageBytes))
}
args := []string{"Hello " + domain}
args = append(args, caps...)
c.WriteResponse(250, args...)
}
}
// READY state -> waiting for MAIL
func (c *Conn) handleMail(arg string) {
if c.helo == "" {
c.WriteResponse(502, "Please introduce yourself first.")
return
}
if c.msg == nil {
c.WriteResponse(502, "Please authenticate first.")
return
}
// Match FROM, while accepting '>' as quoted pair and in double quoted strings
// (?i) makes the regex case insensitive, (?:) is non-grouping sub-match
re := regexp.MustCompile("(?i)^FROM:\\s*<((?:\\\\>|[^>])+|\"[^\"]+\"@[^>]+)>( [\\w= ]+)?$")
m := re.FindStringSubmatch(arg)
if m == nil {
c.WriteResponse(501, "Was expecting MAIL arg syntax of FROM:<address>")
return
}
from := m[1]
// This is where the Conn may put BODY=8BITMIME, but we already
// read the DATA as bytes, so it does not effect our processing.
if m[2] != "" {
args, err := parseArgs(m[2])
if err != nil {
c.WriteResponse(501, "Unable to parse MAIL ESMTP parameters")
return
}
if args["SIZE"] != "" {
size, err := strconv.ParseInt(args["SIZE"], 10, 32)
if err != nil {
c.WriteResponse(501, "Unable to parse SIZE as an integer")
return
}
if c.server.MaxMessageBytes > 0 && int(size) > c.server.MaxMessageBytes {
c.WriteResponse(552, "Max message size exceeded")
return
}
}
}
c.msg.From = from
c.WriteResponse(250, fmt.Sprintf("Roger, accepting mail from <%v>", from))
}
// MAIL state -> waiting for RCPTs followed by DATA
func (c *Conn) handleRcpt(arg string) {
if c.msg == nil || c.msg.From == "" {
c.WriteResponse(502, "Missing MAIL FROM command.")
return
}
if (len(arg) < 4) || (strings.ToUpper(arg[0:3]) != "TO:") {
c.WriteResponse(501, "Was expecting RCPT arg syntax of TO:<address>")
return
}
// TODO: This trim is probably too forgiving
recipient := strings.Trim(arg[3:], "<> ")
if c.server.MaxRecipients > 0 && len(c.msg.To) >= c.server.MaxRecipients {
c.WriteResponse(552, fmt.Sprintf("Maximum limit of %v recipients reached", c.server.MaxRecipients))
return
}
c.msg.To = append(c.msg.To, recipient)
c.WriteResponse(250, fmt.Sprintf("I'll make sure <%v> gets this", recipient))
}
func (c *Conn) handleAuth(arg string) {
if c.helo == "" {
c.WriteResponse(502, "Please introduce yourself first.")
return
}
if arg == "" {
c.WriteResponse(502, "Missing parameter")
return
}
parts := strings.Fields(arg)
mechanism := strings.ToUpper(parts[0])
// Parse client initial response if there is one
var ir []byte
if len(parts) > 1 {
var err error
ir, err = base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return
}
}
newSasl, ok := c.server.auths[mechanism]
if !ok {
c.WriteResponse(504, "Unsupported authentication mechanism")
return
}
sasl := newSasl(c)
response := ir
for {
challenge, done, err := sasl.Next(response)
if err != nil {
c.WriteResponse(454, err.Error())
return
}
if done {
break
}
encoded := ""
if len(challenge) > 0 {
encoded = base64.StdEncoding.EncodeToString(challenge)
}
c.WriteResponse(334, encoded)
encoded, err = c.ReadLine()
if err != nil {
return // TODO: error handling
}
response, err = base64.StdEncoding.DecodeString(encoded)
if err != nil {
c.WriteResponse(454, "Invalid base64 data")
return
}
}
if c.User() != nil {
c.WriteResponse(235, "Authentication succeeded")
c.msg = &message{}
}
}
func (c *Conn) handleStartTLS() {
if c.IsTLS() {
c.WriteResponse(502, "Already running in TLS")
return
}
if c.server.TLSConfig == nil {
c.WriteResponse(502, "TLS not supported")
return
}
c.WriteResponse(220, "Ready to start TLS")
// Upgrade to TLS
var tlsConn *tls.Conn
tlsConn = tls.Server(c.conn, c.server.TLSConfig)
if err := tlsConn.Handshake(); err != nil {
c.WriteResponse(550, "Handshake error")
}
c.conn = tlsConn
c.init()
// Reset envelope as a new EHLO/HELO is required after STARTTLS
c.reset()
}
// DATA
func (c *Conn) handleData(arg string) {
if arg != "" {
c.WriteResponse(501, "DATA command should not have any arguments")
return
}
if c.msg == nil || c.msg.From == "" || len(c.msg.To) == 0 {
c.WriteResponse(502, "Missing RCPT TO command.")
return
}
// We have recipients, go to accept data
c.WriteResponse(354, "Go ahead. End your data with <CR><LF>.<CR><LF>")
c.msg.Reader = newDataReader(c)
err := c.User().Send(c.msg.From, c.msg.To, c.msg.Reader)
io.Copy(ioutil.Discard, c.msg.Reader) // Make sure all the data has been consumed
if err != nil {
if smtperr, ok := err.(*smtpError); ok {
c.WriteResponse(smtperr.Code, smtperr.Message)
} else {
c.WriteResponse(554, "Error: transaction failed, blame it on the weather: "+err.Error())
}
} else {
c.WriteResponse(250, "Ok: queued")
}
c.reset()
}
func (c *Conn) Reject() {
c.WriteResponse(421, "Too busy. Try again later.")
c.Close()
}
func (c *Conn) greet() {
c.WriteResponse(220, fmt.Sprintf("%v ESMTP Service Ready", c.server.Domain))
}
// Calculate the next read or write deadline based on MaxIdleSeconds.
func (c *Conn) nextDeadline() time.Time {
if c.server.MaxIdleSeconds == 0 {
return time.Time{} // No deadline
}
return time.Now().Add(time.Duration(c.server.MaxIdleSeconds) * time.Second)
}
func (c *Conn) WriteResponse(code int, text ...string) {
// TODO: error handling
c.conn.SetDeadline(c.nextDeadline())
for i := 0; i < len(text)-1; i++ {
c.text.PrintfLine("%v-%v", code, text[i])
}
c.text.PrintfLine("%v %v", code, text[len(text)-1])
}
// Reads a line of input
func (c *Conn) ReadLine() (string, error) {
if err := c.conn.SetReadDeadline(c.nextDeadline()); err != nil {
return "", err
}
return c.text.ReadLine()
}
func (c *Conn) reset() {
if user := c.User(); user != nil {
user.Logout()
}
c.locker.Lock()
c.user = nil
c.msg = nil
c.locker.Unlock()
}

57
vendor/github.com/emersion/go-smtp/data.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package smtp
import (
"io"
)
type smtpError struct {
Code int
Message string
}
func (err *smtpError) Error() string {
return err.Message
}
var ErrDataTooLarge = &smtpError{
Code: 552,
Message: "Maximum message size exceeded",
}
type dataReader struct {
r io.Reader
limited bool
n int64 // Maximum bytes remaining
}
func newDataReader(c *Conn) io.Reader {
dr := &dataReader{
r: c.text.DotReader(),
}
if c.server.MaxMessageBytes > 0 {
dr.limited = true
dr.n = int64(c.server.MaxMessageBytes)
}
return dr
}
func (r *dataReader) Read(b []byte) (n int, err error) {
if r.limited {
if r.n <= 0 {
return 0, ErrDataTooLarge
}
if int64(len(b)) > r.n {
b = b[0:r.n]
}
}
n, err = r.r.Read(b)
if r.limited {
r.n -= int64(n)
}
return
}

66
vendor/github.com/emersion/go-smtp/parse.go generated vendored Normal file
View File

@@ -0,0 +1,66 @@
package smtp
import (
"fmt"
"regexp"
"strings"
)
func parseCmd(line string) (cmd string, arg string, err error) {
line = strings.TrimRight(line, "\r\n")
l := len(line)
switch {
case strings.HasPrefix(line, "STARTTLS"):
return "STARTTLS", "", nil
case l == 0:
return "", "", nil
case l < 4:
return "", "", fmt.Errorf("Command too short: %q", line)
case l == 4:
return strings.ToUpper(line), "", nil
case l == 5:
// Too long to be only command, too short to have args
return "", "", fmt.Errorf("Mangled command: %q", line)
}
// If we made it here, command is long enough to have args
if line[4] != ' ' {
// There wasn't a space after the command?
return "", "", fmt.Errorf("Mangled command: %q", line)
}
// I'm not sure if we should trim the args or not, but we will for now
//return strings.ToUpper(line[0:4]), strings.Trim(line[5:], " "), nil
return strings.ToUpper(line[0:4]), strings.Trim(line[5:], " \n\r"), nil
}
// Takes the arguments proceeding a command and files them
// into a map[string]string after uppercasing each key. Sample arg
// string:
// " BODY=8BITMIME SIZE=1024"
// The leading space is mandatory.
func parseArgs(arg string) (args map[string]string, err error) {
args = map[string]string{}
re := regexp.MustCompile(" (\\w+)=(\\w+)")
pm := re.FindAllStringSubmatch(arg, -1)
if pm == nil {
return nil, fmt.Errorf("Failed to parse arg string: %q", arg)
}
for _, m := range pm {
args[strings.ToUpper(m[1])] = m[2]
}
return args, nil
}
func parseHelloArgument(arg string) (string, error) {
domain := arg
if idx := strings.IndexRune(arg, ' '); idx >= 0 {
domain = arg[:idx]
}
if domain == "" {
return "", fmt.Errorf("Invalid domain")
}
return domain, nil
}

187
vendor/github.com/emersion/go-smtp/server.go generated vendored Executable file
View File

@@ -0,0 +1,187 @@
package smtp
import (
"crypto/tls"
"errors"
"io"
"net"
"sync"
"github.com/emersion/go-sasl"
)
// A function that creates SASL servers.
type SaslServerFactory func(conn *Conn) sasl.Server
// A SMTP server.
type Server struct {
// TCP address to listen on.
Addr string
// The server TLS configuration.
TLSConfig *tls.Config
Domain string
MaxRecipients int
MaxIdleSeconds int
MaxMessageBytes int
AllowInsecureAuth bool
Debug io.Writer
// The server backend.
Backend Backend
listener net.Listener
caps []string
auths map[string]SaslServerFactory
locker sync.Mutex
conns map[*Conn]struct{}
}
// New creates a new SMTP server.
func NewServer(be Backend) *Server {
return &Server{
Backend: be,
caps: []string{"PIPELINING", "8BITMIME"},
auths: map[string]SaslServerFactory{
sasl.Plain: func(conn *Conn) sasl.Server {
return sasl.NewPlainServer(func(identity, username, password string) error {
if identity != "" && identity != username {
return errors.New("Identities not supported")
}
user, err := be.Login(username, password)
if err != nil {
return err
}
conn.SetUser(user)
return nil
})
},
},
conns: make(map[*Conn]struct{}),
}
}
// Serve accepts incoming connections on the Listener l.
func (s *Server) Serve(l net.Listener) error {
s.listener = l
defer s.Close()
for {
c, err := l.Accept()
if err != nil {
return err
}
go s.handleConn(newConn(c, s))
}
}
func (s *Server) handleConn(c *Conn) error {
s.locker.Lock()
s.conns[c] = struct{}{}
s.locker.Unlock()
defer func() {
c.Close()
s.locker.Lock()
delete(s.conns, c)
s.locker.Unlock()
}()
c.greet()
for {
line, err := c.ReadLine()
if err == nil {
cmd, arg, err := parseCmd(line)
if err != nil {
c.nbrErrors++
c.WriteResponse(501, "Bad command")
continue
}
c.handle(cmd, arg)
} else {
if err == io.EOF {
return nil
}
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
c.WriteResponse(221, "Idle timeout, bye bye")
return nil
}
c.WriteResponse(221, "Connection error, sorry")
return err
}
}
}
// ListenAndServe listens on the TCP network address s.Addr and then calls Serve
// to handle requests on incoming connections.
//
// If s.Addr is blank, ":smtp" is used.
func (s *Server) ListenAndServe() error {
addr := s.Addr
if addr == "" {
addr = ":smtp"
}
l, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return s.Serve(l)
}
// ListenAndServeTLS listens on the TCP network address s.Addr and then calls
// Serve to handle requests on incoming TLS connections.
//
// If s.Addr is blank, ":smtps" is used.
func (s *Server) ListenAndServeTLS() error {
addr := s.Addr
if addr == "" {
addr = ":smtps"
}
l, err := tls.Listen("tcp", addr, s.TLSConfig)
if err != nil {
return err
}
return s.Serve(l)
}
// Close stops the server.
func (s *Server) Close() {
s.listener.Close()
s.locker.Lock()
defer s.locker.Unlock()
for conn := range s.conns {
conn.Close()
}
}
// EnableAuth enables an authentication mechanism on this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the SMTP protocol.
func (s *Server) EnableAuth(name string, f SaslServerFactory) {
s.auths[name] = f
}
// ForEachConn iterates through all opened connections.
func (s *Server) ForEachConn(f func(*Conn)) {
s.locker.Lock()
defer s.locker.Unlock()
for conn := range s.conns {
f(conn)
}
}

7
vendor/github.com/emersion/go-smtp/smtp.go generated vendored Normal file
View File

@@ -0,0 +1,7 @@
// Package smtp implements the Simple Mail Transfer Protocol as defined in RFC 5321.
// It also implements the following extensions:
// 8BITMIME RFC 1652
// AUTH RFC 2554
// STARTTLS RFC 3207
// Additional extensions may be handled by other packages.
package smtp