steamguard-cli/src/main.rs

323 lines
8.5 KiB
Rust
Raw Normal View History

extern crate rpassword;
use clap::Parser;
2021-03-27 15:35:52 +01:00
use log::*;
use secrecy::SecretString;
2021-08-01 14:43:18 +02:00
use std::{
2021-08-08 18:54:46 +02:00
path::Path,
sync::{Arc, Mutex},
2021-08-01 14:43:18 +02:00
};
2023-07-02 16:54:01 +02:00
use steamguard::transport::WebApiTransport;
use steamguard::SteamGuardAccount;
use crate::accountmanager::migrate::{load_and_migrate, MigrationError};
pub use crate::accountmanager::{AccountManager, ManifestAccountLoadError, ManifestLoadError};
use crate::commands::{CommandType, Subcommands};
2023-07-02 16:54:01 +02:00
pub use login::*;
extern crate lazy_static;
2021-08-01 17:20:57 +02:00
#[macro_use]
extern crate anyhow;
2021-08-15 17:52:54 +02:00
extern crate base64;
extern crate dirs;
2021-08-20 15:37:55 +02:00
#[cfg(test)]
extern crate proptest;
2021-08-01 14:43:18 +02:00
mod accountmanager;
mod commands;
mod debug;
2021-08-19 22:54:18 +02:00
mod encryption;
mod errors;
2023-07-02 16:54:01 +02:00
mod login;
mod secret_string;
pub(crate) mod tui;
2023-07-02 16:54:01 +02:00
2023-06-25 15:24:53 +02:00
#[cfg(feature = "updater")]
mod updater;
2021-08-18 00:12:49 +02:00
fn main() {
2023-06-25 15:24:53 +02:00
let args = commands::Args::parse();
stderrlog::new()
.verbosity(args.global.verbosity as usize)
.module(module_path!())
.module("steamguard")
.init()
.unwrap();
debug!("{:?}", args);
#[cfg(feature = "updater")]
let should_do_update_check = !args.global.no_update_check;
let exit_code = match run(args) {
Ok(_) => 0,
Err(e) => {
error!("{:?}", e);
255
}
2023-06-25 15:24:53 +02:00
};
2023-06-25 15:24:53 +02:00
#[cfg(feature = "updater")]
if should_do_update_check {
match updater::check_for_update() {
Ok(Some(version)) => {
eprintln!();
info!(
"steamguard-cli {} is available. Download it here: https://github.com/dyc3/steamguard-cli/releases",
2023-06-25 15:24:53 +02:00
version
);
}
Ok(None) => {
debug!("No update available");
}
Err(e) => {
warn!("Failed to check for updates: {}", e);
}
}
}
2021-03-27 14:31:38 +01:00
2023-06-25 15:24:53 +02:00
std::process::exit(exit_code);
}
2023-06-25 15:24:53 +02:00
fn run(args: commands::Args) -> anyhow::Result<()> {
let globalargs = args.global;
2021-03-22 02:21:29 +01:00
let cmd: CommandType<WebApiTransport> = match args.sub.unwrap_or(Subcommands::Code(args.code)) {
Subcommands::Debug(args) => CommandType::Const(Box::new(args)),
Subcommands::Completion(args) => CommandType::Const(Box::new(args)),
Subcommands::Setup(args) => CommandType::Manifest(Box::new(args)),
Subcommands::Import(args) => CommandType::Manifest(Box::new(args)),
Subcommands::Encrypt(args) => CommandType::Manifest(Box::new(args)),
Subcommands::Decrypt(args) => CommandType::Manifest(Box::new(args)),
Subcommands::Confirm(args) => CommandType::Account(Box::new(args)),
Subcommands::Remove(args) => CommandType::Account(Box::new(args)),
Subcommands::Code(args) => CommandType::Account(Box::new(args)),
#[cfg(feature = "qr")]
Subcommands::Qr(args) => CommandType::Account(Box::new(args)),
Subcommands::QrLogin(args) => CommandType::Account(Box::new(args)),
2022-06-19 16:48:18 +02:00
};
2021-07-30 01:42:45 +02:00
if let CommandType::Const(cmd) = cmd {
return cmd.execute();
}
let mafiles_dir = if let Some(mafiles_path) = &globalargs.mafiles_path {
mafiles_path.clone()
} else {
get_mafiles_dir()
};
info!("reading manifest from {}", mafiles_dir);
let path = Path::new(&mafiles_dir).join("manifest.json");
let mut passkey = globalargs.passkey.clone();
let mut manager: accountmanager::AccountManager;
if !path.exists() {
error!("Did not find manifest in {}", mafiles_dir);
if tui::prompt_char(
2021-08-14 17:10:21 +02:00
format!("Would you like to create a manifest in {} ?", mafiles_dir).as_str(),
"Yn",
) == 'n'
{
info!("Aborting!");
return Err(errors::UserError::Aborted.into());
2021-08-08 18:54:46 +02:00
}
std::fs::create_dir_all(mafiles_dir)?;
manager = accountmanager::AccountManager::new(path.as_path());
manager.save()?;
2021-08-13 00:54:38 +02:00
} else {
manager = match accountmanager::AccountManager::load(path.as_path()) {
Ok(m) => m,
Err(ManifestLoadError::MigrationNeeded) => {
info!("Migrating manifest");
let manifest;
let accounts;
loop {
match load_and_migrate(path.as_path(), passkey.as_ref()) {
Ok((m, a)) => {
manifest = m;
accounts = a;
break;
}
Err(MigrationError::MissingPasskey { keyring_id }) => {
if passkey.is_some() {
error!("Incorrect passkey");
}
#[cfg(feature = "keyring")]
if let Some(keyring_id) = keyring_id {
if passkey.is_none() {
info!("Attempting to load encryption passkey from keyring");
let entry = encryption::init_keyring(keyring_id)?;
let raw = entry.get_password()?;
passkey = Some(SecretString::new(raw));
continue;
}
}
passkey = Some(tui::prompt_passkey()?);
}
Err(e) => {
error!("Failed to migrate manifest: {}", e);
return Err(e.into());
}
}
}
let mut manager = AccountManager::from_manifest(manifest, mafiles_dir);
manager.register_accounts(accounts);
manager.submit_passkey(passkey.clone());
manager.save()?;
manager
}
Err(err) => {
error!("Failed to load manifest: {}", err);
return Err(err.into());
}
}
2021-08-08 18:54:46 +02:00
}
2021-03-27 15:35:52 +01:00
#[cfg(feature = "keyring")]
if let Some(keyring_id) = manager.keyring_id() {
if passkey.is_none() {
info!("Attempting to load encryption passkey from keyring");
match encryption::try_passkey_from_keyring(keyring_id.clone()) {
Ok(k) => passkey = k,
Err(e) => {
warn!("Failed to load encryption passkey from keyring: {}", e);
}
}
}
}
manager.submit_passkey(passkey);
2021-08-16 05:20:49 +02:00
2021-08-17 03:13:58 +02:00
loop {
match manager.auto_upgrade() {
2022-02-22 15:19:56 +01:00
Ok(upgraded) => {
if upgraded {
info!("Manifest auto-upgraded");
manager.save()?;
} else {
debug!("Manifest is up to date");
2022-02-22 15:19:56 +01:00
}
break;
2022-02-22 15:38:41 +01:00
}
2021-08-17 03:13:58 +02:00
Err(
accountmanager::ManifestAccountLoadError::MissingPasskey
2021-08-20 16:01:23 +02:00
| accountmanager::ManifestAccountLoadError::IncorrectPasskey,
2021-08-17 03:13:58 +02:00
) => {
if manager.has_passkey() {
error!("Incorrect passkey");
}
passkey = Some(tui::prompt_passkey()?);
manager.submit_passkey(passkey);
2021-08-17 03:13:58 +02:00
}
Err(e) => {
error!("Could not load accounts: {}", e);
return Err(e.into());
2021-08-17 03:13:58 +02:00
}
}
}
2021-07-27 22:24:56 +02:00
let mut http_client = reqwest::blocking::Client::builder();
if let Some(proxy) = &globalargs.http_proxy {
let mut proxy = reqwest::Proxy::all(proxy)?;
if let Some(proxy_creds) = &globalargs.proxy_credentials {
let mut creds = proxy_creds.splitn(2, ':');
proxy = proxy.basic_auth(creds.next().unwrap(), creds.next().unwrap());
}
http_client = http_client.proxy(proxy);
}
if globalargs.danger_accept_invalid_certs {
http_client = http_client.danger_accept_invalid_certs(true);
}
let http_client = http_client.build()?;
let transport = WebApiTransport::new(http_client);
if let CommandType::Manifest(cmd) = cmd {
cmd.execute(transport, &mut manager, &globalargs)?;
return Ok(());
2022-06-13 03:46:39 +02:00
}
2022-06-19 19:01:25 +02:00
let selected_accounts: Vec<Arc<Mutex<SteamGuardAccount>>>;
loop {
match get_selected_accounts(&globalargs, &mut manager) {
Ok(accounts) => {
selected_accounts = accounts;
break;
}
Err(
accountmanager::ManifestAccountLoadError::MissingPasskey { .. }
| accountmanager::ManifestAccountLoadError::IncorrectPasskey,
) => {
if manager.has_passkey() {
error!("Incorrect passkey");
}
passkey = Some(tui::prompt_passkey()?);
manager.submit_passkey(passkey);
}
Err(e) => {
error!("Could not load accounts: {}", e);
return Err(e.into());
}
}
}
2021-03-27 17:14:34 +01:00
2021-08-08 18:54:46 +02:00
debug!(
"selected accounts: {:?}",
selected_accounts
.iter()
.map(|a| a.lock().unwrap().account_name.clone())
.collect::<Vec<String>>()
);
2021-03-27 17:14:34 +01:00
if let CommandType::Account(cmd) = cmd {
return cmd.execute(transport, &mut manager, selected_accounts, &globalargs);
2021-08-08 18:54:46 +02:00
}
Ok(())
2021-03-22 02:21:29 +01:00
}
2022-02-22 15:38:41 +01:00
fn get_selected_accounts(
args: &commands::GlobalArgs,
manifest: &mut accountmanager::AccountManager,
2022-02-22 15:38:41 +01:00
) -> anyhow::Result<Vec<Arc<Mutex<SteamGuardAccount>>>, ManifestAccountLoadError> {
let mut selected_accounts: Vec<Arc<Mutex<SteamGuardAccount>>> = vec![];
if args.all {
manifest.load_accounts()?;
for entry in manifest.iter() {
selected_accounts.push(manifest.get_account(&entry.account_name).unwrap().clone());
}
} else {
let entry = if let Some(username) = &args.username {
manifest.get_entry(username)
} else {
2022-02-22 15:38:41 +01:00
manifest
.iter()
.next()
2022-02-22 15:38:41 +01:00
.ok_or(ManifestAccountLoadError::MissingManifestEntry)
}?;
let account_name = entry.account_name.clone();
let account = manifest.get_or_load_account(&account_name)?;
selected_accounts.push(account);
}
Ok(selected_accounts)
}
fn get_mafiles_dir() -> String {
let mut paths = vec![
Path::new(&dirs::config_dir().unwrap()).join("steamguard-cli/maFiles"),
Path::new(&dirs::home_dir().unwrap()).join("maFiles"),
];
if let Ok(current_exe) = std::env::current_exe() {
paths.push(current_exe.parent().unwrap().join("maFiles"));
}
for path in &paths {
if path.join("manifest.json").is_file() {
return path.to_str().unwrap().into();
}
}
return paths[0].to_str().unwrap().into();
}