add vendoring

This commit is contained in:
Aine
2022-11-16 12:08:51 +02:00
parent 14751cbf3a
commit c1d33fe3cb
1104 changed files with 759066 additions and 0 deletions

21
vendor/github.com/emersion/go-msgauth/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 emersion
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.

204
vendor/github.com/emersion/go-msgauth/dkim/canonical.go generated vendored Normal file
View File

@@ -0,0 +1,204 @@
package dkim
import (
"io"
"regexp"
"strings"
)
var rxReduceWS = regexp.MustCompile(`[ \t\r\n]+`)
// Canonicalization is a canonicalization algorithm.
type Canonicalization string
const (
CanonicalizationSimple Canonicalization = "simple"
CanonicalizationRelaxed = "relaxed"
)
type canonicalizer interface {
CanonicalizeHeader(s string) string
CanonicalizeBody(w io.Writer) io.WriteCloser
}
var canonicalizers = map[Canonicalization]canonicalizer{
CanonicalizationSimple: new(simpleCanonicalizer),
CanonicalizationRelaxed: new(relaxedCanonicalizer),
}
// crlfFixer fixes any lone LF without a preceding CR.
type crlfFixer struct {
cr bool
}
func (cf *crlfFixer) Fix(b []byte) []byte {
res := make([]byte, 0, len(b))
for _, ch := range b {
prevCR := cf.cr
cf.cr = false
switch ch {
case '\r':
cf.cr = true
case '\n':
if !prevCR {
res = append(res, '\r')
}
}
res = append(res, ch)
}
return res
}
type simpleCanonicalizer struct{}
func (c *simpleCanonicalizer) CanonicalizeHeader(s string) string {
return s
}
type simpleBodyCanonicalizer struct {
w io.Writer
crlfBuf []byte
crlfFixer crlfFixer
}
func (c *simpleBodyCanonicalizer) Write(b []byte) (int, error) {
written := len(b)
b = append(c.crlfBuf, b...)
b = c.crlfFixer.Fix(b)
end := len(b)
// If it ends with \r, maybe the next write will begin with \n
if end > 0 && b[end-1] == '\r' {
end--
}
// Keep all \r\n sequences
for end >= 2 {
prev := b[end-2]
cur := b[end-1]
if prev != '\r' || cur != '\n' {
break
}
end -= 2
}
c.crlfBuf = b[end:]
var err error
if end > 0 {
_, err = c.w.Write(b[:end])
}
return written, err
}
func (c *simpleBodyCanonicalizer) Close() error {
// Flush crlfBuf if it ends with a single \r (without a matching \n)
if len(c.crlfBuf) > 0 && c.crlfBuf[len(c.crlfBuf)-1] == '\r' {
if _, err := c.w.Write(c.crlfBuf); err != nil {
return err
}
}
c.crlfBuf = nil
if _, err := c.w.Write([]byte(crlf)); err != nil {
return err
}
return nil
}
func (c *simpleCanonicalizer) CanonicalizeBody(w io.Writer) io.WriteCloser {
return &simpleBodyCanonicalizer{w: w}
}
type relaxedCanonicalizer struct{}
func (c *relaxedCanonicalizer) CanonicalizeHeader(s string) string {
kv := strings.SplitN(s, ":", 2)
k := strings.TrimSpace(strings.ToLower(kv[0]))
var v string
if len(kv) > 1 {
v = rxReduceWS.ReplaceAllString(kv[1], " ")
v = strings.TrimSpace(v)
}
return k + ":" + v + crlf
}
type relaxedBodyCanonicalizer struct {
w io.Writer
crlfBuf []byte
wsp bool
written bool
crlfFixer crlfFixer
}
func (c *relaxedBodyCanonicalizer) Write(b []byte) (int, error) {
written := len(b)
b = c.crlfFixer.Fix(b)
canonical := make([]byte, 0, len(b))
for _, ch := range b {
if ch == ' ' || ch == '\t' {
c.wsp = true
} else if ch == '\r' || ch == '\n' {
c.wsp = false
c.crlfBuf = append(c.crlfBuf, ch)
} else {
if len(c.crlfBuf) > 0 {
canonical = append(canonical, c.crlfBuf...)
c.crlfBuf = c.crlfBuf[:0]
}
if c.wsp {
canonical = append(canonical, ' ')
c.wsp = false
}
canonical = append(canonical, ch)
}
}
if !c.written && len(canonical) > 0 {
c.written = true
}
_, err := c.w.Write(canonical)
return written, err
}
func (c *relaxedBodyCanonicalizer) Close() error {
if c.written {
if _, err := c.w.Write([]byte(crlf)); err != nil {
return err
}
}
return nil
}
func (c *relaxedCanonicalizer) CanonicalizeBody(w io.Writer) io.WriteCloser {
return &relaxedBodyCanonicalizer{w: w}
}
type limitedWriter struct {
W io.Writer
N int64
}
func (w *limitedWriter) Write(b []byte) (int, error) {
if w.N <= 0 {
return len(b), nil
}
skipped := 0
if int64(len(b)) > w.N {
b = b[:w.N]
skipped = int(int64(len(b)) - w.N)
}
n, err := w.W.Write(b)
w.N -= int64(n)
return n + skipped, err
}

10
vendor/github.com/emersion/go-msgauth/dkim/dkim.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
// Package dkim creates and verifies DKIM signatures, as specified in RFC 6376.
package dkim
import (
"time"
)
var now = time.Now
const headerFieldName = "DKIM-Signature"

169
vendor/github.com/emersion/go-msgauth/dkim/header.go generated vendored Normal file
View File

