/* * 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 smtpserver import ( "crypto/ed25519" "fmt" "log" "github.com/emersion/go-smtp" "github.com/neilalexander/yggmail/internal/account" "github.com/neilalexander/yggmail/internal/notify" "github.com/neilalexander/yggmail/internal/smtpsender" "github.com/neilalexander/yggmail/internal/storage" "github.com/neilalexander/yggmail/internal/storage/remote" "github.com/neilalexander/yggmail/internal/utils" ) type BackendMode int const ( BackendModeInternal BackendMode = iota BackendModeExternal ) type Backend struct { Mode BackendMode Log *log.Logger Queues *smtpsender.Queues Storage storage.Storage Notify *notify.Notify Account *account.Manager NodePublicKey ed25519.PublicKey // Config *config.Config } func (b *Backend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) { switch b.Mode { case BackendModeInternal: // If our username is email-like, then take just the localpart dPk, aPk, err := utils.ParseAddress(username) if err != nil { return nil, fmt.Errorf("failed to authenticate: wrong domain or username") } // Work in domain mode: Get account session, err := b.Account.Login(username, password) if err != nil { return nil, fmt.Errorf("failed to autheticate: %w", err) } username = utils.EncodeString(aPk) var storage storage.Storage if !b.NodePublicKey.Equal(dPk) { storage = remote.NewRemoteStorage(dPk, session, b.Queues.Transport) b.Log.Println("SMTP L: Authenticate with remote storage as", username) //return nil, fmt.Errorf("failed to authenticate: wrong domain in username") } else { storage = b.Storage b.Log.Println("SMTP L: Authenticate with local storage as", username) } defer b.Log.Printf("Authenticated SMTP user as %q\n", username) return &SessionLocal{ backend: b, session: session, storage: storage, state: state, }, nil case BackendModeExternal: return nil, fmt.Errorf("not expecting authenticated connection on external backend") } return nil, fmt.Errorf("authenticated login failed") } func (b *Backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) { switch b.Mode { case BackendModeInternal: return nil, fmt.Errorf("not expecting anonymous connection on internal backend") case BackendModeExternal: return nil, fmt.Errorf("not expecting anonymous connection on smtp. RPC Only") // // The connection came from our overlay listener, so we should check // // that they are who they claim to be // // Transport Layer address. Hex // nodePk, err := hex.DecodeString(state.RemoteAddr.String()) // if err != nil { // return nil, fmt.Errorf("hex.DecodeString: %w", err) // } // // HELO Hostname. Format // hostPk, err := utils.DecodeString(state.Hostname) // if err != nil { // return nil, fmt.Errorf("utils.DecodeKey: %w", err) // } // nPk := ed25519.PublicKey(nodePk) // hPk := ed25519.PublicKey(hostPk) // if !nPk.Equal(hPk) { // return nil, fmt.Errorf("you are not who you claim to be") // } // b.Log.Println("SMTP Local: Incoming session from", state.Hostname) // return &SessionRemote{ // backend: b, // state: state, // public: nPk[:], // }, nil } return nil, fmt.Errorf("anonymous login failed") }