You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
4.3 KiB
178 lines
4.3 KiB
// Copyright (c) 2022 Lukasz Janyst <lukasz@jany.st> |
|
// |
|
// This file is part of Peroxide. |
|
// |
|
// Peroxide is free software: you can redistribute it and/or modify |
|
// it under the terms of the GNU General Public License as published by |
|
// the Free Software Foundation, either version 3 of the License, or |
|
// (at your option) any later version. |
|
// |
|
// Peroxide is distributed in the hope that it will be useful, |
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
// GNU General Public License for more details. |
|
// |
|
// You should have received a copy of the GNU General Public License |
|
// along with Peroxide. If not, see <https://www.gnu.org/licenses/>. |
|
|
|
package main |
|
|
|
import ( |
|
"bufio" |
|
"context" |
|
"fmt" |
|
"os" |
|
|
|
"github.com/mattn/go-isatty" |
|
"golang.org/x/crypto/ssh/terminal" |
|
|
|
"github.com/ljanyst/peroxide/pkg/bridge" |
|
"github.com/ljanyst/peroxide/pkg/users" |
|
) |
|
|
|
func askPass(prompt string) ([]byte, error) { |
|
f := os.Stdin |
|
if !isatty.IsTerminal(f.Fd()) { |
|
// This can happen if stdin is used for piping data |
|
var err error |
|
if f, err = os.Open("/dev/tty"); err != nil { |
|
return nil, err |
|
} |
|
defer f.Close() |
|
} |
|
fmt.Fprintf(os.Stderr, "%v: ", prompt) |
|
b, err := terminal.ReadPassword(int(f.Fd())) |
|
if err == nil { |
|
fmt.Fprintf(os.Stderr, "\n") |
|
} |
|
return b, err |
|
} |
|
|
|
func listAccounts(b *bridge.Bridge) { |
|
spacing := "%3d: %-20s %-20s %-15s " |
|
for idx, user := range b.Users.GetUsers() { |
|
connected := "disconnected" |
|
if user.IsConnected() { |
|
connected = "connected" |
|
} |
|
|
|
fmt.Printf(spacing, idx, user.Username(), user.GetPrimaryAddress(), connected) |
|
|
|
for _, address := range user.GetAddresses() { |
|
fmt.Printf("%-20s", address) |
|
} |
|
|
|
fmt.Println() |
|
} |
|
} |
|
|
|
func deleteAccount(b *bridge.Bridge, accountName string) error { |
|
if accountName == "" { |
|
return fmt.Errorf("Missing account name") |
|
} |
|
|
|
userArr := b.Users.GetUsers() |
|
if len(userArr) == 0 { |
|
return fmt.Errorf("No registered user accounts") |
|
} |
|
|
|
var user *users.User |
|
|
|
for _, u := range userArr { |
|
if u.Username() == accountName { |
|
user = u |
|
break |
|
} |
|
} |
|
|
|
if user == nil { |
|
return fmt.Errorf("Account %s not found", accountName) |
|
} |
|
|
|
if err := user.Logout(); err != nil { |
|
return fmt.Errorf("Logout of account %s failed: %s", accountName, err) |
|
} |
|
|
|
if err := b.Users.DeleteUser(user.ID(), true); err != nil { |
|
return fmt.Errorf("Deletion of account %s failed: %s", accountName, err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func loginAccount(b *bridge.Bridge, accountName string) error { |
|
if accountName == "" { |
|
return fmt.Errorf("Missing account name") |
|
} |
|
|
|
user, _ := b.Users.GetUser(accountName) |
|
if user != nil { |
|
mainKey, err := askPass("Main key") |
|
if err != nil { |
|
return fmt.Errorf("The main key is required to modify an existing user: %s", err) |
|
} |
|
|
|
if err := user.UnlockCredentials("main", string(mainKey)); err != nil { |
|
return fmt.Errorf("Unable to unlock credentials: %s", err) |
|
} |
|
|
|
if err := user.Logout(); err != nil { |
|
return fmt.Errorf("Unable to logout previous session: %s", err) |
|
} |
|
} |
|
|
|
password, err := askPass("Password") |
|
if err != nil { |
|
return fmt.Errorf("Unable to read password: %s", err) |
|
} |
|
|
|
if len(password) == 0 { |
|
return fmt.Errorf("Empty password") |
|
} |
|
|
|
fmt.Printf("Authenticating %s...\n", accountName) |
|
client, auth, err := b.Users.Login(accountName, password) |
|
if err != nil { |
|
return fmt.Errorf("Login of account %s failed: %s", accountName, err) |
|
} |
|
|
|
if auth.HasTwoFactor() { |
|
scanner := bufio.NewScanner(os.Stdin) |
|
fmt.Printf("2FA TOTP code: ") |
|
scanner.Scan() |
|
code := scanner.Text() |
|
|
|
if code == "" { |
|
return fmt.Errorf("Empty 2FA TOTP code") |
|
} |
|
|
|
err = client.Auth2FA(context.Background(), code) |
|
if err != nil { |
|
return fmt.Errorf("2FA of account %s failed: %s", accountName, err) |
|
} |
|
} |
|
|
|
mailboxPassword := password |
|
if auth.HasMailboxPassword() { |
|
mailboxPassword, err = askPass("Mailbox password: ") |
|
if err != nil { |
|
return fmt.Errorf("Unable to read mailbox password: %s", err) |
|
} |
|
} |
|
|
|
if len(mailboxPassword) == 0 { |
|
return fmt.Errorf("Empty mailbox password") |
|
} |
|
|
|
user, key, err := b.Users.FinishLogin(client, auth, mailboxPassword, "") |
|
if err != nil { |
|
return fmt.Errorf("Login of account %s failed: %s", accountName, err) |
|
} |
|
|
|
fmt.Printf("Account %s has been added successfully.\n", user.Username()) |
|
if len(key) != 0 { |
|
fmt.Printf("Main key: %s\n", key) |
|
} |
|
|
|
return nil |
|
}
|
|
|