2021-07-18 23:03:28 +01:00
|
|
|
/*
|
2026-02-14 02:34:09 +05:00
|
|
|
* Original Copyright (c) 2026 Kaiy Ragur
|
|
|
|
|
* Original Copyright (c) 2021 Neil Alexander
|
2021-07-18 23:03:28 +01:00
|
|
|
*
|
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
*/
|
|
|
|
|
|
2021-07-07 18:15:07 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/ed25519"
|
|
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log"
|
|
|
|
|
"os"
|
2021-07-10 13:33:48 +01:00
|
|
|
"os/signal"
|
2021-07-07 18:15:07 +01:00
|
|
|
"strings"
|
2021-07-10 13:33:48 +01:00
|
|
|
"syscall"
|
2021-07-07 18:15:07 +01:00
|
|
|
|
|
|
|
|
"github.com/emersion/go-sasl"
|
|
|
|
|
"github.com/emersion/go-smtp"
|
2021-07-10 13:22:59 +01:00
|
|
|
"github.com/fatih/color"
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
"net/http"
|
|
|
|
|
_ "net/http/pprof"
|
|
|
|
|
|
|
|
|
|
"github.com/neilalexander/yggmail/internal/account"
|
2021-07-07 18:15:07 +01:00
|
|
|
"github.com/neilalexander/yggmail/internal/config"
|
|
|
|
|
"github.com/neilalexander/yggmail/internal/imapserver"
|
2026-02-14 02:34:09 +05:00
|
|
|
"github.com/neilalexander/yggmail/internal/notify"
|
|
|
|
|
"github.com/neilalexander/yggmail/internal/remote"
|
|
|
|
|
"github.com/neilalexander/yggmail/internal/shell"
|
2021-07-07 18:15:07 +01:00
|
|
|
"github.com/neilalexander/yggmail/internal/smtpsender"
|
|
|
|
|
"github.com/neilalexander/yggmail/internal/smtpserver"
|
2026-02-14 02:34:09 +05:00
|
|
|
"github.com/neilalexander/yggmail/internal/storage"
|
|
|
|
|
"github.com/neilalexander/yggmail/internal/storage/filestore"
|
|
|
|
|
"github.com/neilalexander/yggmail/internal/storage/local"
|
2021-07-07 18:15:07 +01:00
|
|
|
"github.com/neilalexander/yggmail/internal/storage/sqlite3"
|
|
|
|
|
"github.com/neilalexander/yggmail/internal/transport"
|
2021-07-08 23:12:20 +01:00
|
|
|
"github.com/neilalexander/yggmail/internal/utils"
|
2025-12-20 16:30:37 +02:00
|
|
|
"github.com/neilalexander/yggmail/internal/welcome"
|
2021-07-07 18:15:07 +01:00
|
|
|
)
|
|
|
|
|
|
2021-07-10 13:13:21 +01:00
|
|
|
type peerAddrList []string
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
func (i *peerAddrList) String() string { return strings.Join(*i, ", ") }
|
2021-07-10 13:13:21 +01:00
|
|
|
func (i *peerAddrList) Set(value string) error {
|
|
|
|
|
*i = append(*i, value)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2021-07-07 18:15:07 +01:00
|
|
|
|
|
|
|
|
func main() {
|
2026-02-14 02:34:09 +05:00
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
http.ListenAndServe("localhost:6060", nil)
|
|
|
|
|
}()
|
|
|
|
|
|
2021-07-10 13:29:56 +01:00
|
|
|
rawlog := log.New(color.Output, "", 0)
|
2021-07-10 13:22:59 +01:00
|
|
|
green := color.New(color.FgGreen).SprintfFunc()
|
2022-09-04 20:51:44 +01:00
|
|
|
log := log.New(rawlog.Writer(), fmt.Sprintf("[ %s ] ", green("Yggmail")), log.LstdFlags|log.Lmsgprefix)
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2021-07-10 13:13:21 +01:00
|
|
|
var peerAddrs peerAddrList
|
|
|
|
|
database := flag.String("database", "yggmail.db", "SQLite database file")
|
|
|
|
|
smtpaddr := flag.String("smtp", "localhost:1025", "SMTP listen address")
|
|
|
|
|
imapaddr := flag.String("imap", "localhost:1143", "IMAP listen address")
|
|
|
|
|
multicast := flag.Bool("multicast", false, "Connect to Yggdrasil peers on your LAN")
|
2025-12-20 14:06:14 +00:00
|
|
|
mcastregexp := flag.String("mcastregexp", ".*", "Regexp for multicast")
|
2026-02-14 02:34:09 +05:00
|
|
|
shellMode := flag.String("sh", "", "-sh \"command args\"")
|
|
|
|
|
flag.Var(&peerAddrs, "peer", "Connect to a specific Yggdrasil static peer")
|
2021-07-09 18:42:52 +01:00
|
|
|
flag.Parse()
|
2021-07-10 13:13:21 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
db, err := sqlite3.NewSQLite3StorageStorage(*database)
|
2021-07-07 18:15:07 +01:00
|
|
|
if err != nil {
|
2026-02-14 02:34:09 +05:00
|
|
|
log.Fatalf("Failed to open database: %v", err)
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
defer db.Close()
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
fs, err := filestore.NewFileStore("./mails/")
|
2021-07-07 18:15:07 +01:00
|
|
|
if err != nil {
|
2026-02-14 02:34:09 +05:00
|
|
|
log.Fatalf("Failed to open filesystem: %v", err)
|
2021-07-09 19:38:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
storage := local.NewLocalStorage(db, fs, 0)
|
2025-12-20 14:06:14 +00:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
accManager := account.NewManager(storage, log)
|
2024-10-02 13:31:09 +03:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
nodeSk, nodePk := getID(storage, log)
|
|
|
|
|
cfg := &config.Config{
|
|
|
|
|
PublicKey: nodePk,
|
|
|
|
|
PrivateKey: nodeSk,
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
if *shellMode != "" {
|
|
|
|
|
s := shell.NewShell(storage, log, cfg)
|
|
|
|
|
args := strings.Split(*shellMode, " ")
|
|
|
|
|
s.Run(args[0], args[1:])
|
|
|
|
|
return
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
checkFirstRun(accManager, storage, log, nodePk)
|
|
|
|
|
|
|
|
|
|
tr, err := transport.NewYggdrasilTransport(rawlog, nodeSk, nodePk, peerAddrs, *multicast, *mcastregexp)
|
2021-07-07 18:15:07 +01:00
|
|
|
if err != nil {
|
2026-02-14 02:34:09 +05:00
|
|
|
log.Fatalf("Failed to setup Yggdrasil: %v", err)
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
// Notify bus
|
|
|
|
|
notify := notify.NewNotifyBus(log)
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
// Imap Frontend
|
|
|
|
|
imapBackend := imapserver.NewBackend(
|
|
|
|
|
cfg,
|
|
|
|
|
log,
|
|
|
|
|
storage,
|
|
|
|
|
accManager,
|
|
|
|
|
tr,
|
|
|
|
|
notify,
|
|
|
|
|
)
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
srv, err := imapserver.NewIMAPServer(imapBackend, *imapaddr, true)
|
2021-07-10 11:42:55 +01:00
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2026-02-14 02:34:09 +05:00
|
|
|
imapBackend.Server = srv
|
2021-07-07 18:15:07 +01:00
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
// Queues schedule
|
|
|
|
|
|
|
|
|
|
// For connect with Peers and Push Mail we sign request
|
|
|
|
|
nodeSigner := func(data []byte) ([]byte, error) {
|
|
|
|
|
return ed25519.Sign(nodeSk, data), nil
|
|
|
|
|
}
|
|
|
|
|
nodeRPCClient := remote.NewClient(
|
|
|
|
|
nodePk,
|
|
|
|
|
nodeSigner,
|
|
|
|
|
tr,
|
|
|
|
|
)
|
|
|
|
|
queues := smtpsender.NewQueues(cfg, log, tr, storage, nodeRPCClient, notify)
|
|
|
|
|
|
|
|
|
|
// SMTP-Frontend
|
2021-07-07 18:15:07 +01:00
|
|
|
go func() {
|
|
|
|
|
localBackend := &smtpserver.Backend{
|
2026-02-14 02:34:09 +05:00
|
|
|
Log: log,
|
|
|
|
|
Mode: smtpserver.BackendModeInternal,
|
|
|
|
|
// Config: cfg,
|
|
|
|
|
Storage: storage,
|
|
|
|
|
Queues: queues,
|
|
|
|
|
Notify: notify,
|
|
|
|
|
Account: accManager,
|
|
|
|
|
NodePublicKey: cfg.PublicKey,
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
server := smtp.NewServer(localBackend)
|
|
|
|
|
server.Addr = *smtpaddr
|
|
|
|
|
server.Domain = utils.EncodeString(nodePk)
|
|
|
|
|
server.AllowInsecureAuth = true
|
|
|
|
|
server.EnableAuth(sasl.Login, func(conn *smtp.Conn) sasl.Server {
|
2021-07-07 18:15:07 +01:00
|
|
|
return sasl.NewLoginServer(func(username, password string) error {
|
|
|
|
|
_, err := localBackend.Login(nil, username, password)
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
})
|
2026-02-14 02:34:09 +05:00
|
|
|
log.Println("Listening for local SMTP on:", server.Addr)
|
|
|
|
|
if err := server.ListenAndServe(); err != nil {
|
2021-07-07 18:15:07 +01:00
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2026-02-14 02:34:09 +05:00
|
|
|
// RPC Mail-Server
|
|
|
|
|
remoteSrv := remote.NewRemoteServer(cfg.PublicKey, storage, accManager, log, notify, queues)
|
2021-07-07 18:15:07 +01:00
|
|
|
go func() {
|
2026-02-14 02:34:09 +05:00
|
|
|
if err := remoteSrv.Serve(tr.Listener()); err != nil {
|
|
|
|
|
log.Fatalf("Remote Storage Server failed: %v", err)
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2021-07-10 13:33:48 +01:00
|
|
|
sigs := make(chan os.Signal, 1)
|
|
|
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
|
<-sigs
|
2026-02-14 02:34:09 +05:00
|
|
|
log.Println("Shutting down...")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getID(s storage.Storage, l *log.Logger) (ed25519.PrivateKey, ed25519.PublicKey) {
|
|
|
|
|
skStr, _ := s.ConfigGet("private_key")
|
|
|
|
|
if skStr == "" {
|
|
|
|
|
_, sk, _ := ed25519.GenerateKey(nil)
|
|
|
|
|
s.ConfigSet("private_key", utils.EncodeString(sk))
|
|
|
|
|
l.Println("Generated new Yggdrasil Node Identity")
|
|
|
|
|
return sk, sk.Public().(ed25519.PublicKey)
|
|
|
|
|
}
|
|
|
|
|
skBytes, _ := utils.DecodeString(skStr)
|
|
|
|
|
sk := ed25519.PrivateKey(skBytes)
|
|
|
|
|
return sk, sk.Public().(ed25519.PublicKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func checkFirstRun(m *account.Manager, s storage.Storage, l *log.Logger, nodePk ed25519.PublicKey) {
|
|
|
|
|
accounts, _ := m.AccountList()
|
|
|
|
|
if len(accounts) > 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
l.Println("--- FIRST RUN: CREATE YOUR ACCOUNT ---")
|
|
|
|
|
|
|
|
|
|
pProvider := func() (string, error) {
|
|
|
|
|
p, err := account.DefaultProvider("Enter password for your new mail account: ")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
p2, err := account.DefaultProvider("Comfirm your password: ")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if p == p2 {
|
|
|
|
|
return p2, nil
|
|
|
|
|
}
|
|
|
|
|
return "", fmt.Errorf("Password not match")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acc, err := m.CreateNewAccount(pProvider)
|
|
|
|
|
if err != nil {
|
|
|
|
|
l.Fatalf("Failed to create initial account: %v", err)
|
|
|
|
|
}
|
|
|
|
|
welcome.Onboard(acc.PublicKey, s, l)
|
|
|
|
|
l.Printf("SUCCESS! Your address is: %s@%s\n", acc.PublicKey, utils.EncodeString(nodePk))
|
|
|
|
|
l.Println("--------------------------------------")
|
2021-07-07 18:15:07 +01:00
|
|
|
}
|