/* * Original Copyright (c) 2026 Kaiy Ragur * Original Copyright (c) 2021 Neil Alexander * * 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/. */ package main import ( "crypto/ed25519" "flag" "fmt" "log" "os" "os/signal" "strings" "syscall" "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" "github.com/fatih/color" "net/http" _ "net/http/pprof" "github.com/neilalexander/yggmail/internal/account" "github.com/neilalexander/yggmail/internal/config" "github.com/neilalexander/yggmail/internal/imapserver" "github.com/neilalexander/yggmail/internal/notify" "github.com/neilalexander/yggmail/internal/remote" "github.com/neilalexander/yggmail/internal/shell" "github.com/neilalexander/yggmail/internal/smtpsender" "github.com/neilalexander/yggmail/internal/smtpserver" "github.com/neilalexander/yggmail/internal/storage" "github.com/neilalexander/yggmail/internal/storage/filestore" "github.com/neilalexander/yggmail/internal/storage/local" "github.com/neilalexander/yggmail/internal/storage/sqlite3" "github.com/neilalexander/yggmail/internal/transport" "github.com/neilalexander/yggmail/internal/utils" "github.com/neilalexander/yggmail/internal/welcome" ) type peerAddrList []string func (i *peerAddrList) String() string { return strings.Join(*i, ", ") } func (i *peerAddrList) Set(value string) error { *i = append(*i, value) return nil } func main() { go func() { http.ListenAndServe("localhost:6060", nil) }() rawlog := log.New(color.Output, "", 0) green := color.New(color.FgGreen).SprintfFunc() log := log.New(rawlog.Writer(), fmt.Sprintf("[ %s ] ", green("Yggmail")), log.LstdFlags|log.Lmsgprefix) 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") mcastregexp := flag.String("mcastregexp", ".*", "Regexp for multicast") shellMode := flag.String("sh", "", "-sh \"command args\"") flag.Var(&peerAddrs, "peer", "Connect to a specific Yggdrasil static peer") flag.Parse() db, err := sqlite3.NewSQLite3StorageStorage(*database) if err != nil { log.Fatalf("Failed to open database: %v", err) } defer db.Close() fs, err := filestore.NewFileStore("./mails/") if err != nil { log.Fatalf("Failed to open filesystem: %v", err) } storage := local.NewLocalStorage(db, fs, 0) accManager := account.NewManager(storage, log) nodeSk, nodePk := getID(storage, log) cfg := &config.Config{ PublicKey: nodePk, PrivateKey: nodeSk, } if *shellMode != "" { s := shell.NewShell(storage, log, cfg) args := strings.Split(*shellMode, " ") s.Run(args[0], args[1:]) return } checkFirstRun(accManager, storage, log, nodePk) tr, err := transport.NewYggdrasilTransport(rawlog, nodeSk, nodePk, peerAddrs, *multicast, *mcastregexp) if err != nil { log.Fatalf("Failed to setup Yggdrasil: %v", err) } // Notify bus notify := notify.NewNotifyBus(log) // Imap Frontend imapBackend := imapserver.NewBackend( cfg, log, storage, accManager, tr, notify, ) srv, err := imapserver.NewIMAPServer(imapBackend, *imapaddr, true) if err != nil { log.Fatal(err) } imapBackend.Server = srv // 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 go func() { localBackend := &smtpserver.Backend{ Log: log, Mode: smtpserver.BackendModeInternal, // Config: cfg, Storage: storage, Queues: queues, Notify: notify, Account: accManager, NodePublicKey: cfg.PublicKey, } 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 { return sasl.NewLoginServer(func(username, password string) error { _, err := localBackend.Login(nil, username, password) return err }) }) log.Println("Listening for local SMTP on:", server.Addr) if err := server.ListenAndServe(); err != nil { log.Fatal(err) } }() // RPC Mail-Server remoteSrv := remote.NewRemoteServer(cfg.PublicKey, storage, accManager, log, notify, queues) go func() { if err := remoteSrv.Serve(tr.Listener()); err != nil { log.Fatalf("Remote Storage Server failed: %v", err) } }() sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs 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("--------------------------------------") }