mirror of
https://github.com/neilalexander/yggmail.git
synced 2026-05-05 19:46:27 +03:00
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -32,4 +32,4 @@ jobs:
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/yggmail:${{ github.ref_name == 'main' && 'latest' || github.ref_name }}
|
||||
ghcr.io/${{ github.repository_owner }}/yggmail:${{ github.ref_name }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/golang:alpine3.18 as builder
|
||||
FROM docker.io/golang:alpine as builder
|
||||
|
||||
COPY . /src
|
||||
WORKDIR /src
|
||||
@@ -6,7 +6,7 @@ WORKDIR /src
|
||||
RUN apk add --no-cache --update go gcc g++
|
||||
RUN go build -o /src/yggmail ./cmd/yggmail
|
||||
|
||||
FROM docker.io/alpine:3.18
|
||||
FROM docker.io/alpine
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/neilalexander/yggmail
|
||||
LABEL org.opencontainers.image.description=Yggmail
|
||||
|
||||
@@ -207,7 +207,7 @@ func main() {
|
||||
overlayServer.MaxRecipients = 50
|
||||
overlayServer.AuthDisabled = true
|
||||
|
||||
if err := overlayServer.Serve(transport); err != nil {
|
||||
if err := overlayServer.Serve(transport.Listener()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
9
go.mod
9
go.mod
@@ -3,6 +3,7 @@ module github.com/neilalexander/yggmail
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/Arceliar/ironwood v0.0.0-20231104025256-ec84c695fc44
|
||||
github.com/emersion/go-imap v1.2.1
|
||||
github.com/emersion/go-imap-idle v0.0.0-20210907174914-db2568431445
|
||||
github.com/emersion/go-message v0.17.0
|
||||
@@ -10,16 +11,15 @@ require (
|
||||
github.com/emersion/go-smtp v0.15.0
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/gologme/log v1.3.0
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.5.4
|
||||
github.com/yggdrasil-network/yggquic v0.0.0-20231209220136-b412fc6f0d7e
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/quic-go/quic-go v0.40.0
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.5.2
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/term v0.13.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Arceliar/ironwood v0.0.0-20231127131626-465b82dfb5bd // indirect
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d // indirect
|
||||
github.com/bits-and-blooms/bitset v1.10.0 // indirect
|
||||
github.com/bits-and-blooms/bloom/v3 v3.6.0 // indirect
|
||||
@@ -31,7 +31,6 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/quic-go/quic-go v0.40.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
|
||||
14
go.sum
14
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/Arceliar/ironwood v0.0.0-20231127131626-465b82dfb5bd h1:458tnmZ4zM2gbLtefdYbaxyAJevDNEWu6tLKEqbK4wg=
|
||||
github.com/Arceliar/ironwood v0.0.0-20231127131626-465b82dfb5bd/go.mod h1:5x7fWW0mshe9WQ1lvSMmmHBYC3BeHH9gpwW5tz7cbfw=
|
||||
github.com/Arceliar/ironwood v0.0.0-20231104025256-ec84c695fc44 h1:u328GAZGtL0W4oFWQs4YWHZT215LL6Lw9aYJxx76UVs=
|
||||
github.com/Arceliar/ironwood v0.0.0-20231104025256-ec84c695fc44/go.mod h1:5x7fWW0mshe9WQ1lvSMmmHBYC3BeHH9gpwW5tz7cbfw=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
|
||||
github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
@@ -49,8 +49,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
@@ -67,10 +67,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
|
||||
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.5.4 h1:A7ZFmxkkbZhtqJgQXBVDw5sHsi25aUawLlJCCHnNsAs=
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.5.4/go.mod h1:TLmU4X0nfzCY9t5xABtFQ6GLoOtCae8xVatC9JwjD5I=
|
||||
github.com/yggdrasil-network/yggquic v0.0.0-20231209220136-b412fc6f0d7e h1:Ncp4P0jFiIJ+6o06a4PABcvWeJDdbUQSjMVaJVSOu2I=
|
||||
github.com/yggdrasil-network/yggquic v0.0.0-20231209220136-b412fc6f0d7e/go.mod h1:XFL2wkUXrqdl4AYiJghA+wW9xAm4ISvUtNrHdWPXjsE=
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.5.2 h1:OEt5xi5iQDhK4yGjp0Bq9B0uZyQz741WIlorE8oVW1c=
|
||||
github.com/yggdrasil-network/yggdrasil-go v0.5.2/go.mod h1:oATGHx91oFqq3h3RKFU9qADFcO27TCf3CbQqrG2wzvU=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
|
||||
@@ -123,7 +123,7 @@ func (q *Queue) run() {
|
||||
q.queues.Log.Println("Sending mail from", ref.From, "to", q.destination)
|
||||
|
||||
if err := func() error {
|
||||
conn, err := q.queues.Transport.Dial("yggdrasil", q.destination)
|
||||
conn, err := q.queues.Transport.Dial(q.destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("q.queues.Transport.Dial: %w", err)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@ import (
|
||||
)
|
||||
|
||||
type Transport interface {
|
||||
Dial(network, host string) (net.Conn, error)
|
||||
net.Listener
|
||||
Dial(host string) (net.Conn, error)
|
||||
Listener() net.Listener
|
||||
}
|
||||
|
||||
@@ -9,21 +9,48 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
iwt "github.com/Arceliar/ironwood/types"
|
||||
"github.com/fatih/color"
|
||||
gologme "github.com/gologme/log"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||
"github.com/yggdrasil-network/yggquic"
|
||||
)
|
||||
|
||||
func NewYggdrasilTransport(log *log.Logger, sk ed25519.PrivateKey, pk ed25519.PublicKey, peers []string, mcast bool) (*yggquic.YggdrasilTransport, error) {
|
||||
type YggdrasilTransport struct {
|
||||
listener *quic.Listener
|
||||
yggdrasil net.PacketConn
|
||||
transport *quic.Transport
|
||||
tlsConfig *tls.Config
|
||||
quicConfig *quic.Config
|
||||
incoming chan *yggdrasilSession
|
||||
sessions sync.Map // string -> quic.Connection
|
||||
dials sync.Map // string -> *yggdrasilDial
|
||||
}
|
||||
|
||||
type yggdrasilSession struct {
|
||||
quic.Connection
|
||||
quic.Stream
|
||||
}
|
||||
|
||||
type yggdrasilDial struct {
|
||||
context.Context
|
||||
context.CancelFunc
|
||||
}
|
||||
|
||||
func NewYggdrasilTransport(log *log.Logger, sk ed25519.PrivateKey, pk ed25519.PublicKey, peers []string, mcast bool) (*YggdrasilTransport, error) {
|
||||
yellow := color.New(color.FgYellow).SprintfFunc()
|
||||
glog := gologme.New(log.Writer(), fmt.Sprintf("[ %s ] ", yellow("Yggdrasil")), gologme.LstdFlags|gologme.Lmsgprefix)
|
||||
glog.EnableLevel("warn")
|
||||
@@ -69,5 +96,140 @@ func NewYggdrasilTransport(log *log.Logger, sk ed25519.PrivateKey, pk ed25519.Pu
|
||||
}
|
||||
}
|
||||
|
||||
return yggquic.New(ygg, *cfg.Certificate, nil)
|
||||
tr := &YggdrasilTransport{
|
||||
tlsConfig: &tls.Config{
|
||||
ServerName: hex.EncodeToString(ygg.PublicKey()),
|
||||
Certificates: []tls.Certificate{
|
||||
*cfg.Certificate,
|
||||
},
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
quicConfig: &quic.Config{
|
||||
HandshakeIdleTimeout: time.Second * 5,
|
||||
MaxIdleTimeout: time.Second * 60,
|
||||
},
|
||||
transport: &quic.Transport{
|
||||
Conn: ygg,
|
||||
},
|
||||
yggdrasil: ygg,
|
||||
incoming: make(chan *yggdrasilSession, 1),
|
||||
}
|
||||
|
||||
if tr.listener, err = tr.transport.Listen(tr.tlsConfig, tr.quicConfig); err != nil {
|
||||
return nil, fmt.Errorf("quic.Listen: %w", err)
|
||||
}
|
||||
|
||||
go tr.connectionAcceptLoop()
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
func (t *YggdrasilTransport) connectionAcceptLoop() {
|
||||
for {
|
||||
qc, err := t.listener.Accept(context.TODO())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
host := qc.RemoteAddr().String()
|
||||
if eqc, ok := t.sessions.LoadAndDelete(host); ok {
|
||||
eqc := eqc.(quic.Connection)
|
||||
_ = eqc.CloseWithError(0, "Connection replaced")
|
||||
}
|
||||
t.sessions.Store(host, qc)
|
||||
if dial, ok := t.dials.LoadAndDelete(host); ok {
|
||||
dial := dial.(*yggdrasilDial)
|
||||
dial.CancelFunc()
|
||||
}
|
||||
|
||||
go t.streamAcceptLoop(qc)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *YggdrasilTransport) streamAcceptLoop(qc quic.Connection) {
|
||||
host := qc.RemoteAddr().String()
|
||||
|
||||
defer qc.CloseWithError(0, "Timed out") // nolint:errcheck
|
||||
defer t.sessions.Delete(host)
|
||||
|
||||
for {
|
||||
qs, err := qc.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
t.incoming <- &yggdrasilSession{qc, qs}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *YggdrasilTransport) Dial(host string) (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
var retry bool
|
||||
retry:
|
||||
qc, ok := t.sessions.Load(host)
|
||||
if !ok {
|
||||
if dial, ok := t.dials.Load(host); ok {
|
||||
<-dial.(*yggdrasilDial).Done()
|
||||
}
|
||||
if qc, ok = t.sessions.Load(host); !ok {
|
||||
dialctx, dialcancel := context.WithCancel(ctx)
|
||||
defer dialcancel()
|
||||
|
||||
t.dials.Store(host, &yggdrasilDial{dialctx, dialcancel})
|
||||
defer t.dials.Delete(host)
|
||||
|
||||
addr := make(iwt.Addr, ed25519.PublicKeySize)
|
||||
k, err := hex.DecodeString(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(addr, k)
|
||||
|
||||
if qc, err = t.transport.Dial(dialctx, addr, t.tlsConfig, t.quicConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qc := qc.(quic.Connection)
|
||||
t.sessions.Store(host, qc)
|
||||
go t.streamAcceptLoop(qc)
|
||||
}
|
||||
}
|
||||
if qc == nil {
|
||||
return nil, net.ErrClosed
|
||||
} else {
|
||||
qc := qc.(quic.Connection)
|
||||
qs, err := qc.OpenStreamSync(ctx)
|
||||
if err != nil {
|
||||
if !retry {
|
||||
retry = true
|
||||
goto retry
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// For some reason this is needed to kick the stream
|
||||
_, err = qs.Write([]byte(" "))
|
||||
return &yggdrasilSession{qc, qs}, err
|
||||
}
|
||||
}
|
||||
|
||||
func (t *YggdrasilTransport) Listener() net.Listener {
|
||||
return &yggdrasilListener{t}
|
||||
}
|
||||
|
||||
type yggdrasilListener struct {
|
||||
*YggdrasilTransport
|
||||
}
|
||||
|
||||
func (t *yggdrasilListener) Accept() (net.Conn, error) {
|
||||
return <-t.incoming, nil
|
||||
}
|
||||
|
||||
func (t *yggdrasilListener) Addr() net.Addr {
|
||||
return t.listener.Addr()
|
||||
}
|
||||
|
||||
func (t *yggdrasilListener) Close() error {
|
||||
if err := t.listener.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.yggdrasil.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user