Initial commit
This commit is contained in:
26
vendor/github.com/emersion/go-smtp/.gitignore
generated
vendored
Normal file
26
vendor/github.com/emersion/go-smtp/.gitignore
generated
vendored
Normal 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
5
vendor/github.com/emersion/go-smtp/.travis.yml
generated
vendored
Normal 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
24
vendor/github.com/emersion/go-smtp/LICENSE
generated
vendored
Normal 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
125
vendor/github.com/emersion/go-smtp/README.md
generated
vendored
Executable file
@@ -0,0 +1,125 @@
|
||||
# go-smtp
|
||||
|
||||
[](https://godoc.org/github.com/emersion/go-smtp)
|
||||
[](https://travis-ci.org/emersion/go-smtp)
|
||||
[](https://codecov.io/gh/emersion/go-smtp)
|
||||
[](https://github.com/emersion/stability-badges#unstable)
|
||||
[](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
19
vendor/github.com/emersion/go-smtp/backend.go
generated
vendored
Normal 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
384
vendor/github.com/emersion/go-smtp/client.go
generated
vendored
Normal 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
432
vendor/github.com/emersion/go-smtp/conn.go
generated
vendored
Normal 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
57
vendor/github.com/emersion/go-smtp/data.go
generated
vendored
Normal 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
66
vendor/github.com/emersion/go-smtp/parse.go
generated
vendored
Normal 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
187
vendor/github.com/emersion/go-smtp/server.go
generated
vendored
Executable 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
7
vendor/github.com/emersion/go-smtp/smtp.go
generated
vendored
Normal 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
|
Reference in New Issue
Block a user