@@ -0,0 +1,169 @@
package dkim
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"net/textproto"
"sort"
"strings"
)
const crlf = "\r\n"
type header []string
func readHeader(r *bufio.Reader) (header, error) {
tr := textproto.NewReader(r)
var h header
for {
l, err := tr.ReadLine()
if err != nil {
return h, fmt.Errorf("failed to read header: %v", err)
}
if len(l) == 0 {
break
} else if len(h) > 0 && (l[0] == ' ' || l[0] == '\t') {
// This is a continuation line
h[len(h)-1] += l + crlf
} else {
h = append(h, l+crlf)
}
}
return h, nil
}
func writeHeader(w io.Writer, h header) error {
for _, kv := range h {
if _, err := w.Write([]byte(kv)); err != nil {
return err
}
}
_, err := w.Write([]byte(crlf))
return err
}
func foldHeaderField(kv string) string {
buf := bytes.NewBufferString(kv)
line := make([]byte, 75) // 78 - len("\r\n\s")
first := true
var fold strings.Builder
for len, err := buf.Read(line); err != io.EOF; len, err = buf.Read(line) {
if first {
first = false
} else {
fold.WriteString("\r\n ")
}
fold.Write(line[:len])
}
return fold.String() + crlf
}
func parseHeaderField(s string) (k string, v string) {
kv := strings.SplitN(s, ":", 2)
k = strings.TrimSpace(kv[0])
if len(kv) > 1 {
v = strings.TrimSpace(kv[1])
}
return
}
func parseHeaderParams(s string) (map[string]string, error) {
pairs := strings.Split(s, ";")
params := make(map[string]string)
for _, s := range pairs {
kv := strings.SplitN(s, "=", 2)
if len(kv) != 2 {
if strings.TrimSpace(s) == "" {
continue
}
return params, errors.New("dkim: malformed header params")
}
params[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
}
return params, nil
}
func formatHeaderParams(headerFieldName string, params map[string]string) string {
keys, bvalue, bfound := sortParams(params)
s := headerFieldName + ":"
var line string
for _, k := range keys {
v := params[k]
nextLength := 3 + len(line) + len(v) + len(k)
if nextLength > 75 {
s += line + crlf
line = ""
}
line = fmt.Sprintf("%v %v=%v;", line, k, v)
}
if line != "" {
s += line
}
if bfound {
bfiled := foldHeaderField(" b=" + bvalue)
s += crlf + bfiled
}
return s
}
func sortParams(params map[string]string) ([]string, string, bool) {
keys := make([]string, 0, len(params))
bfound := false
var bvalue string
for k := range params {
if k == "b" {
bvalue = params["b"]
bfound = true
} else {
keys = append(keys, k)
}
}
sort.Strings(keys)
return keys, bvalue, bfound
}
type headerPicker struct {
h header
picked map[string]int
}
func newHeaderPicker(h header) *headerPicker {
return &headerPicker{
h: h,
picked: make(map[string]int),
}
}
func (p *headerPicker) Pick(key string) string {
at := p.picked[key]
for i := len(p.h) - 1; i >= 0; i-- {
kv := p.h[i]
k, _ := parseHeaderField(kv)
if !strings.EqualFold(k, key) {
continue
}
if at == 0 {
p.picked[key]++
return kv
}
at--
}
return ""
}

177
vendor/github.com/emersion/go-msgauth/dkim/query.go generated vendored Normal file
View File

@@ -0,0 +1,177 @@
package dkim
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"net"
"strings"
"golang.org/x/crypto/ed25519"
)
type verifier interface {
Public() crypto.PublicKey
Verify(hash crypto.Hash, hashed []byte, sig []byte) error
}
type rsaVerifier struct {
*rsa.PublicKey
}
func (v rsaVerifier) Public() crypto.PublicKey {
return v.PublicKey
}
func (v rsaVerifier) Verify(hash crypto.Hash, hashed, sig []byte) error {
return rsa.VerifyPKCS1v15(v.PublicKey, hash, hashed, sig)
}
type ed25519Verifier struct {
ed25519.PublicKey
}
func (v ed25519Verifier) Public() crypto.PublicKey {
return v.PublicKey
}
func (v ed25519Verifier) Verify(hash crypto.Hash, hashed, sig []byte) error {
if !ed25519.Verify(v.PublicKey, hashed, sig) {
return errors.New("dkim: invalid Ed25519 signature")
}
return nil
}
type queryResult struct {
Verifier verifier
KeyAlgo string
HashAlgos []string
Notes string
Services []string
Flags []string
}
// QueryMethod is a DKIM query method.
type QueryMethod string
const (
// DNS TXT resource record (RR) lookup algorithm
QueryMethodDNSTXT QueryMethod = "dns/txt"
)
type txtLookupFunc func(domain string) ([]string, error)
type queryFunc func(domain, selector string, txtLookup txtLookupFunc) (*queryResult, error)
var queryMethods = map[QueryMethod]queryFunc{
QueryMethodDNSTXT: queryDNSTXT,
}
func queryDNSTXT(domain, selector string, txtLookup txtLookupFunc) (*queryResult, error) {
var txts []string
var err error
if txtLookup != nil {
txts, err = txtLookup(selector + "._domainkey." + domain)
} else {
txts, err = net.LookupTXT(selector + "._domainkey." + domain)
}
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
return nil, tempFailError("key unavailable: " + err.Error())
} else if err != nil {
return nil, permFailError("no key for signature: " + err.Error())
}
// Long keys are split in multiple parts
txt := strings.Join(txts, "")
return parsePublicKey(txt)
}
func parsePublicKey(s string) (*queryResult, error) {
params, err := parseHeaderParams(s)
if err != nil {
return nil, permFailError("key syntax error: " + err.Error())
}
res := new(queryResult)
if v, ok := params["v"]; ok && v != "DKIM1" {
return nil, permFailError("incompatible public key version")
}
p, ok := params["p"]
if !ok {
return nil, permFailError("key syntax error: missing public key data")
}
if p == "" {
return nil, permFailError("key revoked")
}
p = strings.ReplaceAll(p, " ", "")
b, err := base64.StdEncoding.DecodeString(p)
if err != nil {
return nil, permFailError("key syntax error: " + err.Error())
}
switch params["k"] {
case "rsa", "":
pub, err := x509.ParsePKIXPublicKey(b)
if err != nil {
// RFC 6376 is inconsistent about whether RSA public keys should
// be formatted as RSAPublicKey or SubjectPublicKeyInfo.
// Erratum 3017 (https://www.rfc-editor.org/errata/eid3017) proposes
// allowing both.
pub, err = x509.ParsePKCS1PublicKey(b)
if err != nil {
return nil, permFailError("key syntax error: " + err.Error())
}
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, permFailError("key syntax error: not an RSA public key")
}
// RFC 8301 section 3.2: verifiers MUST NOT consider signatures using
// RSA keys of less than 1024 bits as valid signatures.
if rsaPub.Size()*8 < 1024 {
return nil, permFailError(fmt.Sprintf("key is too short: want 1024 bits, has %v bits", rsaPub.Size()*8))
}
res.Verifier = rsaVerifier{rsaPub}
res.KeyAlgo = "rsa"
case "ed25519":
if len(b) != ed25519.PublicKeySize {
return nil, permFailError(fmt.Sprintf("invalid Ed25519 public key size: %v bytes", len(b)))
}
ed25519Pub := ed25519.PublicKey(b)
res.Verifier = ed25519Verifier{ed25519Pub}
res.KeyAlgo = "ed25519"
default:
return nil, permFailError("unsupported key algorithm")
}
if hashesStr, ok := params["h"]; ok {
res.HashAlgos = parseTagList(hashesStr)
}
if notes, ok := params["n"]; ok {
res.Notes = notes
}
if servicesStr, ok := params["s"]; ok {
services := parseTagList(servicesStr)
hasWildcard := false
for _, s := range services {
if s == "*" {
hasWildcard = true
break
}
}
if !hasWildcard {
res.Services = services
}
}
if flagsStr, ok := params["t"]; ok {
res.Flags = parseTagList(flagsStr)
}
return res, nil
}

346
vendor/github.com/emersion/go-msgauth/dkim/sign.go generated vendored Normal file
View File

@@ -0,0 +1,346 @@
package dkim
import (
"bufio"
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"fmt"
"io"
"strconv"
"strings"
"time"
"golang.org/x/crypto/ed25519"
)
var randReader io.Reader = rand.Reader
// SignOptions is used to configure Sign. Domain, Selector and Signer are
// mandatory.
type SignOptions struct {
// The SDID claiming responsibility for an introduction of a message into the
// mail stream. Hence, the SDID value is used to form the query for the public
// key. The SDID MUST correspond to a valid DNS name under which the DKIM key
// record is published.
//
// This can't be empty.
Domain string
// The selector subdividing the namespace for the domain.
//
// This can't be empty.
Selector string
// The Agent or User Identifier (AUID) on behalf of which the SDID is taking
// responsibility.
//
// This is optional.
Identifier string
// The key used to sign the message.
//
// Supported Signer.Public() values are *rsa.PublicKey and
// ed25519.PublicKey.
Signer crypto.Signer
// The hash algorithm used to sign the message. If zero, a default hash will
// be chosen.
//
// The only supported hash algorithm is crypto.SHA256.
Hash crypto.Hash
// Header and body canonicalization algorithms.
//
// If empty, CanonicalizationSimple is used.
HeaderCanonicalization Canonicalization
BodyCanonicalization Canonicalization
// A list of header fields to include in the signature. If nil, all headers
// will be included. If not nil, "From" MUST be in the list.
//
// See RFC 6376 section 5.4.1 for recommended header fields.
HeaderKeys []string
// The expiration time. A zero value means no expiration.
Expiration time.Time
// A list of query methods used to retrieve the public key.
//
// If nil, it is implicitly defined as QueryMethodDNSTXT.
QueryMethods []QueryMethod
}
// Signer generates a DKIM signature.
//
// The whole message header and body must be written to the Signer. Close should
// always be called (either after the whole message has been written, or after
// an error occured and the signer won't be used anymore). Close may return an
// error in case signing fails.
//
// After a successful Close, Signature can be called to retrieve the
// DKIM-Signature header field that the caller should prepend to the message.
type Signer struct {
pw *io.PipeWriter
done <-chan error
sigParams map[string]string // only valid after done received nil
}
// NewSigner creates a new signer. It returns an error if SignOptions is
// invalid.
func NewSigner(options *SignOptions) (*Signer, error) {
if options == nil {
return nil, fmt.Errorf("dkim: no options specified")
}
if options.Domain == "" {
return nil, fmt.Errorf("dkim: no domain specified")
}
if options.Selector == "" {
return nil, fmt.Errorf("dkim: no selector specified")
}
if options.Signer == nil {
return nil, fmt.Errorf("dkim: no signer specified")
}
headerCan := options.HeaderCanonicalization
if headerCan == "" {
headerCan = CanonicalizationSimple
}
if _, ok := canonicalizers[headerCan]; !ok {
return nil, fmt.Errorf("dkim: unknown header canonicalization %q", headerCan)
}
bodyCan := options.BodyCanonicalization
if bodyCan == "" {
bodyCan = CanonicalizationSimple
}
if _, ok := canonicalizers[bodyCan]; !ok {
return nil, fmt.Errorf("dkim: unknown body canonicalization %q", bodyCan)
}
var keyAlgo string
switch options.Signer.Public().(type) {
case *rsa.PublicKey:
keyAlgo = "rsa"
case ed25519.PublicKey:
keyAlgo = "ed25519"
default:
return nil, fmt.Errorf("dkim: unsupported key algorithm %T", options.Signer.Public())
}
hash := options.Hash
var hashAlgo string
switch options.Hash {
case 0: // sha256 is the default
hash = crypto.SHA256
fallthrough
case crypto.SHA256:
hashAlgo = "sha256"
case crypto.SHA1:
return nil, fmt.Errorf("dkim: hash algorithm too weak: sha1")
default:
return nil, fmt.Errorf("dkim: unsupported hash algorithm")
}
if options.HeaderKeys != nil {
ok := false
for _, k := range options.HeaderKeys {
if strings.EqualFold(k, "From") {
ok = true
break
}
}
if !ok {
return nil, fmt.Errorf("dkim: the From header field must be signed")
}
}
done := make(chan error, 1)
pr, pw := io.Pipe()
s := &Signer{
pw: pw,
done: done,
}
closeReadWithError := func(err error) {
pr.CloseWithError(err)
done <- err
}
go func() {
defer close(done)
// Read header
br := bufio.NewReader(pr)
h, err := readHeader(br)
if err != nil {
closeReadWithError(err)
return
}
// Hash body
hasher := hash.New()
can := canonicalizers[bodyCan].CanonicalizeBody(hasher)
if _, err := io.Copy(can, br); err != nil {
closeReadWithError(err)
return
}
if err := can.Close(); err != nil {
closeReadWithError(err)
return
}
bodyHashed := hasher.Sum(nil)
params := map[string]string{
"v": "1",
"a": keyAlgo + "-" + hashAlgo,
"bh": base64.StdEncoding.EncodeToString(bodyHashed),
"c": string(headerCan) + "/" + string(bodyCan),
"d": options.Domain,
//"l": "", // TODO
"s": options.Selector,
"t": formatTime(now()),
//"z": "", // TODO
}
var headerKeys []string
if options.HeaderKeys != nil {
headerKeys = options.HeaderKeys
} else {
for _, kv := range h {
k, _ := parseHeaderField(kv)
headerKeys = append(headerKeys, k)
}
}
params["h"] = formatTagList(headerKeys)
if options.Identifier != "" {
params["i"] = options.Identifier
}
if options.QueryMethods != nil {
methods := make([]string, len(options.QueryMethods))
for i, method := range options.QueryMethods {
methods[i] = string(method)
}
params["q"] = formatTagList(methods)
}
if !options.Expiration.IsZero() {
params["x"] = formatTime(options.Expiration)
}
// Hash and sign headers
hasher.Reset()
picker := newHeaderPicker(h)
for _, k := range headerKeys {
kv := picker.Pick(k)
if kv == "" {
// The Signer MAY include more instances of a header field name
// in "h=" than there are actual corresponding header fields so
// that the signature will not verify if additional header
// fields of that name are added.
continue
}
kv = canonicalizers[headerCan].CanonicalizeHeader(kv)
if _, err := io.WriteString(hasher, kv); err != nil {
closeReadWithError(err)
return
}
}
params["b"] = ""
sigField := formatSignature(params)
sigField = canonicalizers[headerCan].CanonicalizeHeader(sigField)
sigField = strings.TrimRight(sigField, crlf)
if _, err := io.WriteString(hasher, sigField); err != nil {
closeReadWithError(err)
return
}
hashed := hasher.Sum(nil)
// Don't pass Hash to Sign for ed25519 as it doesn't support it
// and will return an error ("ed25519: cannot sign hashed message").
if keyAlgo == "ed25519" {
hash = crypto.Hash(0)
}
sig, err := options.Signer.Sign(randReader, hashed, hash)
if err != nil {
closeReadWithError(err)
return
}
params["b"] = base64.StdEncoding.EncodeToString(sig)
s.sigParams = params
closeReadWithError(nil)
}()
return s, nil
}
// Write implements io.WriteCloser.
func (s *Signer) Write(b []byte) (n int, err error) {
return s.pw.Write(b)
}
// Close implements io.WriteCloser. The error return by Close must be checked.
func (s *Signer) Close() error {
if err := s.pw.Close(); err != nil {
return err
}
return <-s.done
}
// Signature returns the whole DKIM-Signature header field. It can only be
// called after a successful Signer.Close call.
//
// The returned value contains both the header field name, its value and the
// final CRLF.
func (s *Signer) Signature() string {
if s.sigParams == nil {
panic("dkim: Signer.Signature must only be called after a succesful Signer.Close")
}
return formatSignature(s.sigParams)
}
// Sign signs a message. It reads it from r and writes the signed version to w.
func Sign(w io.Writer, r io.Reader, options *SignOptions) error {
s, err := NewSigner(options)
if err != nil {
return err
}
defer s.Close()
// We need to keep the message in a buffer so we can write the new DKIM
// header field before the rest of the message
var b bytes.Buffer
mw := io.MultiWriter(&b, s)
if _, err := io.Copy(mw, r); err != nil {
return err
}
if err := s.Close(); err != nil {
return err
}
if _, err := io.WriteString(w, s.Signature()); err != nil {
return err
}
_, err = io.Copy(w, &b)
return err
}
func formatSignature(params map[string]string) string {
sig := formatHeaderParams(headerFieldName, params)
return sig
}
func formatTagList(l []string) string {
return strings.Join(l, ":")
}
func formatTime(t time.Time) string {
return strconv.FormatInt(t.Unix(), 10)
}

