refac: removed 7 direct and 12 indirect go modules for custom code instead
This commit is contained in:
1 parent
cfcf108488
commit
c68e9a7858
73 files changed
+2455
-428
No files matched your search
@@ -0,0 +1,261 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fakeSMTPOptions struct {
|
||||
authMechanisms string
|
||||
startTLS bool
|
||||
rejectRcpt bool
|
||||
rejectData bool
|
||||
implicitTLS bool
|
||||
listenAddress string
|
||||
}
|
||||
|
||||
type fakeSMTPSession struct {
|
||||
commands []string
|
||||
from string
|
||||
to string
|
||||
data string
|
||||
username string
|
||||
password string
|
||||
usedTLS bool
|
||||
}
|
||||
|
||||
type fakeSMTPServer struct {
|
||||
listener net.Listener
|
||||
options fakeSMTPOptions
|
||||
cert tls.Certificate
|
||||
roots *x509.CertPool
|
||||
mu sync.Mutex
|
||||
sessions []*fakeSMTPSession
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func newFakeSMTPServer(t *testing.T, options fakeSMTPOptions) *fakeSMTPServer {
|
||||
t.Helper()
|
||||
address := options.listenAddress
|
||||
if address == "" {
|
||||
address = "127.0.0.1:0"
|
||||
}
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
t.Skipf("cannot listen on %s: %v", address, err)
|
||||
}
|
||||
cert, roots := selfSignedLoopbackCert(t)
|
||||
server := &fakeSMTPServer{listener: listener, options: options, cert: cert, roots: roots}
|
||||
server.wg.Add(1)
|
||||
go server.acceptLoop()
|
||||
t.Cleanup(func() {
|
||||
_ = listener.Close()
|
||||
server.wg.Wait()
|
||||
})
|
||||
return server
|
||||
}
|
||||
|
||||
func selfSignedLoopbackCert(t *testing.T) (tls.Certificate, *x509.CertPool) {
|
||||
t.Helper()
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{CommonName: "127.0.0.1"},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parsed, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
roots.AddCert(parsed)
|
||||
return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key}, roots
|
||||
}
|
||||
|
||||
func (s *fakeSMTPServer) port() int {
|
||||
return s.listener.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
func (s *fakeSMTPServer) clientTLS() *tls.Config {
|
||||
return &tls.Config{RootCAs: s.roots}
|
||||
}
|
||||
|
||||
func (s *fakeSMTPServer) received() []*fakeSMTPSession {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return append([]*fakeSMTPSession(nil), s.sessions...)
|
||||
}
|
||||
|
||||
func (s *fakeSMTPServer) acceptLoop() {
|
||||
defer s.wg.Done()
|
||||
for {
|
||||
connection, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
s.serve(connection)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fakeSMTPServer) serve(connection net.Conn) {
|
||||
defer func() { _ = connection.Close() }()
|
||||
_ = connection.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
session := &fakeSMTPSession{}
|
||||
s.mu.Lock()
|
||||
s.sessions = append(s.sessions, session)
|
||||
s.mu.Unlock()
|
||||
|
||||
if s.options.implicitTLS {
|
||||
connection = tls.Server(connection, &tls.Config{Certificates: []tls.Certificate{s.cert}})
|
||||
session.usedTLS = true
|
||||
}
|
||||
reader := bufio.NewReader(connection)
|
||||
reply := func(line string) { _, _ = connection.Write([]byte(line + "\r\n")) }
|
||||
reply("220 fake ESMTP")
|
||||
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
line = strings.TrimRight(line, "\r\n")
|
||||
s.record(session, line)
|
||||
verb := strings.ToUpper(strings.Fields(line + " ")[0])
|
||||
|
||||
switch verb {
|
||||
case "EHLO":
|
||||
s.replyEhlo(reply, session)
|
||||
case "STARTTLS":
|
||||
reply("220 ready")
|
||||
connection = tls.Server(connection, &tls.Config{Certificates: []tls.Certificate{s.cert}})
|
||||
reader = bufio.NewReader(connection)
|
||||
reply = func(line string) { _, _ = connection.Write([]byte(line + "\r\n")) }
|
||||
session.usedTLS = true
|
||||
case "AUTH":
|
||||
if !s.authenticate(line, reader, reply, session) {
|
||||
return
|
||||
}
|
||||
case "MAIL":
|
||||
session.from = smtpAngleContent(line, "<", ">")
|
||||
reply("250 ok")
|
||||
case "RCPT":
|
||||
if s.options.rejectRcpt {
|
||||
reply("550 no such user")
|
||||
continue
|
||||
}
|
||||
session.to = smtpAngleContent(line, "<", ">")
|
||||
reply("250 ok")
|
||||
case "DATA":
|
||||
if s.options.rejectData {
|
||||
reply("554 refused")
|
||||
continue
|
||||
}
|
||||
reply("354 go ahead")
|
||||
session.data = readSMTPData(reader)
|
||||
reply("250 queued")
|
||||
case "QUIT":
|
||||
reply("221 bye")
|
||||
return
|
||||
default:
|
||||
reply("250 ok")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fakeSMTPServer) record(session *fakeSMTPSession, line string) {
|
||||
s.mu.Lock()
|
||||
session.commands = append(session.commands, line)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *fakeSMTPServer) replyEhlo(reply func(string), session *fakeSMTPSession) {
|
||||
lines := []string{"250-fake"}
|
||||
if s.options.startTLS && !session.usedTLS {
|
||||
lines = append(lines, "250-STARTTLS")
|
||||
}
|
||||
if s.options.authMechanisms != "" {
|
||||
lines = append(lines, "250-AUTH "+s.options.authMechanisms)
|
||||
}
|
||||
lines = append(lines, "250 8BITMIME")
|
||||
for _, line := range lines {
|
||||
reply(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fakeSMTPServer) authenticate(line string, reader *bufio.Reader, reply func(string), session *fakeSMTPSession) bool {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
reply("501 syntax")
|
||||
return false
|
||||
}
|
||||
switch strings.ToUpper(fields[1]) {
|
||||
case "PLAIN":
|
||||
raw, _ := base64.StdEncoding.DecodeString(fields[2])
|
||||
parts := strings.Split(string(raw), "\x00")
|
||||
session.username, session.password = parts[1], parts[2]
|
||||
case "LOGIN":
|
||||
reply("334 " + base64.StdEncoding.EncodeToString([]byte("Username:")))
|
||||
username, _ := reader.ReadString('\n')
|
||||
reply("334 " + base64.StdEncoding.EncodeToString([]byte("Password:")))
|
||||
password, _ := reader.ReadString('\n')
|
||||
session.username = decodeBase64Line(username)
|
||||
session.password = decodeBase64Line(password)
|
||||
default:
|
||||
reply("504 unsupported")
|
||||
return false
|
||||
}
|
||||
reply("235 ok")
|
||||
return true
|
||||
}
|
||||
|
||||
func decodeBase64Line(line string) string {
|
||||
raw, _ := base64.StdEncoding.DecodeString(strings.TrimSpace(line))
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
func smtpAngleContent(line string, open string, close string) string {
|
||||
start := strings.Index(line, open)
|
||||
end := strings.LastIndex(line, close)
|
||||
if start < 0 || end <= start {
|
||||
return ""
|
||||
}
|
||||
return line[start+1 : end]
|
||||
}
|
||||
|
||||
func readSMTPData(reader *bufio.Reader) string {
|
||||
var data strings.Builder
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil || line == ".\r\n" {
|
||||
return data.String()
|
||||
}
|
||||
data.WriteString(strings.TrimPrefix(line, "."))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user