From 704fc53db10aa52b1ad7356432da71acaf7af0de Mon Sep 17 00:00:00 2001 From: FIGBERT Date: Sun, 6 Aug 2023 22:01:40 -0700 Subject: [PATCH] Change headless from flag to subpackage --- headless/headless.go | 136 +++++++++++++++++++++++++++++++++++++++++++ initialize/gomuks.go | 28 +++------ interface/matrix.go | 1 - main.go | 27 +-------- matrix/matrix.go | 50 ++++------------ ui/view-roster.go | 4 -- 6 files changed, 154 insertions(+), 92 deletions(-) create mode 100644 headless/headless.go diff --git a/headless/headless.go b/headless/headless.go new file mode 100644 index 0000000..b4f0d51 --- /dev/null +++ b/headless/headless.go @@ -0,0 +1,136 @@ +package headless + +import ( + "errors" + "fmt" + "os" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/crypto" + "maunium.net/go/mautrix/crypto/ssss" + + "maunium.net/go/gomuks/initialize" + "maunium.net/go/gomuks/ui" +) + +type HeadlessConfig struct { + OutputDir, + MxID, MxPassword, + KeyPath, KeyPassword, + RecoveryPhrase string +} + +func HeadlessInit(conf HeadlessConfig) error { + // setup package dir + os.Setenv("GOMUKS_ROOT", conf.OutputDir) + + // init boilerplate + configDir, dataDir, cacheDir, downloadDir, err := initDirs() + if err != nil { + return err + } + + gmx := initialize.NewGomuks(ui.NewGomuksUI, configDir, dataDir, cacheDir, downloadDir) + err = gmx.StartHeadless() + if err != nil { + return err + } + + // login section + if err = gmx.Matrix().Login(conf.MxID, conf.MxPassword); err != nil { + return err + } + + // key import + data, err := os.ReadFile(conf.KeyPath) + if err != nil { + return err + } + mach := gmx.Matrix().Crypto().(*crypto.OlmMachine) + _, _, err = mach.ImportKeys(conf.KeyPassword, data) + if err != nil { + return fmt.Errorf("Failed to import sessions: %v", err) + } + + // verify (fetch) + key, err := getSSSS(mach, conf.MxPassword, conf.RecoveryPhrase) + if err != nil { + return err + } + + err = mach.FetchCrossSigningKeysFromSSSS(key) + if err != nil { + return fmt.Errorf("Error fetching cross-signing keys: %v", err) + } + + // verify (sign) + if mach.CrossSigningKeys == nil { + return fmt.Errorf("Cross-signing keys not cached") + } + + err = mach.SignOwnDevice(mach.OwnIdentity()) + if err != nil { + return fmt.Errorf("Failed to self-sign: %v", err) + } + + // sync + // how? + // this does too much: gmx.Matrix().Start() + + return nil +} + +func initDirs() (string, string, string, string, error) { + config, err := initialize.UserConfigDir() + if err != nil { + return "", "", "", "", fmt.Errorf("Failed to get config directory: %v", err) + } + + data, err := initialize.UserDataDir() + if err != nil { + return "", "", "", "", fmt.Errorf("Failed to get data directory: %v", err) + } + + cache, err := initialize.UserCacheDir() + if err != nil { + return "", "", "", "", fmt.Errorf("Failed to get cache directory: %v", err) + } + + download, err := initialize.UserDownloadDir() + if err != nil { + return "", "", "", "", fmt.Errorf("Failed to get download directory: %v", err) + } + + return config, data, cache, download, nil +} + +func getSSSS(mach *crypto.OlmMachine, password, recoveryPhrase string) (*ssss.Key, error) { + _, keyData, err := mach.SSSS.GetDefaultKeyData() + if err != nil { + if errors.Is(err, mautrix.MNotFound) { + return nil, fmt.Errorf("SSSS not set up, use `!ssss generate --set-default` first") + } else { + return nil, fmt.Errorf("Failed to fetch default SSSS key data: %v", err) + } + } + + var key *ssss.Key + if keyData.Passphrase != nil && keyData.Passphrase.Algorithm == ssss.PassphraseAlgorithmPBKDF2 { + key, err = keyData.VerifyPassphrase(password) + if errors.Is(err, ssss.ErrIncorrectSSSSKey) { + return nil, fmt.Errorf("Incorrect passphrase") + } + } else { + key, err = keyData.VerifyRecoveryKey(recoveryPhrase) + if errors.Is(err, ssss.ErrInvalidRecoveryKey) { + return nil, fmt.Errorf("Malformed recovery key") + } else if errors.Is(err, ssss.ErrIncorrectSSSSKey) { + return nil, fmt.Errorf("Incorrect recovery key") + } + } + // All the errors should already be handled above, this is just for backup + if err != nil { + return nil, fmt.Errorf("Failed to get SSSS key: %v", err) + } + return key, nil +} diff --git a/initialize/gomuks.go b/initialize/gomuks.go index e7223e1..01df90b 100644 --- a/initialize/gomuks.go +++ b/initialize/gomuks.go @@ -43,14 +43,14 @@ type Gomuks struct { // NewGomuks creates a new Gomuks instance with everything initialized, // but does not start it. -func NewGomuks(uiProvider ifc.UIProvider, configDir, dataDir, cacheDir, downloadDir string, isHeadless bool) *Gomuks { +func NewGomuks(uiProvider ifc.UIProvider, configDir, dataDir, cacheDir, downloadDir string) *Gomuks { gmx := &Gomuks{ stop: make(chan bool, 1), } gmx.config = config.NewConfig(configDir, dataDir, cacheDir, downloadDir) gmx.ui = uiProvider(gmx) - gmx.matrix = matrix.NewContainer(gmx, isHeadless) + gmx.matrix = matrix.NewContainer(gmx) gmx.config.LoadAll() gmx.ui.Init() @@ -103,24 +103,6 @@ func (gmx *Gomuks) internalStop(save bool) { if save { gmx.Save() } - if gmx.matrix.IsHeadless() { - fmt.Println("🚚📦📦 gomuks is ready to go 🚚📦📦") - fmt.Println("⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒") - fmt.Println() - fmt.Println("1.") - fmt.Println("copy your new `transfer` folder to the target") - fmt.Println("location (perhaps a shiny new beepberry) with") - fmt.Println("cp, rsync, or equivalent.") - fmt.Println() - fmt.Println("2.") - fmt.Println("set the GOMUKS_ROOT environment variable to match") - fmt.Println("the new location of the directory and edit the") - fmt.Println("config file to reflect the changes.") - fmt.Println() - fmt.Println("recommended reading:") - fmt.Println("https://docs.mau.fi/gomuks/faq.html#where-does-gomuks-store-data") - fmt.Println("https://beepy.sqfmi.com") - } debug.Print("Exiting process") os.Exit(0) } @@ -130,7 +112,7 @@ func (gmx *Gomuks) internalStop(save bool) { // If the tview app returns an error, it will be passed into panic(), which // will be recovered as specified in Recover(). func (gmx *Gomuks) Start() { - err := gmx.matrix.InitClient(true) + err := gmx.StartHeadless() if err != nil { if errors.Is(err, matrix.ErrServerOutdated) { _, _ = fmt.Fprintln(os.Stderr, strings.Replace(err.Error(), "homeserver", gmx.config.HS, 1)) @@ -161,6 +143,10 @@ func (gmx *Gomuks) Start() { } } +func (gmx *Gomuks) StartHeadless() error { + return gmx.matrix.InitClient(true) +} + // Matrix returns the MatrixContainer instance. func (gmx *Gomuks) Matrix() ifc.MatrixContainer { return gmx.matrix diff --git a/interface/matrix.go b/interface/matrix.go index 06d685f..d4b2baa 100644 --- a/interface/matrix.go +++ b/interface/matrix.go @@ -45,7 +45,6 @@ type MatrixContainer interface { Preferences() *config.UserPreferences InitClient(isStartup bool) error Initialized() bool - IsHeadless() bool Start() Stop() diff --git a/main.go b/main.go index 945f565..362263d 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,6 @@ package main import ( "fmt" "os" - "path/filepath" "runtime" "strings" "time" @@ -72,7 +71,6 @@ var wantVersion = flag.MakeFull("v", "version", "Show the version of gomuks", "f var clearCache = flag.MakeFull("c", "clear-cache", "Clear the cache directory instead of starting", "false").Bool() var skipVersionCheck = flag.MakeFull("s", "skip-version-check", "Skip the homeserver version checks at startup and login", "false").Bool() var clearData = flag.Make().LongKey("clear-all-data").Usage("Clear all data instead of starting").Default("false").Bool() -var logInForTransfer = flag.Make().LongKey("log-in-for-transfer").Usage("Log in and generate packaged data for transfer").Default("false").Bool() var wantHelp, _ = flag.MakeHelpFlag() func main() { @@ -91,27 +89,6 @@ func main() { fmt.Println(VersionString) return } - if *logInForTransfer { - if currentDir, err := os.Getwd(); err == nil { - pack := filepath.Join(currentDir, "transfer") - if _, err := os.Stat(pack); err == nil { - fmt.Println("with the --log-in-for-transfer flag, gomuks packs your data up into") - fmt.Println("the transfer/ directory so you can move it around easily. please make") - fmt.Println("sure there is nothing there already, and then run it again.") - os.Exit(1) - } - - keys := filepath.Join(currentDir, "keys.txt") - if _, err := os.Stat(keys); err != nil { - fmt.Println("with the --log-in-for-transfer flag, gomuks packs your data up so") - fmt.Println("you can move it around easily. please export your existing client") - fmt.Println("keys to the file keys.txt, and then run gomuks again.") - os.Exit(1) - } - - os.Setenv("GOMUKS_ROOT", pack) - } - } debugDir := os.Getenv("DEBUG_DIR") if len(debugDir) > 0 { @@ -155,7 +132,7 @@ func main() { debug.Print("Download directory:", downloadDir) matrix.SkipVersionCheck = *skipVersionCheck - gmx := initialize.NewGomuks(MainUIProvider, configDir, dataDir, cacheDir, downloadDir, *logInForTransfer) + gmx := initialize.NewGomuks(MainUIProvider, configDir, dataDir, cacheDir, downloadDir) if *clearCache { debug.Print("Clearing cache as requested by CLI flag") @@ -169,8 +146,6 @@ func main() { _ = os.RemoveAll(gmx.Config().Dir) fmt.Printf("Cleared cache at %s, data at %s and config at %s\n", gmx.Config().CacheDir, gmx.Config().DataDir, gmx.Config().Dir) return - } else if *logInForTransfer { - debug.Print("Initializing in headless mode as requested by CLI flag") } gmx.Start() diff --git a/matrix/matrix.go b/matrix/matrix.go index 9e5f29b..3a9affe 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -47,7 +47,6 @@ import ( "maunium.net/go/gomuks/lib/open" "maunium.net/go/gomuks/matrix/muksevt" "maunium.net/go/gomuks/matrix/rooms" - "maunium.net/go/gomuks/ui" ) // Container is a wrapper for a mautrix Client and some other stuff. @@ -61,20 +60,18 @@ type Container struct { ui ifc.GomuksUI config *config.Config history *HistoryManager - running, - headless bool - stop chan bool + running bool + stop chan bool typing int64 } // NewContainer creates a new Container for the given Gomuks instance. -func NewContainer(gmx ifc.Gomuks, isHeadless bool) *Container { +func NewContainer(gmx ifc.Gomuks) *Container { c := &Container{ - config: gmx.Config(), - ui: gmx.UI(), - gmx: gmx, - headless: isHeadless, + config: gmx.Config(), + ui: gmx.UI(), + gmx: gmx, } return c @@ -85,10 +82,6 @@ func (c *Container) Client() *mautrix.Client { return c.client } -func (c *Container) IsHeadless() bool { - return c.headless -} - type mxLogger struct{} func (log mxLogger) Debugfln(message string, args ...interface{}) { @@ -395,11 +388,6 @@ func (c *Container) OnLogin() { c.client.Store = c.config - if c.headless { - debug.Print("Importing keys...") - c.RunCommand("/import keys.txt") - } - debug.Print("Initializing syncer") c.syncer = NewGomuksSyncer(c.config.Rooms) if c.crypto != nil { @@ -438,12 +426,6 @@ func (c *Container) OnLogin() { c.syncer.Progress.Close() c.syncer.Progress = StubSyncingModal{} c.syncer.FirstDoneCallback = nil - if c.headless { - c.RunCommand("/cs fetch") - c.RunCommand("/cs self-sign") - c.config.Preferences.DisplayMode = config.DisplayModeModern - c.gmx.Stop(true) - } } } c.syncer.InitDoneCallback = func() { @@ -457,11 +439,9 @@ func (c *Container) OnLogin() { c.config.Rooms.ForceClean() debug.Print("Saving all data") c.config.SaveAll() - if !c.headless { - debug.Print("Adding rooms to UI") - c.ui.MainView().SetRooms(c.config.Rooms) - c.ui.Render() - } + debug.Print("Adding rooms to UI") + c.ui.MainView().SetRooms(c.config.Rooms) + c.ui.Render() // The initial sync can be a bit heavy, so we force run the GC here // after cleaning up rooms from memory above. debug.Print("Running GC") @@ -471,9 +451,7 @@ func (c *Container) OnLogin() { c.client.Syncer = c.syncer debug.Print("Setting existing rooms") - if !c.headless { - c.ui.MainView().SetRooms(c.config.Rooms) - } + c.ui.MainView().SetRooms(c.config.Rooms) debug.Print("OnLogin() done.") } @@ -513,14 +491,6 @@ func (c *Container) Start() { } } -func (c *Container) RunCommand(text string) { - if view, ok := c.ui.MainView().(*ui.MainView); ok { - if cmd := view.CmdProcessor().ParseCommand(nil, text); cmd != nil { - view.CmdProcessor().HandleCommand(cmd) - } - } -} - func (c *Container) HandlePreferences(source mautrix.EventSource, evt *event.Event) { if source&mautrix.EventSourceAccountData == 0 { return diff --git a/ui/view-roster.go b/ui/view-roster.go index a517389..f4b3dde 100644 --- a/ui/view-roster.go +++ b/ui/view-roster.go @@ -494,10 +494,6 @@ func (rstr *RosterView) Draw(screen mauview.Screen) { } func (rstr *RosterView) OnKeyEvent(event mauview.KeyEvent) bool { - if rstr.parent.matrix.IsHeadless() { - return false - } - kb := config.Keybind{ Key: event.Key(), Ch: event.Rune(),