462
vendor/github.com/emersion/go-msgauth/dkim/verify.go generated vendored Normal file
View File

@@ -0,0 +1,462 @@
package dkim
import (
"bufio"
"crypto"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"regexp"
"strconv"
"strings"
"time"
"unicode"
)
type permFailError string
func (err permFailError) Error() string {
return "dkim: " + string(err)
}
// IsPermFail returns true if the error returned by Verify is a permanent
// failure. A permanent failure is for instance a missing required field or a
// malformed header.
func IsPermFail(err error) bool {
_, ok := err.(permFailError)
return ok
}
type tempFailError string
func (err tempFailError) Error() string {
return "dkim: " + string(err)
}
// IsTempFail returns true if the error returned by Verify is a temporary
// failure.
func IsTempFail(err error) bool {
_, ok := err.(tempFailError)
return ok
}
type failError string
func (err failError) Error() string {
return "dkim: " + string(err)
}
// isFail returns true if the error returned by Verify is a signature error.
func isFail(err error) bool {
_, ok := err.(failError)
return ok
}
// ErrTooManySignatures is returned by Verify when the message exceeds the
// maximum number of signatures.
var ErrTooManySignatures = errors.New("dkim: too many signatures")
var requiredTags = []string{"v", "a", "b", "bh", "d", "h", "s"}
// A Verification is produced by Verify when it checks if one signature is
// valid. If the signature is valid, Err is nil.
type Verification struct {
// The SDID claiming responsibility for an introduction of a message into the
// mail stream.
Domain string
// The Agent or User Identifier (AUID) on behalf of which the SDID is taking
// responsibility.
Identifier string
// The list of signed header fields.
HeaderKeys []string
// The time that this signature was created. If unknown, it's set to zero.
Time time.Time
// The expiration time. If the signature doesn't expire, it's set to zero.
Expiration time.Time
// Err is nil if the signature is valid.
Err error
}
type signature struct {
i int
v string
}
// VerifyOptions allows to customize the default signature verification
// behavior.
type VerifyOptions struct {
// LookupTXT returns the DNS TXT records for the given domain name. If nil,
// net.LookupTXT is used.
LookupTXT func(domain string) ([]string, error)
// MaxVerifications controls the maximum number of signature verifications
// to perform. If more signatures are present, the first MaxVerifications
// signatures are verified, the rest are ignored and ErrTooManySignatures
// is returned. If zero, there is no maximum.
MaxVerifications int
}
// Verify checks if a message's signatures are valid. It returns one
// verification per signature.
//
// There is no guarantee that the reader will be completely consumed.
func Verify(r io.Reader) ([]*Verification, error) {
return VerifyWithOptions(r, nil)
}
// VerifyWithOptions performs the same task as Verify, but allows specifying
// verification options.
func VerifyWithOptions(r io.Reader, options *VerifyOptions) ([]*Verification, error) {
// Read header
bufr := bufio.NewReader(r)
h, err := readHeader(bufr)
if err != nil {
return nil, err
}
// Scan header fields for signatures
var signatures []*signature
for i, kv := range h {
k, v := parseHeaderField(kv)
if strings.EqualFold(k, headerFieldName) {
signatures = append(signatures, &signature{i, v})
}
}
tooManySignatures := false
if options != nil && options.MaxVerifications > 0 && len(signatures) > options.MaxVerifications {
tooManySignatures = true
signatures = signatures[:options.MaxVerifications]
}
var verifs []*Verification
if len(signatures) == 1 {
// If there is only one signature - just verify it.
v, err := verify(h, bufr, h[signatures[0].i], signatures[0].v, options)
if err != nil && !IsTempFail(err) && !IsPermFail(err) && !isFail(err) {
return nil, err
}
v.Err = err
verifs = []*Verification{v}
} else {
verifs, err = parallelVerify(bufr, h, signatures, options)
if err != nil {
return nil, err
}
}
if tooManySignatures {
return verifs, ErrTooManySignatures
}
return verifs, nil
}
func parallelVerify(r io.Reader, h header, signatures []*signature, options *VerifyOptions) ([]*Verification, error) {
pipeWriters := make([]*io.PipeWriter, len(signatures))
// We can't pass pipeWriter to io.MultiWriter directly,
// we need a slice of io.Writer, but we also need *io.PipeWriter
// to call Close on it.
writers := make([]io.Writer, len(signatures))
chans := make([]chan *Verification, len(signatures))
for i, sig := range signatures {
// Be careful with loop variables and goroutines.
i, sig := i, sig
chans[i] = make(chan *Verification, 1)
pr, pw := io.Pipe()
writers[i] = pw
pipeWriters[i] = pw
go func() {
v, err := verify(h, pr, h[sig.i], sig.v, options)
// Make sure we consume the whole reader, otherwise io.Copy on
// other side can block forever.
io.Copy(ioutil.Discard, pr)
v.Err = err
chans[i] <- v
}()
}
if _, err := io.Copy(io.MultiWriter(writers...), r); err != nil {
return nil, err
}
for _, wr := range pipeWriters {
wr.Close()
}
verifications := make([]*Verification, len(signatures))
for i, ch := range chans {
verifications[i] = <-ch
}
// Return unexpected failures as a separate error.
for _, v := range verifications {
err := v.Err
if err != nil && !IsTempFail(err) && !IsPermFail(err) && !isFail(err) {
v.Err = nil
return verifications, err
}
}
return verifications, nil
}
func verify(h header, r io.Reader, sigField, sigValue string, options *VerifyOptions) (*Verification, error) {
verif := new(Verification)
params, err := parseHeaderParams(sigValue)
if err != nil {
return verif, permFailError("malformed signature tags: " + err.Error())
}
if params["v"] != "1" {
return verif, permFailError("incompatible signature version")
}
verif.Domain = stripWhitespace(params["d"])
for _, tag := range requiredTags {
if _, ok := params[tag]; !ok {
return verif, permFailError("signature missing required tag")
}
}
if i, ok := params["i"]; ok {
verif.Identifier = stripWhitespace(i)
if !strings.HasSuffix(verif.Identifier, "@"+verif.Domain) && !strings.HasSuffix(verif.Identifier, "."+verif.Domain) {
return verif, permFailError("domain mismatch")
}
} else {
verif.Identifier = "@" + verif.Domain
}
headerKeys := parseTagList(params["h"])
ok := false
for _, k := range headerKeys {
if strings.EqualFold(k, "from") {
ok = true
break
}
}
if !ok {
return verif, permFailError("From field not signed")
}
verif.HeaderKeys = headerKeys
if timeStr, ok := params["t"]; ok {
t, err := parseTime(timeStr)
if err != nil {
return verif, permFailError("malformed time: " + err.Error())
}
verif.Time = t
}
if expiresStr, ok := params["x"]; ok {
t, err := parseTime(expiresStr)
if err != nil {
return verif, permFailError("malformed expiration time: " + err.Error())
}
verif.Expiration = t
if now().After(t) {
return verif, permFailError("signature has expired")
}
}
// Query public key
// TODO: compute hash in parallel
methods := []string{string(QueryMethodDNSTXT)}
if methodsStr, ok := params["q"]; ok {
methods = parseTagList(methodsStr)
}
var res *queryResult
for _, method := range methods {
if query, ok := queryMethods[QueryMethod(method)]; ok {
if options != nil {
res, err = query(verif.Domain, stripWhitespace(params["s"]), options.LookupTXT)
} else {
res, err = query(verif.Domain, stripWhitespace(params["s"]), nil)
}
break
}
}
if err != nil {
return verif, err
} else if res == nil {
return verif, permFailError("unsupported public key query method")
}
// Parse algos
algos := strings.SplitN(stripWhitespace(params["a"]), "-", 2)
if len(algos) != 2 {
return verif, permFailError("malformed algorithm name")
}
keyAlgo := algos[0]
hashAlgo := algos[1]
// Check hash algo
if res.HashAlgos != nil {
ok := false
for _, algo := range res.HashAlgos {
if algo == hashAlgo {
ok = true
break
}
}
if !ok {
return verif, permFailError("inappropriate hash algorithm")
}
}
var hash crypto.Hash
switch hashAlgo {
case "sha1":
// RFC 8301 section 3.1: rsa-sha1 MUST NOT be used for signing or
// verifying.
return verif, permFailError(fmt.Sprintf("hash algorithm too weak: %v", hashAlgo))
case "sha256":
hash = crypto.SHA256
default:
return verif, permFailError("unsupported hash algorithm")
}
// Check key algo
if res.KeyAlgo != keyAlgo {
return verif, permFailError("inappropriate key algorithm")
}
if res.Services != nil {
ok := false
for _, s := range res.Services {
if s == "email" {
ok = true
break
}
}
if !ok {
return verif, permFailError("inappropriate service")
}
}
headerCan, bodyCan := parseCanonicalization(params["c"])
if _, ok := canonicalizers[headerCan]; !ok {
return verif, permFailError("unsupported header canonicalization algorithm")
}
if _, ok := canonicalizers[bodyCan]; !ok {
return verif, permFailError("unsupported body canonicalization algorithm")
}
// The body length "l" parameter is insecure, because it allows parts of
// the message body to not be signed. Reject messages which have it set.
if _, ok := params["l"]; ok {
// TODO: technically should be policyError
return verif, failError("message contains an insecure body length tag")
}
// Parse body hash and signature
bodyHashed, err := decodeBase64String(params["bh"])
if err != nil {
return verif, permFailError("malformed body hash: " + err.Error())
}
sig, err := decodeBase64String(params["b"])
if err != nil {
return verif, permFailError("malformed signature: " + err.Error())
}
// Check body hash
hasher := hash.New()
wc := canonicalizers[bodyCan].CanonicalizeBody(hasher)
if _, err := io.Copy(wc, r); err != nil {
return verif, err
}
if err := wc.Close(); err != nil {
return verif, err
}
if subtle.ConstantTimeCompare(hasher.Sum(nil), bodyHashed) != 1 {
return verif, failError("body hash did not verify")
}
// Compute data hash
hasher.Reset()
picker := newHeaderPicker(h)
for _, key := range headerKeys {
kv := picker.Pick(key)
if kv == "" {
// The field MAY contain names of header fields that do not exist
// when signed; nonexistent header fields do not contribute to the
// signature computation
continue
}
kv = canonicalizers[headerCan].CanonicalizeHeader(kv)
if _, err := hasher.Write([]byte(kv)); err != nil {
return verif, err
}
}
canSigField := removeSignature(sigField)
canSigField = canonicalizers[headerCan].CanonicalizeHeader(canSigField)
canSigField = strings.TrimRight(canSigField, "\r\n")
if _, err := hasher.Write([]byte(canSigField)); err != nil {
return verif, err
}
hashed := hasher.Sum(nil)
// Check signature
if err := res.Verifier.Verify(hash, hashed, sig); err != nil {
return verif, failError("signature did not verify: " + err.Error())
}
return verif, nil
}
func parseTagList(s string) []string {
tags := strings.Split(s, ":")
for i, t := range tags {
tags[i] = stripWhitespace(t)
}
return tags
}
func parseCanonicalization(s string) (headerCan, bodyCan Canonicalization) {
headerCan = CanonicalizationSimple
bodyCan = CanonicalizationSimple
cans := strings.SplitN(stripWhitespace(s), "/", 2)
if cans[0] != "" {
headerCan = Canonicalization(cans[0])
}
if len(cans) > 1 {
bodyCan = Canonicalization(cans[1])
}
return
}
func parseTime(s string) (time.Time, error) {
sec, err := strconv.ParseInt(stripWhitespace(s), 10, 64)
if err != nil {
return time.Time{}, err
}
return time.Unix(sec, 0), nil
}
func decodeBase64String(s string) ([]byte, error) {
return base64.StdEncoding.DecodeString(stripWhitespace(s))
}
func stripWhitespace(s string) string {
return strings.Map(func(r rune) rune {
if unicode.IsSpace(r) {
return -1
}
return r
}, s)
}
func removeSignature(s string) string {
return regexp.MustCompile(`(b\s*=)[^;]+`).ReplaceAllString(s, "$1")
}

19
vendor/github.com/emersion/go-sasl/.build.yml generated vendored Normal file
View File

@@ -0,0 +1,19 @@
image: alpine/latest
packages:
- go
# Required by codecov
- bash
- findutils
sources:
- https://github.com/emersion/go-sasl
tasks:
- build: |
cd go-sasl
go build -v ./...
- test: |
cd go-sasl
go test -coverprofile=coverage.txt -covermode=atomic ./...
- upload-coverage: |
cd go-sasl
export CODECOV_TOKEN=3f257f71-a128-4834-8f68-2b534e9f4cb1
curl -s https://codecov.io/bash | bash

24
vendor/github.com/emersion/go-sasl/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,24 @@
# 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

21
vendor/github.com/emersion/go-sasl/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 emersion
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.

17
vendor/github.com/emersion/go-sasl/README.md generated vendored Normal file
View File

@@ -0,0 +1,17 @@
# go-sasl
[![GoDoc](https://godoc.org/github.com/emersion/go-sasl?status.svg)](https://godoc.org/github.com/emersion/go-sasl)
[![Build Status](https://travis-ci.org/emersion/go-sasl.svg?branch=master)](https://travis-ci.org/emersion/go-sasl)
A [SASL](https://tools.ietf.org/html/rfc4422) library written in Go.
Implemented mechanisms:
* [ANONYMOUS](https://tools.ietf.org/html/rfc4505)
* [EXTERNAL](https://tools.ietf.org/html/rfc4422#appendix-A)
* [LOGIN](https://tools.ietf.org/html/draft-murchison-sasl-login-00) (obsolete, use PLAIN instead)
* [PLAIN](https://tools.ietf.org/html/rfc4616)
* [OAUTHBEARER](https://tools.ietf.org/html/rfc7628)
## License
MIT

56
vendor/github.com/emersion/go-sasl/anonymous.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
package sasl
// The ANONYMOUS mechanism name.
const Anonymous = "ANONYMOUS"
type anonymousClient struct {
Trace string
}
func (c *anonymousClient) Start() (mech string, ir []byte, err error) {
mech = Anonymous
ir = []byte(c.Trace)
return
}
func (c *anonymousClient) Next(challenge []byte) (response []byte, err error) {
return nil, ErrUnexpectedServerChallenge
}
// A client implementation of the ANONYMOUS authentication mechanism, as
// described in RFC 4505.
func NewAnonymousClient(trace string) Client {
return &anonymousClient{trace}
}
// Get trace information from clients logging in anonymously.
type AnonymousAuthenticator func(trace string) error
type anonymousServer struct {
done bool
authenticate AnonymousAuthenticator
}
func (s *anonymousServer) Next(response []byte) (challenge []byte, done bool, err error) {
if s.done {
err = ErrUnexpectedClientResponse
return
}
// No initial response, send an empty challenge
if response == nil {
return []byte{}, false, nil
}
s.done = true
err = s.authenticate(string(response))
done = true
return
}
// A server implementation of the ANONYMOUS authentication mechanism, as
// described in RFC 4505.
func NewAnonymousServer(authenticator AnonymousAuthenticator) Server {
return &anonymousServer{authenticate: authenticator}
}

26
vendor/github.com/emersion/go-sasl/external.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
package sasl
// The EXTERNAL mechanism name.
const External = "EXTERNAL"
type externalClient struct {
Identity string
}
func (a *externalClient) Start() (mech string, ir []byte, err error) {
mech = External
ir = []byte(a.Identity)
return
}
func (a *externalClient) Next(challenge []byte) (response []byte, err error) {
return nil, ErrUnexpectedServerChallenge
}
// An implementation of the EXTERNAL authentication mechanism, as described in
// RFC 4422. Authorization identity may be left blank to indicate that the
// client is requesting to act as the identity associated with the
// authentication credentials.
func NewExternalClient(identity string) Client {
return &externalClient{identity}
}

89
vendor/github.com/emersion/go-sasl/login.go generated vendored Normal file
View File

@@ -0,0 +1,89 @@
package sasl
import (
"bytes"
)
// The LOGIN mechanism name.
const Login = "LOGIN"
var expectedChallenge = []byte("Password:")
type loginClient struct {
Username string
Password string
}
func (a *loginClient) Start() (mech string, ir []byte, err error) {
mech = "LOGIN"
ir = []byte(a.Username)
return
}
func (a *loginClient) Next(challenge []byte) (response []byte, err error) {
if bytes.Compare(challenge, expectedChallenge) != 0 {
return nil, ErrUnexpectedServerChallenge
} else {
return []byte(a.Password), nil
}
}
// A client implementation of the LOGIN authentication mechanism for SMTP,
// as described in http://www.iana.org/go/draft-murchison-sasl-login
//
// It is considered obsolete, and should not be used when other mechanisms are
// available. For plaintext password authentication use PLAIN mechanism.
func NewLoginClient(username, password string) Client {
return &loginClient{username, password}
}
// Authenticates users with an username and a password.
type LoginAuthenticator func(username, password string) error
type loginState int
const (
loginNotStarted loginState = iota
loginWaitingUsername
loginWaitingPassword
)
type loginServer struct {
state loginState
username, password string
authenticate LoginAuthenticator
}
// A server implementation of the LOGIN authentication mechanism, as described
// in https://tools.ietf.org/html/draft-murchison-sasl-login-00.
//
// LOGIN is obsolete and should only be enabled for legacy clients that cannot
// be updated to use PLAIN.
func NewLoginServer(authenticator LoginAuthenticator) Server {
return &loginServer{authenticate: authenticator}
}
func (a *loginServer) Next(response []byte) (challenge []byte, done bool, err error) {
switch a.state {
case loginNotStarted:
// Check for initial response field, as per RFC4422 section 3
if response == nil {
challenge = []byte("Username:")
break
}
a.state++
fallthrough
case loginWaitingUsername:
a.username = string(response)
challenge = []byte("Password:")
case loginWaitingPassword:
a.password = string(response)
err = a.authenticate(a.username, a.password)
done = true
default:
err = ErrUnexpectedClientResponse
}
a.state++
return
}

191
vendor/github.com/emersion/go-sasl/oauthbearer.go generated vendored Normal file
View File

@@ -0,0 +1,191 @@
package sasl
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
)
// The OAUTHBEARER mechanism name.
const OAuthBearer = "OAUTHBEARER"
type OAuthBearerError struct {
Status string `json:"status"`
Schemes string `json:"schemes"`
Scope string `json:"scope"`
}
type OAuthBearerOptions struct {
Username string
Token string
Host string
Port int
}
// Implements error
func (err *OAuthBearerError) Error() string {
return fmt.Sprintf("OAUTHBEARER authentication error (%v)", err.Status)
}
type oauthBearerClient struct {
OAuthBearerOptions
}
func (a *oauthBearerClient) Start() (mech string, ir []byte, err error) {
mech = OAuthBearer
var str = "n,a=" + a.Username + ","
if a.Host != "" {
str += "\x01host=" + a.Host
}
if a.Port != 0 {
str += "\x01port=" + strconv.Itoa(a.Port)
}
str += "\x01auth=Bearer " + a.Token + "\x01\x01"
ir = []byte(str)
return
}
func (a *oauthBearerClient) Next(challenge []byte) ([]byte, error) {
authBearerErr := &OAuthBearerError{}
if err := json.Unmarshal(challenge, authBearerErr); err != nil {
return nil, err
} else {
return nil, authBearerErr
}
}
// An implementation of the OAUTHBEARER authentication mechanism, as
// described in RFC 7628.
func NewOAuthBearerClient(opt *OAuthBearerOptions) Client {
return &oauthBearerClient{*opt}
}
type OAuthBearerAuthenticator func(opts OAuthBearerOptions) *OAuthBearerError
type oauthBearerServer struct {
done bool
failErr error
authenticate OAuthBearerAuthenticator
}
func (a *oauthBearerServer) fail(descr string) ([]byte, bool, error) {
blob, err := json.Marshal(OAuthBearerError{
Status: "invalid_request",
Schemes: "bearer",
})
if err != nil {
panic(err) // wtf
}
a.failErr = errors.New(descr)
return blob, false, nil
}
func (a *oauthBearerServer) Next(response []byte) (challenge []byte, done bool, err error) {
// Per RFC, we cannot just send an error, we need to return JSON-structured
// value as a challenge and then after getting dummy response from the
// client stop the exchange.
if a.failErr != nil {
// Server libraries (go-smtp, go-imap) will not call Next on
// protocol-specific SASL cancel response ('*'). However, GS2 (and
// indirectly OAUTHBEARER) defines a protocol-independent way to do so
// using 0x01.
if len(response) != 1 && response[0] != 0x01 {
return nil, true, errors.New("unexpected response")
}
return nil, true, a.failErr
}
if a.done {
err = ErrUnexpectedClientResponse
return
}
// Generate empty challenge.
if response == nil {
return []byte{}, false, nil
}
a.done = true
// Cut n,a=username,\x01host=...\x01auth=...
// into
// n
// a=username
// \x01host=...\x01auth=...\x01\x01
parts := bytes.SplitN(response, []byte{','}, 3)
if len(parts) != 3 {
return a.fail("Invalid response")
}
if !bytes.Equal(parts[0], []byte{'n'}) {
return a.fail("Invalid response, missing 'n'")
}
opts := OAuthBearerOptions{}
if !bytes.HasPrefix(parts[1], []byte("a=")) {
return a.fail("Invalid response, missing 'a'")
}
opts.Username = string(bytes.TrimPrefix(parts[1], []byte("a=")))
// Cut \x01host=...\x01auth=...\x01\x01
// into
// *empty*
// host=...
// auth=...
// *empty*
//
// Note that this code does not do a lot of checks to make sure the input
// follows the exact format specified by RFC.
params := bytes.Split(parts[2], []byte{0x01})
for _, p := range params {
// Skip empty fields (one at start and end).
if len(p) == 0 {
continue
}
pParts := bytes.SplitN(p, []byte{'='}, 2)
if len(pParts) != 2 {
return a.fail("Invalid response, missing '='")
}
switch string(pParts[0]) {
case "host":
opts.Host = string(pParts[1])
case "port":
port, err := strconv.ParseUint(string(pParts[1]), 10, 16)
if err != nil {
return a.fail("Invalid response, malformed 'port' value")
}
opts.Port = int(port)
case "auth":
const prefix = "bearer "
strValue := string(pParts[1])
// Token type is case-insensitive.
if !strings.HasPrefix(strings.ToLower(strValue), prefix) {
return a.fail("Unsupported token type")
}
opts.Token = strValue[len(prefix):]
default:
return a.fail("Invalid response, unknown parameter: " + string(pParts[0]))
}
}
authzErr := a.authenticate(opts)
if authzErr != nil {
blob, err := json.Marshal(authzErr)
if err != nil {
panic(err) // wtf
}
a.failErr = authzErr
return blob, false, nil
}
return nil, true, nil
}
func NewOAuthBearerServer(auth OAuthBearerAuthenticator) Server {
return &oauthBearerServer{authenticate: auth}
}

77
vendor/github.com/emersion/go-sasl/plain.go generated vendored Normal file
View File

@@ -0,0 +1,77 @@
package sasl
import (
"bytes"
"errors"
)
// The PLAIN mechanism name.
const Plain = "PLAIN"
type plainClient struct {
Identity string
Username string
Password string
}
func (a *plainClient) Start() (mech string, ir []byte, err error) {
mech = "PLAIN"
ir = []byte(a.Identity + "\x00" + a.Username + "\x00" + a.Password)
return
}
func (a *plainClient) Next(challenge []byte) (response []byte, err error) {
return nil, ErrUnexpectedServerChallenge
}
// A client implementation of the PLAIN authentication mechanism, as described
// in RFC 4616. Authorization identity may be left blank to indicate that it is
// the same as the username.
func NewPlainClient(identity, username, password string) Client {
return &plainClient{identity, username, password}
}
// Authenticates users with an identity, a username and a password. If the
// identity is left blank, it indicates that it is the same as the username.
// If identity is not empty and the server doesn't support it, an error must be
// returned.
type PlainAuthenticator func(identity, username, password string) error
type plainServer struct {
done bool
authenticate PlainAuthenticator
}
func (a *plainServer) Next(response []byte) (challenge []byte, done bool, err error) {
if a.done {
err = ErrUnexpectedClientResponse
return
}
// No initial response, send an empty challenge
if response == nil {
return []byte{}, false, nil
}
a.done = true
parts := bytes.Split(response, []byte("\x00"))
if len(parts) != 3 {
err = errors.New("Invalid response")
return
}
identity := string(parts[0])
username := string(parts[1])
password := string(parts[2])
err = a.authenticate(identity, username, password)
done = true
return
}
// A server implementation of the PLAIN authentication mechanism, as described
// in RFC 4616.
func NewPlainServer(authenticator PlainAuthenticator) Server {
return &plainServer{authenticate: authenticator}
}

45
vendor/github.com/emersion/go-sasl/sasl.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
// Library for Simple Authentication and Security Layer (SASL) defined in RFC 4422.
package sasl
// Note:
// Most of this code was copied, with some modifications, from net/smtp. It
// would be better if Go provided a standard package (e.g. crypto/sasl) that
// could be shared by SMTP, IMAP, and other packages.
import (
"errors"
)
// Common SASL errors.
var (
ErrUnexpectedClientResponse = errors.New("sasl: unexpected client response")
ErrUnexpectedServerChallenge = errors.New("sasl: unexpected server challenge")
)
// Client interface to perform challenge-response authentication.
type Client interface {
// Begins SASL authentication with the server. It returns the
// authentication mechanism name and "initial response" data (if required by
// the selected mechanism). A non-nil error causes the client to abort the
// authentication attempt.
//
// A nil ir value is different from a zero-length value. The nil value
// indicates that the selected mechanism does not use an initial response,
// while a zero-length value indicates an empty initial response, which must
// be sent to the server.
Start() (mech string, ir []byte, err error)
// Continues challenge-response authentication. A non-nil error causes
// the client to abort the authentication attempt.
Next(challenge []byte) (response []byte, err error)
}
// Server interface to perform challenge-response authentication.
type Server interface {
// Begins or continues challenge-response authentication. If the client
// supplies an initial response, response is non-nil.
//
// If the authentication is finished, done is set to true. If the
// authentication has failed, an error is returned.
Next(response []byte) (challenge []byte, done bool, err error)
}

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

@@ -0,0 +1,19 @@
image: alpine/edge
packages:
- go
# Required by codecov
- bash
- findutils
sources:
- https://github.com/emersion/go-smtp
tasks:
- build: |
cd go-smtp
go build -v ./...
- test: |
cd go-smtp
go test -coverprofile=coverage.txt -covermode=atomic ./...
- upload-coverage: |
cd go-smtp
export CODECOV_TOKEN=3f257f71-a128-4834-8f68-2b534e9f4cb1
curl -s https://codecov.io/bash | bash

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

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.

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

@@ -0,0 +1,151 @@
# go-smtp
[![godocs.io](https://godocs.io/github.com/emersion/go-smtp?status.svg)](https://godocs.io/github.com/emersion/go-smtp)
[![builds.sr.ht status](https://builds.sr.ht/~emersion/go-smtp/commits.svg)](https://builds.sr.ht/~emersion/go-smtp/commits?)
[![codecov](https://codecov.io/gh/emersion/go-smtp/branch/master/graph/badge.svg)](https://codecov.io/gh/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
* [LMTP](https://tools.ietf.org/html/rfc2033) support
## 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"
"io/ioutil"
"log"
"time"
"github.com/emersion/go-smtp"
)
// The Backend implements SMTP server methods.
type Backend struct{}
// Login handles a login command with username and password.
func (bkd *Backend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
if username != "username" || password != "password" {
return nil, errors.New("Invalid username or password")
}
return &Session{}, nil
}
// AnonymousLogin requires clients to authenticate using SMTP AUTH before sending emails
func (bkd *Backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
return nil, smtp.ErrAuthRequired
}
// A Session is returned after successful login.
type Session struct{}
func (s *Session) Mail(from string, opts smtp.MailOptions) error {
log.Println("Mail from:", from)
return nil
}
func (s *Session) Rcpt(to string) error {
log.Println("Rcpt to:", to)
return nil
}
func (s *Session) Data(r io.Reader) error {
if b, err := ioutil.ReadAll(r); err != nil {
return err
} else {
log.Println("Data:", string(b))
}
return nil
}
func (s *Session) Reset() {}
func (s *Session) Logout() error {
return nil
}
func main() {
be := &Backend{}
s := smtp.NewServer(be)
s.Addr = ":1025"
s.Domain = "localhost"
s.ReadTimeout = 10 * time.Second
s.WriteTimeout = 10 * time.Second
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
.
```
## Relationship with net/smtp
The Go standard library provides a SMTP client implementation in `net/smtp`.
However `net/smtp` is frozen: it's not getting any new features. go-smtp
provides a server implementation and a number of client improvements.
## Licence
MIT

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

@@ -0,0 +1,102 @@
package smtp
import (
"errors"
"io"
)
var (
ErrAuthRequired = errors.New("Please authenticate first")
ErrAuthUnsupported = errors.New("Authentication not supported")
)
// A SMTP server backend.
type Backend interface {
// Authenticate a user. Return smtp.ErrAuthUnsupported if you don't want to
// support this.
Login(state *ConnectionState, username, password string) (Session, error)
// Called if the client attempts to send mail without logging in first.
// Return smtp.ErrAuthRequired if you don't want to support this.
AnonymousLogin(state *ConnectionState) (Session, error)
}
type BodyType string
const (
Body7Bit BodyType = "7BIT"
Body8BitMIME BodyType = "8BITMIME"
BodyBinaryMIME BodyType = "BINARYMIME"
)
// MailOptions contains custom arguments that were
// passed as an argument to the MAIL command.
type MailOptions struct {
// Value of BODY= argument, 7BIT, 8BITMIME or BINARYMIME.
Body BodyType
// Size of the body. Can be 0 if not specified by client.
Size int
// TLS is required for the message transmission.
//
// The message should be rejected if it can't be transmitted
// with TLS.
RequireTLS bool
// The message envelope or message header contains UTF-8-encoded strings.
// This flag is set by SMTPUTF8-aware (RFC 6531) client.
UTF8 bool
// The authorization identity asserted by the message sender in decoded
// form with angle brackets stripped.
//
// nil value indicates missing AUTH, non-nil empty string indicates
// AUTH=<>.
//
// Defined in RFC 4954.
Auth *string
}
// Session is used by servers to respond to an SMTP client.
//
// The methods are called when the remote client issues the matching command.
type Session interface {
// Discard currently processed message.
Reset()
// Free all resources associated with session.
Logout() error
// Set return path for currently processed message.
Mail(from string, opts MailOptions) error
// Add recipient for currently processed message.
Rcpt(to string) error
// Set currently processed message contents and send it.
Data(r io.Reader) error
}
// LMTPSession is an add-on interface for Session. It can be implemented by
// LMTP servers to provide extra functionality.
type LMTPSession interface {
// LMTPData is the LMTP-specific version of Data method.
// It can be optionally implemented by the backend to provide
// per-recipient status information when it is used over LMTP
// protocol.
//
// LMTPData implementation sets status information using passed
// StatusCollector by calling SetStatus once per each AddRcpt
// call, even if AddRcpt was called multiple times with
// the same argument. SetStatus must not be called after
// LMTPData returns.
//
// Return value of LMTPData itself is used as a status for
// recipients that got no status set before using StatusCollector.
LMTPData(r io.Reader, status StatusCollector) error
}
// StatusCollector allows a backend to provide per-recipient status
// information.
type StatusCollector interface {
SetStatus(rcptTo string, err error)
}

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

@@ -0,0 +1,679 @@
// 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"
"fmt"
"io"
"net"
"net/textproto"
"strconv"
"strings"
"time"
"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
lmtp bool
// map of supported extensions
ext map[string]string
// supported auth mechanisms
auth []string
localName string // the name to use in HELO/EHLO/LHLO
didHello bool // whether we've said HELO/EHLO/LHLO
helloError error // the error from the hello
rcpts []string // recipients accumulated for the current session
// Time to wait for command responses (this includes 3xx reply to DATA).
CommandTimeout time.Duration
// Time to wait for responses after final dot.
SubmissionTimeout time.Duration
// Logger for all network activity.
DebugWriter io.Writer
}
// 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)
}
// DialTLS returns a new Client connected to an SMTP server via TLS at addr.
// The addr must include a port, as in "mail.example.com:smtps".
//
// A nil tlsConfig is equivalent to a zero tls.Config.
func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
conn, err := tls.Dial("tcp", addr, tlsConfig)
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) {
c := &Client{
serverName: host,
localName: "localhost",
// As recommended by RFC 5321. For DATA command reply (3xx one) RFC
// recommends a slightly shorter timeout but we do not bother
// differentiating these.
CommandTimeout: 5 * time.Minute,
// 10 minutes + 2 minute buffer in case the server is doing transparent
// forwarding and also follows recommended timeouts.
SubmissionTimeout: 12 * time.Minute,
}
c.setConn(conn)
_, _, err := c.Text.ReadResponse(220)
if err != nil {
c.Text.Close()
if protoErr, ok := err.(*textproto.Error); ok {
return nil, toSMTPErr(protoErr)
}
return nil, err
}
return c, nil
}
// NewClientLMTP returns a new LMTP Client (as defined in RFC 2033) using an
// existing connector and host as a server name to be used when authenticating.
func NewClientLMTP(conn net.Conn, host string) (*Client, error) {
c, err := NewClient(conn, host)
if err != nil {
return nil, err
}
c.lmtp = true
return c, nil
}
// setConn sets the underlying network connection for the client.
func (c *Client) setConn(conn net.Conn) {
c.conn = conn
var r io.Reader = conn
var w io.Writer = conn
r = &lineLimitReader{
R: conn,
// Doubled maximum line length per RFC 5321 (Section 4.5.3.1.6)
LineLimit: 2000,
}
r = io.TeeReader(r, clientDebugWriter{c})
w = io.MultiWriter(w, clientDebugWriter{c})
rwc := struct {
io.Reader
io.Writer
io.Closer
}{
Reader: r,
Writer: w,
Closer: conn,
}
c.Text = textproto.NewConn(rwc)
_, isTLS := conn.(*tls.Conn)
c.tls = isTLS
}
// 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.
//
// If server returns an error, it will be of type *SMTPError.
func (c *Client) Hello(localName string) error {
if err := validateLine(localName); err != nil {
return err
}
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
// textproto.Error returned by c.Text.ReadResponse is converted into SMTPError.
func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
defer c.conn.SetDeadline(time.Time{})
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)
if err != nil {
if protoErr, ok := err.(*textproto.Error); ok {
smtpErr := toSMTPErr(protoErr)
return code, smtpErr.Message, smtpErr
}
return code, msg, err
}
return code, msg, nil
}
// 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 {
cmd := "EHLO"
if c.lmtp {
cmd = "LHLO"
}
_, msg, err := c.cmd(250, "%s %s", cmd, 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.
//
// A nil config is equivalent to a zero tls.Config.
//
// If server returns an error, it will be of type *SMTPError.
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
}
if config == nil {
config = &tls.Config{}
}
if config.ServerName == "" {
// Make a copy to avoid polluting argument
config = config.Clone()
config.ServerName = c.serverName
}
if testHookStartTLS != nil {
testHookStartTLS(config)
}
c.setConn(tls.Client(c.conn, config))
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.
//
// If server returns an error, it will be of type *SMTPError.
func (c *Client) Verify(addr string) error {
if err := validateLine(addr); err != nil {
return err
}
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.
// Only servers that advertise the AUTH extension support this function.
//
// If server returns an error, it will be of type *SMTPError.
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 {
return err
}
resp64 := make([]byte, encoding.EncodedLen(len(resp)))
encoding.Encode(resp64, resp)
code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("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 = toSMTPErr(&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, "*")
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.
//
// If opts is not nil, MAIL arguments provided in the structure will be added
// to the command. Handling of unsupported options depends on the extension.
//
// If server returns an error, it will be of type *SMTPError.
func (c *Client) Mail(from string, opts *MailOptions) error {
if err := validateLine(from); err != nil {
return err
}
if err := c.hello(); err != nil {
return err
}
cmdStr := "MAIL FROM:<%s>"
if _, ok := c.ext["8BITMIME"]; ok {
cmdStr += " BODY=8BITMIME"
}
if _, ok := c.ext["SIZE"]; ok && opts != nil && opts.Size != 0 {
cmdStr += " SIZE=" + strconv.Itoa(opts.Size)
}
if opts != nil && opts.RequireTLS {
if _, ok := c.ext["REQUIRETLS"]; ok {
cmdStr += " REQUIRETLS"
} else {
return errors.New("smtp: server does not support REQUIRETLS")
}
}
if opts != nil && opts.UTF8 {
if _, ok := c.ext["SMTPUTF8"]; ok {
cmdStr += " SMTPUTF8"
} else {
return errors.New("smtp: server does not support SMTPUTF8")
}
}
if opts != nil && opts.Auth != nil {
if _, ok := c.ext["AUTH"]; ok {
cmdStr += " AUTH=" + encodeXtext(*opts.Auth)
}
// We can safely discard parameter if server does not support AUTH.
}
_, _, 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.
//
// If server returns an error, it will be of type *SMTPError.
func (c *Client) Rcpt(to string) error {
if err := validateLine(to); err != nil {
return err
}
if _, _, err := c.cmd(25, "RCPT TO:<%s>", to); err != nil {
return err
}
c.rcpts = append(c.rcpts, to)
return nil
}
type dataCloser struct {
c *Client
io.WriteCloser
statusCb func(rcpt string, status *SMTPError)
}
func (d *dataCloser) Close() error {
d.WriteCloser.Close()
d.c.conn.SetDeadline(time.Now().Add(d.c.SubmissionTimeout))
defer d.c.conn.SetDeadline(time.Time{})
expectedResponses := len(d.c.rcpts)
if d.c.lmtp {
for expectedResponses > 0 {
rcpt := d.c.rcpts[len(d.c.rcpts)-expectedResponses]
if _, _, err := d.c.Text.ReadResponse(250); err != nil {
if protoErr, ok := err.(*textproto.Error); ok {
if d.statusCb != nil {
d.statusCb(rcpt, toSMTPErr(protoErr))
}
} else {
return err
}
} else if d.statusCb != nil {
d.statusCb(rcpt, nil)
}
expectedResponses--
}
return nil
} else {
_, _, err := d.c.Text.ReadResponse(250)
if err != nil {
if protoErr, ok := err.(*textproto.Error); ok {
return toSMTPErr(protoErr)
}
return err
}
return nil
}
}
// 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.
//
// If server returns an error, it will be of type *SMTPError.
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}, nil
}
// LMTPData is the LMTP-specific version of the Data method. It accepts a callback
// that will be called for each status response received from the server.
//
// Status callback will receive a SMTPError argument for each negative server
// reply and nil for each positive reply. I/O errors will not be reported using
// callback and instead will be returned by the Close method of io.WriteCloser.
// Callback will be called for each successfull Rcpt call done before in the
// same order.
func (c *Client) LMTPData(statusCb func(rcpt string, status *SMTPError)) (io.WriteCloser, error) {
if !c.lmtp {
return nil, errors.New("smtp: not a LMTP client")
}
_, _, err := c.cmd(354, "DATA")
if err != nil {
return nil, err
}
return &dataCloser{c, c.Text.DotWriter(), statusCb}, 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.
//
// SendMail is intended to be used for very simple use-cases. If you want to
// customize SendMail's behavior, use a Client instead.
//
// The SendMail function and the go-smtp package are low-level
// mechanisms and provide no support for DKIM signing (see go-msgauth), MIME
// attachments (see the mime/multipart package or the go-message package), or
// other mail functionality.
func SendMail(addr string, a sasl.Client, from string, to []string, r io.Reader) error {
if err := validateLine(from); err != nil {
return err
}
for _, recp := range to {
if err := validateLine(recp); err != nil {
return err
}
}
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 {
if err = c.StartTLS(nil); err != nil {
return err
}
}
if a != nil && c.ext != nil {
if _, ok := c.ext["AUTH"]; !ok {
return errors.New("smtp: server doesn't support AUTH")
}
if err = c.Auth(a); err != nil {
return err
}
}
if err = c.Mail(from, nil); 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
}
if _, _, err := c.cmd(250, "RSET"); err != nil {
return err
}
c.rcpts = nil
return nil
}
// Noop sends the NOOP command to the server. It does nothing but check
// that the connection to the server is okay.
func (c *Client) Noop() error {
if err := c.hello(); err != nil {
return err
}
_, _, err := c.cmd(250, "NOOP")
return err
}
// Quit sends the QUIT command and closes the connection to the server.
//
// If Quit fails the connection is not closed, Close should be used
// in this case.
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()
}
func parseEnhancedCode(s string) (EnhancedCode, error) {
parts := strings.Split(s, ".")
if len(parts) != 3 {
return EnhancedCode{}, fmt.Errorf("wrong amount of enhanced code parts")
}
code := EnhancedCode{}
for i, part := range parts {
num, err := strconv.Atoi(part)
if err != nil {
return code, err
}
code[i] = num
}
return code, nil
}
// toSMTPErr converts textproto.Error into SMTPError, parsing
// enhanced status code if it is present.
func toSMTPErr(protoErr *textproto.Error) *SMTPError {
if protoErr == nil {
return nil
}
smtpErr := &SMTPError{
Code: protoErr.Code,
Message: protoErr.Msg,
}
parts := strings.SplitN(protoErr.Msg, " ", 2)
if len(parts) != 2 {
return smtpErr
}
enchCode, err := parseEnhancedCode(parts[0])
if err != nil {
return smtpErr
}
msg := parts[1]
// Per RFC 2034, enhanced code should be prepended to each line.
msg = strings.ReplaceAll(msg, "\n"+parts[0]+" ", "\n")
smtpErr.EnhancedCode = enchCode
smtpErr.Message = msg
return smtpErr
}
type clientDebugWriter struct {
c *Client
}
func (cdw clientDebugWriter) Write(b []byte) (int, error) {
if cdw.c.DebugWriter == nil {
return len(b), nil
}
return cdw.c.DebugWriter.Write(b)
}

1005
vendor/github.com/emersion/go-smtp/conn.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,147 @@
package smtp
import (
"bufio"
"io"
)
type EnhancedCode [3]int
// SMTPError specifies the error code, enhanced error code (if any) and
// message returned by the server.
type SMTPError struct {
Code int
EnhancedCode EnhancedCode
Message string
}
// NoEnhancedCode is used to indicate that enhanced error code should not be
// included in response.
//
// Note that RFC 2034 requires an enhanced code to be included in all 2xx, 4xx
// and 5xx responses. This constant is exported for use by extensions, you
// should probably use EnhancedCodeNotSet instead.
var NoEnhancedCode = EnhancedCode{-1, -1, -1}
// EnhancedCodeNotSet is a nil value of EnhancedCode field in SMTPError, used
// to indicate that backend failed to provide enhanced status code. X.0.0 will
// be used (X is derived from error code).
var EnhancedCodeNotSet = EnhancedCode{0, 0, 0}
func (err *SMTPError) Error() string {
return err.Message
}
func (err *SMTPError) Temporary() bool {
return err.Code/100 == 4
}
var ErrDataTooLarge = &SMTPError{
Code: 552,
EnhancedCode: EnhancedCode{5, 3, 4},
Message: "Maximum message size exceeded",
}
type dataReader struct {
r *bufio.Reader
state int
limited bool
n int64 // Maximum bytes remaining
}
func newDataReader(c *Conn) *dataReader {
dr := &dataReader{
r: c.text.R,
}
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]
}
}
// Code below is taken from net/textproto with only one modification to
// not rewrite CRLF -> LF.
// Run data through a simple state machine to
// elide leading dots and detect ending .\r\n line.
const (
stateBeginLine = iota // beginning of line; initial state; must be zero
stateDot // read . at beginning of line
stateDotCR // read .\r at beginning of line
stateCR // read \r (possibly at end of line)
stateData // reading data in middle of line
stateEOF // reached .\r\n end marker line
)
for n < len(b) && r.state != stateEOF {
var c byte
c, err = r.r.ReadByte()
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
break
}
switch r.state {
case stateBeginLine:
if c == '.' {
r.state = stateDot
continue
}
r.state = stateData
case stateDot:
if c == '\r' {
r.state = stateDotCR
continue
}
if c == '\n' {
r.state = stateEOF
continue
}
r.state = stateData
case stateDotCR:
if c == '\n' {
r.state = stateEOF
continue
}
r.state = stateData
case stateCR:
if c == '\n' {
r.state = stateBeginLine
break
}
r.state = stateData
case stateData:
if c == '\r' {
r.state = stateCR
}
if c == '\n' {
r.state = stateBeginLine
}
}
b[n] = c
n++
}
if err == nil && r.state == stateEOF {
err = io.EOF
}
if r.limited {
r.n -= int64(n)
}
return
}

View File

@@ -0,0 +1,47 @@
package smtp
import (
"errors"
"io"
)
var ErrTooLongLine = errors.New("smtp: too longer line in input stream")
// lineLimitReader reads from the underlying Reader but restricts
// line length of lines in input stream to a certain length.
//
// If line length exceeds the limit - Read returns ErrTooLongLine
type lineLimitReader struct {
R io.Reader
LineLimit int
curLineLength int
}
func (r *lineLimitReader) Read(b []byte) (int, error) {
if r.curLineLength > r.LineLimit && r.LineLimit > 0 {
return 0, ErrTooLongLine
}
n, err := r.R.Read(b)
if err != nil {
return n, err
}
if r.LineLimit == 0 {
return n, nil
}
for _, chr := range b[:n] {
if chr == '\n' {
r.curLineLength = 0
}
r.curLineLength++
if r.curLineLength > r.LineLimit {
return 0, ErrTooLongLine
}
}
return n, nil
}

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

@@ -0,0 +1,70 @@
package smtp
import (
"fmt"
"strings"
)
func parseCmd(line string) (cmd string, arg string, err error) {
line = strings.TrimRight(line, "\r\n")
l := len(line)
switch {
case strings.HasPrefix(strings.ToUpper(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 SMTPUTF8"
// The leading space is mandatory.
func parseArgs(args []string) (map[string]string, error) {
argMap := map[string]string{}
for _, arg := range args {
if arg == "" {
continue
}
m := strings.Split(arg, "=")
switch len(m) {
case 2:
argMap[strings.ToUpper(m[0])] = m[1]
case 1:
argMap[strings.ToUpper(m[0])] = ""
default:
return nil, fmt.Errorf("Failed to parse arg string: %q", arg)
}
}
return argMap, 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
}

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

@@ -0,0 +1,263 @@
package smtp
import (
"crypto/tls"
"errors"
"io"
"log"
"net"
"os"
"sync"
"time"
"github.com/emersion/go-sasl"
)
var errTCPAndLMTP = errors.New("smtp: cannot start LMTP server listening on a TCP socket")
// A function that creates SASL servers.
type SaslServerFactory func(conn *Conn) sasl.Server
// Logger interface is used by Server to report unexpected internal errors.
type Logger interface {
Printf(format string, v ...interface{})
Println(v ...interface{})
}
// A SMTP server.
type Server struct {
// TCP or Unix address to listen on.
Addr string
// The server TLS configuration.
TLSConfig *tls.Config
// Enable LMTP mode, as defined in RFC 2033. LMTP mode cannot be used with a
// TCP listener.
LMTP bool
Domain string
MaxRecipients int
MaxMessageBytes int
MaxLineLength int
AllowInsecureAuth bool
Strict bool
Debug io.Writer
ErrorLog Logger
ReadTimeout time.Duration
WriteTimeout time.Duration
// Advertise SMTPUTF8 (RFC 6531) capability.
// Should be used only if backend supports it.
EnableSMTPUTF8 bool
// Advertise REQUIRETLS (RFC 8689) capability.
// Should be used only if backend supports it.
EnableREQUIRETLS bool
// Advertise BINARYMIME (RFC 3030) capability.
// Should be used only if backend supports it.
EnableBINARYMIME bool
// If set, the AUTH command will not be advertised and authentication
// attempts will be rejected. This setting overrides AllowInsecureAuth.
AuthDisabled bool
// The server backend.
Backend Backend
caps []string
auths map[string]SaslServerFactory
done chan struct{}
locker sync.Mutex
listeners []net.Listener
conns map[*Conn]struct{}
}
// New creates a new SMTP server.
func NewServer(be Backend) *Server {
return &Server{
// Doubled maximum line length per RFC 5321 (Section 4.5.3.1.6)
MaxLineLength: 2000,
Backend: be,
done: make(chan struct{}, 1),
ErrorLog: log.New(os.Stderr, "smtp/server ", log.LstdFlags),
caps: []string{"PIPELINING", "8BITMIME", "ENHANCEDSTATUSCODES", "CHUNKING"},
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")
}
state := conn.State()
session, err := be.Login(&state, username, password)
if err != nil {
return err
}
conn.SetSession(session)
return nil
})
},
},
conns: make(map[*Conn]struct{}),
}
}
// Serve accepts incoming connections on the Listener l.
func (s *Server) Serve(l net.Listener) error {
s.locker.Lock()
s.listeners = append(s.listeners, l)
s.locker.Unlock()
for {
c, err := l.Accept()
if err != nil {
select {
case <-s.done:
// we called Close()
return nil
default:
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.protocolError(501, EnhancedCode{5, 5, 2}, "Bad command")
continue
}
c.handle(cmd, arg)
} else {
if err == io.EOF {
return nil
}
if err == ErrTooLongLine {
c.WriteResponse(500, EnhancedCode{5, 4, 0}, "Too long line, closing connection")
return nil
}
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
c.WriteResponse(221, EnhancedCode{2, 4, 2}, "Idle timeout, bye bye")
return nil
}
c.WriteResponse(221, EnhancedCode{2, 4, 0}, "Connection error, sorry")
return err
}
}
}
// ListenAndServe listens on the network address s.Addr and then calls Serve
// to handle requests on incoming connections.
//
// If s.Addr is blank and LMTP is disabled, ":smtp" is used.
func (s *Server) ListenAndServe() error {
network := "tcp"
if s.LMTP {
network = "unix"
}
addr := s.Addr
if !s.LMTP && addr == "" {
addr = ":smtp"
}
l, err := net.Listen(network, 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 {
if s.LMTP {
return errTCPAndLMTP
}
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 immediately closes all active listeners and connections.
//
// Close returns any error returned from closing the server's underlying
// listener(s).
func (s *Server) Close() error {
select {
case <-s.done:
return errors.New("smtp: server already closed")
default:
close(s.done)
}
var err error
for _, l := range s.listeners {
if lerr := l.Close(); lerr != nil && err == nil {
err = lerr
}
}
s.locker.Lock()
for conn := range s.conns {
conn.Close()
}
s.locker.Unlock()
return err
}
// 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)
}
}

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

@@ -0,0 +1,30 @@
// 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
// ENHANCEDSTATUSCODES: RFC 2034
// SMTPUTF8: RFC 6531
// REQUIRETLS: RFC 8689
// CHUNKING: RFC 3030
// BINARYMIME: RFC 3030
//
// LMTP (RFC 2033) is also supported.
//
// Additional extensions may be handled by other packages.
package smtp
import (
"errors"
"strings"
)
// validateLine checks to see if a line has CR or LF as per RFC 5321
func validateLine(line string) error {
if strings.ContainsAny(line, "\n\r") {
return errors.New("smtp: A line must not contain CR or LF")
}
return nil
}