2021-03-24 22:49:09 +01:00
extern crate rpassword ;
2022-06-19 20:44:47 +02:00
use clap ::{ IntoApp , Parser } ;
2021-03-27 15:35:52 +01:00
use log ::* ;
2022-06-21 02:43:53 +02:00
use std ::time ::{ SystemTime , UNIX_EPOCH } ;
2021-08-01 14:43:18 +02:00
use std ::{
2021-08-14 16:01:25 +02:00
io ::{ stdout , Write } ,
2021-08-08 18:54:46 +02:00
path ::Path ,
sync ::{ Arc , Mutex } ,
2021-08-01 14:43:18 +02:00
} ;
2021-08-08 18:34:06 +02:00
use steamguard ::{
2022-06-19 20:44:18 +02:00
steamapi , AccountLinkError , AccountLinker , Confirmation , ExposeSecret , FinalizeLinkError ,
LoginError , SteamGuardAccount , UserLogin ,
2021-08-01 14:43:18 +02:00
} ;
2021-03-24 22:49:09 +01:00
2022-06-19 19:01:25 +02:00
use crate ::accountmanager ::ManifestAccountLoadError ;
2022-02-22 15:17:04 +01:00
2021-04-04 16:40:16 +02:00
#[ macro_use ]
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 ;
2021-08-13 00:06:18 +02:00
extern crate dirs ;
2021-08-20 15:37:55 +02:00
#[ cfg(test) ]
extern crate proptest ;
2021-08-15 17:52:54 +02:00
extern crate ring ;
2021-08-01 14:43:18 +02:00
mod accountmanager ;
2022-06-19 16:48:18 +02:00
mod cli ;
2021-08-14 16:01:25 +02:00
mod demos ;
2021-08-19 22:54:18 +02:00
mod encryption ;
2022-02-21 17:57:19 +01:00
mod errors ;
2022-02-04 19:08:23 +01:00
pub ( crate ) mod tui ;
2021-04-04 16:40:16 +02:00
2021-08-18 00:12:49 +02:00
fn main ( ) {
2022-02-21 17:57:19 +01:00
std ::process ::exit ( match run ( ) {
Ok ( _ ) = > 0 ,
Err ( e ) = > {
error! ( " {:?} " , e ) ;
255
}
} ) ;
}
fn run ( ) -> anyhow ::Result < ( ) > {
2022-06-19 19:00:36 +02:00
let args = cli ::Args ::parse ( ) ;
info! ( " {:?} " , args ) ;
2021-03-27 14:31:38 +01:00
2021-08-08 18:54:46 +02:00
stderrlog ::new ( )
2022-06-19 19:00:36 +02:00
. verbosity ( args . verbosity as usize )
2021-08-08 18:54:46 +02:00
. module ( module_path! ( ) )
2021-08-10 01:48:18 +02:00
. module ( " steamguard " )
2021-08-08 18:54:46 +02:00
. init ( )
. unwrap ( ) ;
2021-03-22 02:21:29 +01:00
2022-06-19 19:00:36 +02:00
match args . sub {
2022-06-19 17:14:35 +02:00
Some ( cli ::Subcommands ::Debug ( args ) ) = > {
2022-06-19 18:16:20 +02:00
return do_subcmd_debug ( args ) ;
2022-06-19 20:44:47 +02:00
}
2022-06-19 18:37:40 +02:00
Some ( cli ::Subcommands ::Completion ( args ) ) = > {
return do_subcmd_completion ( args ) ;
2022-06-19 20:44:47 +02:00
}
_ = > { }
2022-06-19 16:48:18 +02:00
} ;
2021-07-30 01:42:45 +02:00
2022-06-19 19:00:36 +02:00
let mafiles_dir = if let Some ( mafiles_path ) = & args . mafiles_path {
2022-06-19 18:57:54 +02:00
mafiles_path . clone ( )
2021-08-13 00:36:03 +02:00
} else {
get_mafiles_dir ( )
} ;
2021-08-13 00:06:18 +02:00
info! ( " reading manifest from {} " , mafiles_dir ) ;
let path = Path ::new ( & mafiles_dir ) . join ( " manifest.json " ) ;
2021-08-08 18:54:46 +02:00
let mut manifest : accountmanager ::Manifest ;
2021-08-13 00:06:18 +02:00
if ! path . exists ( ) {
error! ( " Did not find manifest in {} " , mafiles_dir ) ;
2021-08-14 17:10:21 +02:00
match tui ::prompt_char (
format! ( " Would you like to create a manifest in {} ? " , mafiles_dir ) . as_str ( ) ,
" Yn " ,
) {
'n' = > {
2021-08-13 00:06:18 +02:00
info! ( " Aborting! " ) ;
2022-02-21 17:57:19 +01:00
return Err ( errors ::UserError ::Aborted . into ( ) ) ;
2021-08-13 00:06:18 +02:00
}
_ = > { }
2021-08-08 18:54:46 +02:00
}
2022-02-21 17:57:19 +01:00
std ::fs ::create_dir_all ( mafiles_dir ) ? ;
2021-08-13 00:06:18 +02:00
manifest = accountmanager ::Manifest ::new ( path . as_path ( ) ) ;
2022-02-18 20:55:10 +01:00
manifest . save ( ) ? ;
2021-08-13 00:54:38 +02:00
} else {
2022-02-21 17:57:19 +01:00
manifest = accountmanager ::Manifest ::load ( path . as_path ( ) ) ? ;
2021-08-08 18:54:46 +02:00
}
2021-03-27 15:35:52 +01:00
2022-06-19 19:00:36 +02:00
let mut passkey : Option < String > = args . passkey . clone ( ) ;
2022-02-18 20:55:10 +01:00
manifest . submit_passkey ( passkey ) ;
2021-08-16 05:20:49 +02:00
2021-08-17 03:13:58 +02:00
loop {
2022-02-22 15:19:56 +01:00
match manifest . auto_upgrade ( ) {
Ok ( upgraded ) = > {
if upgraded {
info! ( " Manifest auto-upgraded " ) ;
manifest . save ( ) ? ;
2022-06-13 04:01:35 +02:00
} 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
) = > {
2022-02-18 20:55:10 +01:00
if manifest . has_passkey ( ) {
2021-08-18 01:20:57 +02:00
error! ( " Incorrect passkey " ) ;
}
2021-08-17 03:13:58 +02:00
passkey = rpassword ::prompt_password_stdout ( " Enter encryption passkey: " ) . ok ( ) ;
2022-02-18 20:55:10 +01:00
manifest . submit_passkey ( passkey ) ;
2021-08-17 03:13:58 +02:00
}
Err ( e ) = > {
error! ( " Could not load accounts: {} " , e ) ;
2022-02-21 17:57:19 +01:00
return Err ( e . into ( ) ) ;
2021-08-17 03:13:58 +02:00
}
}
}
2021-07-27 22:24:56 +02:00
2022-06-19 19:00:36 +02:00
match args . sub {
2022-06-19 17:14:35 +02:00
Some ( cli ::Subcommands ::Setup ( args ) ) = > {
return do_subcmd_setup ( args , & mut manifest ) ;
2022-06-19 20:44:47 +02:00
}
2022-06-19 17:43:37 +02:00
Some ( cli ::Subcommands ::Import ( args ) ) = > {
return do_subcmd_import ( args , & mut manifest ) ;
2022-06-19 20:44:47 +02:00
}
2022-06-19 17:40:20 +02:00
Some ( cli ::Subcommands ::Encrypt ( args ) ) = > {
return do_subcmd_encrypt ( args , & mut manifest ) ;
2022-06-19 20:44:47 +02:00
}
2022-06-19 17:40:20 +02:00
Some ( cli ::Subcommands ::Decrypt ( args ) ) = > {
return do_subcmd_decrypt ( args , & mut manifest ) ;
2022-06-19 20:44:47 +02:00
}
_ = > { }
2022-06-13 03:46:39 +02:00
}
2022-06-19 19:01:25 +02:00
let selected_accounts : Vec < Arc < Mutex < SteamGuardAccount > > > ;
2022-06-13 04:01:35 +02:00
loop {
2022-06-19 19:00:36 +02:00
match get_selected_accounts ( & args , & mut manifest ) {
2022-06-13 04:01:35 +02:00
Ok ( accounts ) = > {
selected_accounts = accounts ;
break ;
}
Err (
accountmanager ::ManifestAccountLoadError ::MissingPasskey
| accountmanager ::ManifestAccountLoadError ::IncorrectPasskey ,
) = > {
if manifest . has_passkey ( ) {
error! ( " Incorrect passkey " ) ;
}
passkey = rpassword ::prompt_password_stdout ( " Enter encryption passkey: " ) . ok ( ) ;
manifest . 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
2022-06-22 01:31:17 +02:00
match args . sub . unwrap_or ( cli ::Subcommands ::Code ( args . code ) ) {
cli ::Subcommands ::Trade ( args ) = > {
2022-06-19 17:54:16 +02:00
return do_subcmd_trade ( args , & mut manifest , selected_accounts ) ;
2022-06-19 20:44:47 +02:00
}
2022-06-22 01:31:17 +02:00
cli ::Subcommands ::Remove ( args ) = > {
2022-06-19 17:54:16 +02:00
return do_subcmd_remove ( args , & mut manifest , selected_accounts ) ;
2022-06-19 20:44:47 +02:00
}
2022-06-22 01:31:17 +02:00
cli ::Subcommands ::Code ( args ) = > {
return do_subcmd_code ( args , selected_accounts ) ;
}
s = > {
2022-06-19 16:56:52 +02:00
error! ( " Unknown subcommand: {:?} " , s ) ;
2022-06-19 18:45:33 +02:00
return Err ( errors ::UserError ::UnknownSubcommand . into ( ) ) ;
2022-06-19 20:44:47 +02:00
}
2021-08-08 18:54:46 +02:00
}
2021-03-22 02:21:29 +01:00
}
2021-04-04 16:40:16 +02:00
2022-02-22 15:38:41 +01:00
fn get_selected_accounts (
2022-06-19 18:57:54 +02:00
args : & cli ::Args ,
2022-02-22 15:38:41 +01:00
manifest : & mut accountmanager ::Manifest ,
) -> anyhow ::Result < Vec < Arc < Mutex < SteamGuardAccount > > > , ManifestAccountLoadError > {
2022-02-22 15:17:04 +01:00
let mut selected_accounts : Vec < Arc < Mutex < SteamGuardAccount > > > = vec! [ ] ;
2022-06-19 18:57:54 +02:00
if args . all {
2022-02-22 15:17:04 +01:00
manifest . load_accounts ( ) ? ;
for entry in & manifest . entries {
selected_accounts . push ( manifest . get_account ( & entry . account_name ) . unwrap ( ) . clone ( ) ) ;
}
} else {
2022-06-19 18:57:54 +02:00
let entry = if let Some ( username ) = & args . username {
manifest . get_entry ( & username )
2022-02-22 15:17:04 +01:00
} else {
2022-02-22 15:38:41 +01:00
manifest
. entries
. first ( )
. ok_or ( ManifestAccountLoadError ::MissingManifestEntry )
2022-02-22 15:17:04 +01:00
} ? ;
let account_name = entry . account_name . clone ( ) ;
let account = manifest . get_or_load_account ( & account_name ) ? ;
selected_accounts . push ( account ) ;
}
return Ok ( selected_accounts ) ;
}
2021-08-14 17:24:15 +02:00
fn do_login ( account : & mut SteamGuardAccount ) -> anyhow ::Result < ( ) > {
2021-08-08 18:54:46 +02:00
if account . account_name . len ( ) > 0 {
println! ( " Username: {} " , account . account_name ) ;
} else {
print! ( " Username: " ) ;
2021-08-14 16:01:25 +02:00
account . account_name = tui ::prompt ( ) ;
2021-08-08 18:54:46 +02:00
}
let _ = std ::io ::stdout ( ) . flush ( ) ;
let password = rpassword ::prompt_password_stdout ( " Password: " ) . unwrap ( ) ;
if password . len ( ) > 0 {
debug! ( " password is present " ) ;
} else {
debug! ( " password is empty " ) ;
}
2022-06-19 20:42:07 +02:00
account . set_session ( do_login_impl (
2021-08-14 17:24:15 +02:00
account . account_name . clone ( ) ,
password ,
Some ( account ) ,
) ? ) ;
return Ok ( ( ) ) ;
2021-07-27 22:24:56 +02:00
}
2021-07-30 01:42:45 +02:00
2021-09-06 22:51:44 +02:00
fn do_login_raw ( username : String ) -> anyhow ::Result < steamapi ::Session > {
2021-08-09 00:32:50 +02:00
let _ = std ::io ::stdout ( ) . flush ( ) ;
let password = rpassword ::prompt_password_stdout ( " Password: " ) . unwrap ( ) ;
if password . len ( ) > 0 {
debug! ( " password is present " ) ;
} else {
debug! ( " password is empty " ) ;
}
2021-08-14 17:24:15 +02:00
return do_login_impl ( username , password , None ) ;
}
fn do_login_impl (
username : String ,
password : String ,
account : Option < & SteamGuardAccount > ,
) -> anyhow ::Result < steamapi ::Session > {
2021-08-09 00:32:50 +02:00
// TODO: reprompt if password is empty
let mut login = UserLogin ::new ( username , password ) ;
let mut loops = 0 ;
loop {
match login . login ( ) {
Ok ( s ) = > {
return Ok ( s ) ;
}
2021-08-14 17:24:15 +02:00
Err ( LoginError ::Need2FA ) = > match account {
Some ( a ) = > {
2022-06-21 02:05:00 +02:00
let server_time = steamapi ::get_server_time ( ) ? . server_time ;
2021-08-14 17:24:15 +02:00
login . twofactor_code = a . generate_code ( server_time ) ;
}
None = > {
print! ( " Enter 2fa code: " ) ;
login . twofactor_code = tui ::prompt ( ) ;
}
} ,
2021-08-09 00:32:50 +02:00
Err ( LoginError ::NeedCaptcha { captcha_gid } ) = > {
debug! ( " need captcha to log in " ) ;
2021-08-14 16:01:25 +02:00
login . captcha_text = tui ::prompt_captcha_text ( & captcha_gid ) ;
2021-08-09 00:32:50 +02:00
}
Err ( LoginError ::NeedEmail ) = > {
println! ( " You should have received an email with a code. " ) ;
print! ( " Enter code: " ) ;
2021-08-14 16:01:25 +02:00
login . email_code = tui ::prompt ( ) ;
2021-08-09 00:32:50 +02:00
}
Err ( r ) = > {
error! ( " Fatal login result: {:?} " , r ) ;
bail! ( r ) ;
}
}
loops + = 1 ;
if loops > 2 {
error! ( " Too many loops. Aborting login process, to avoid getting rate limited. " ) ;
bail! ( " Too many loops. Login process aborted to avoid getting rate limited. " ) ;
}
}
}
2021-08-13 00:36:03 +02:00
fn get_mafiles_dir ( ) -> String {
2021-08-13 00:06:18 +02:00
let paths = vec! [
Path ::new ( & dirs ::config_dir ( ) . unwrap ( ) ) . join ( " steamguard-cli/maFiles " ) ,
Path ::new ( & dirs ::home_dir ( ) . 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 ( ) ;
}
2022-06-13 03:46:39 +02:00
2022-06-19 21:23:55 +02:00
fn load_accounts_with_prompts ( manifest : & mut accountmanager ::Manifest ) -> anyhow ::Result < ( ) > {
loop {
match manifest . load_accounts ( ) {
Ok ( _ ) = > return Ok ( ( ) ) ,
Err (
accountmanager ::ManifestAccountLoadError ::MissingPasskey
| accountmanager ::ManifestAccountLoadError ::IncorrectPasskey ,
) = > {
if manifest . has_passkey ( ) {
error! ( " Incorrect passkey " ) ;
}
let passkey = rpassword ::prompt_password_stdout ( " Enter encryption passkey: " ) . ok ( ) ;
manifest . submit_passkey ( passkey ) ;
}
Err ( e ) = > {
error! ( " Could not load accounts: {} " , e ) ;
return Err ( e . into ( ) ) ;
}
}
}
}
2022-06-19 18:16:20 +02:00
fn do_subcmd_debug ( args : cli ::ArgsDebug ) -> anyhow ::Result < ( ) > {
2022-06-25 16:57:37 +02:00
if args . demo_prompt {
demos ::demo_prompt ( ) ;
}
if args . demo_prompt_char {
demos ::demo_prompt_char ( ) ;
}
2022-06-19 18:16:20 +02:00
if args . demo_conf_menu {
demos ::demo_confirmation_menu ( ) ;
}
return Ok ( ( ) ) ;
}
2022-06-19 18:37:40 +02:00
fn do_subcmd_completion ( args : cli ::ArgsCompletions ) -> Result < ( ) , anyhow ::Error > {
let mut app = cli ::Args ::command_for_update ( ) ;
clap_complete ::generate ( args . shell , & mut app , " steamguard " , & mut std ::io ::stdout ( ) ) ;
return Ok ( ( ) ) ;
}
2022-06-19 20:44:47 +02:00
fn do_subcmd_setup (
args : cli ::ArgsSetup ,
manifest : & mut accountmanager ::Manifest ,
) -> anyhow ::Result < ( ) > {
2022-06-19 16:38:59 +02:00
println! ( " Log in to the account that you want to link to steamguard-cli " ) ;
print! ( " Username: " ) ;
2022-06-19 16:48:18 +02:00
let username = if args . username . is_some ( ) {
let u = args . username . unwrap ( ) ;
println! ( " {} " , u ) ;
u
} else {
tui ::prompt ( )
} ;
2022-06-19 16:38:59 +02:00
let account_name = username . clone ( ) ;
if manifest . account_exists ( & username ) {
bail! (
" Account {} already exists in manifest, remove it first " ,
username
) ;
}
2022-06-19 20:44:47 +02:00
let session = do_login_raw ( username ) . expect ( " Failed to log in. Account has not been linked. " ) ;
2022-06-13 03:46:39 +02:00
2022-06-19 16:38:59 +02:00
let mut linker = AccountLinker ::new ( session ) ;
let account : SteamGuardAccount ;
loop {
match linker . link ( ) {
Ok ( a ) = > {
account = a ;
break ;
}
Err ( AccountLinkError ::MustRemovePhoneNumber ) = > {
println! ( " There is already a phone number on this account, please remove it and try again. " ) ;
bail! ( " There is already a phone number on this account, please remove it and try again. " ) ;
}
Err ( AccountLinkError ::MustProvidePhoneNumber ) = > {
println! ( " Enter your phone number in the following format: +1 123-456-7890 " ) ;
print! ( " Phone number: " ) ;
linker . phone_number = tui ::prompt ( ) . replace ( & [ '(' , ')' , '-' ] [ .. ] , " " ) ;
}
Err ( AccountLinkError ::AuthenticatorPresent ) = > {
println! ( " An authenticator is already present on this account. " ) ;
bail! ( " An authenticator is already present on this account. " ) ;
}
Err ( AccountLinkError ::MustConfirmEmail ) = > {
println! ( " Check your email and click the link. " ) ;
tui ::pause ( ) ;
}
Err ( err ) = > {
error! (
" Failed to link authenticator. Account has not been linked. {} " ,
err
) ;
return Err ( err . into ( ) ) ;
}
}
}
manifest . add_account ( account ) ;
match manifest . save ( ) {
Ok ( _ ) = > { }
Err ( err ) = > {
error! ( " Aborting the account linking process because we failed to save the manifest. This is really bad. Here is the error: {} " , err ) ;
println! (
" Just in case, here is the account info. Save it somewhere just in case! \n {:?} " ,
manifest . get_account ( & account_name ) . unwrap ( ) . lock ( ) . unwrap ( )
) ;
return Err ( err . into ( ) ) ;
}
}
let account_arc = manifest . get_account ( & account_name ) . unwrap ( ) ;
let mut account = account_arc . lock ( ) . unwrap ( ) ;
2022-06-19 20:09:08 +02:00
println! ( " Authenticator has not yet been linked. Before continuing with finalization, please take the time to write down your revocation code: {} " , account . revocation_code . expose_secret ( ) ) ;
2022-06-19 16:38:59 +02:00
tui ::pause ( ) ;
debug! ( " attempting link finalization " ) ;
print! ( " Enter SMS code: " ) ;
let sms_code = tui ::prompt ( ) ;
let mut tries = 0 ;
loop {
match linker . finalize ( & mut account , sms_code . clone ( ) ) {
Ok ( _ ) = > break ,
Err ( FinalizeLinkError ::WantMore ) = > {
debug! ( " steam wants more 2fa codes (tries: {}) " , tries ) ;
tries + = 1 ;
if tries > = 30 {
error! ( " Failed to finalize: unable to generate valid 2fa codes " ) ;
bail! ( " Failed to finalize: unable to generate valid 2fa codes " ) ;
}
}
Err ( err ) = > {
error! ( " Failed to finalize: {} " , err ) ;
return Err ( err . into ( ) ) ;
}
}
}
println! ( " Authenticator finalized. " ) ;
match manifest . save ( ) {
Ok ( _ ) = > { }
Err ( err ) = > {
println! (
" Failed to save manifest, but we were able to save it before. {} " ,
err
) ;
return Err ( err ) ;
}
}
println! (
" Authenticator has been finalized. Please actually write down your revocation code: {} " ,
2022-06-19 20:09:08 +02:00
account . revocation_code . expose_secret ( )
2022-06-19 16:38:59 +02:00
) ;
return Ok ( ( ) ) ;
2022-06-13 03:46:39 +02:00
}
2022-06-19 17:40:20 +02:00
2022-06-19 20:44:47 +02:00
fn do_subcmd_import (
args : cli ::ArgsImport ,
manifest : & mut accountmanager ::Manifest ,
) -> anyhow ::Result < ( ) > {
2022-06-19 17:43:37 +02:00
for file_path in args . files {
match manifest . import_account ( & file_path ) {
Ok ( _ ) = > {
info! ( " Imported account: {} " , & file_path ) ;
}
Err ( err ) = > {
bail! ( " Failed to import account: {} {} " , & file_path , err ) ;
}
}
}
manifest . save ( ) ? ;
return Ok ( ( ) ) ;
}
2022-06-19 20:44:47 +02:00
fn do_subcmd_trade (
args : cli ::ArgsTrade ,
manifest : & mut accountmanager ::Manifest ,
mut selected_accounts : Vec < Arc < Mutex < SteamGuardAccount > > > ,
) -> anyhow ::Result < ( ) > {
2022-06-19 17:54:16 +02:00
for a in selected_accounts . iter_mut ( ) {
let mut account = a . lock ( ) . unwrap ( ) ;
info! ( " Checking for trade confirmations " ) ;
let confirmations : Vec < Confirmation > ;
loop {
match account . get_trade_confirmations ( ) {
Ok ( confs ) = > {
confirmations = confs ;
break ;
}
Err ( _ ) = > {
info! ( " failed to get trade confirmations, asking user to log in " ) ;
do_login ( & mut account ) ? ;
}
}
}
let mut any_failed = false ;
if args . accept_all {
info! ( " accepting all confirmations " ) ;
for conf in & confirmations {
let result = account . accept_confirmation ( conf ) ;
if result . is_err ( ) {
warn! ( " accept confirmation result: {:?} " , result ) ;
any_failed = true ;
if args . fail_fast {
return result ;
}
} else {
debug! ( " accept confirmation result: {:?} " , result ) ;
}
}
} else {
if termion ::is_tty ( & stdout ( ) ) {
2022-06-25 16:14:34 +02:00
let ( accept , deny ) = tui ::prompt_confirmation_menu ( confirmations ) ? ;
2022-06-19 17:54:16 +02:00
for conf in & accept {
let result = account . accept_confirmation ( conf ) ;
if result . is_err ( ) {
warn! ( " accept confirmation result: {:?} " , result ) ;
any_failed = true ;
if args . fail_fast {
return result ;
}
} else {
debug! ( " accept confirmation result: {:?} " , result ) ;
}
}
for conf in & deny {
let result = account . deny_confirmation ( conf ) ;
debug! ( " deny confirmation result: {:?} " , result ) ;
if result . is_err ( ) {
warn! ( " deny confirmation result: {:?} " , result ) ;
any_failed = true ;
if args . fail_fast {
return result ;
}
} else {
debug! ( " deny confirmation result: {:?} " , result ) ;
}
}
} else {
warn! ( " not a tty, not showing menu " ) ;
for conf in & confirmations {
println! ( " {} " , conf . description ( ) ) ;
}
}
}
if any_failed {
error! ( " Failed to respond to some confirmations. " ) ;
}
}
manifest . save ( ) ? ;
return Ok ( ( ) ) ;
}
2022-06-19 20:44:47 +02:00
fn do_subcmd_remove (
_args : cli ::ArgsRemove ,
manifest : & mut accountmanager ::Manifest ,
selected_accounts : Vec < Arc < Mutex < SteamGuardAccount > > > ,
) -> anyhow ::Result < ( ) > {
2022-06-19 17:54:16 +02:00
println! (
" This will remove the mobile authenticator from {} accounts: {} " ,
selected_accounts . len ( ) ,
selected_accounts
. iter ( )
. map ( | a | a . lock ( ) . unwrap ( ) . account_name . clone ( ) )
. collect ::< Vec < String > > ( )
. join ( " , " )
) ;
match tui ::prompt_char ( " Do you want to continue? " , " yN " ) {
'y' = > { }
_ = > {
info! ( " Aborting! " ) ;
return Err ( errors ::UserError ::Aborted . into ( ) ) ;
}
}
let mut successful = vec! [ ] ;
for a in selected_accounts {
let account = a . lock ( ) . unwrap ( ) ;
match account . remove_authenticator ( None ) {
Ok ( success ) = > {
if success {
println! ( " Removed authenticator from {} " , account . account_name ) ;
successful . push ( account . account_name . clone ( ) ) ;
} else {
println! (
" Failed to remove authenticator from {} " ,
account . account_name
) ;
match tui ::prompt_char (
" Would you like to remove it from the manifest anyway? " ,
" yN " ,
) {
'y' = > {
successful . push ( account . account_name . clone ( ) ) ;
}
_ = > { }
}
}
}
Err ( err ) = > {
error! (
" Unexpected error when removing authenticator from {}: {} " ,
account . account_name , err
) ;
}
}
}
for account_name in successful {
manifest . remove_account ( account_name ) ;
}
manifest . save ( ) ? ;
return Ok ( ( ) ) ;
}
2022-06-19 20:44:47 +02:00
fn do_subcmd_encrypt (
_args : cli ::ArgsEncrypt ,
manifest : & mut accountmanager ::Manifest ,
) -> anyhow ::Result < ( ) > {
2022-06-19 17:40:20 +02:00
if ! manifest . has_passkey ( ) {
let mut passkey ;
loop {
passkey = rpassword ::prompt_password_stdout ( " Enter encryption passkey: " ) . ok ( ) ;
let passkey_confirm =
rpassword ::prompt_password_stdout ( " Confirm encryption passkey: " ) . ok ( ) ;
if passkey = = passkey_confirm {
break ;
}
error! ( " Passkeys do not match, try again. " ) ;
}
manifest . submit_passkey ( passkey ) ;
}
manifest . load_accounts ( ) ? ;
for entry in & mut manifest . entries {
entry . encryption = Some ( accountmanager ::EntryEncryptionParams ::generate ( ) ) ;
}
manifest . save ( ) ? ;
return Ok ( ( ) ) ;
}
2022-06-19 20:44:47 +02:00
fn do_subcmd_decrypt (
_args : cli ::ArgsDecrypt ,
manifest : & mut accountmanager ::Manifest ,
) -> anyhow ::Result < ( ) > {
2022-06-19 21:23:55 +02:00
load_accounts_with_prompts ( manifest ) ? ;
2022-06-19 17:40:20 +02:00
for entry in & mut manifest . entries {
entry . encryption = None ;
}
manifest . submit_passkey ( None ) ;
manifest . save ( ) ? ;
return Ok ( ( ) ) ;
}
2022-06-19 17:54:42 +02:00
2022-06-21 02:43:53 +02:00
fn do_subcmd_code (
args : cli ::ArgsCode ,
selected_accounts : Vec < Arc < Mutex < SteamGuardAccount > > > ,
) -> anyhow ::Result < ( ) > {
let server_time = if args . offline {
SystemTime ::now ( ) . duration_since ( UNIX_EPOCH ) ? . as_secs ( )
} else {
steamapi ::get_server_time ( ) ? . server_time
} ;
2022-06-19 17:54:42 +02:00
debug! ( " Time used to generate codes: {} " , server_time ) ;
for account in selected_accounts {
info! (
" Generating code for {} " ,
account . lock ( ) . unwrap ( ) . account_name
) ;
trace! ( " {:?} " , account ) ;
let code = account . lock ( ) . unwrap ( ) . generate_code ( server_time ) ;
println! ( " {} " , code ) ;
}
return Ok ( ( ) ) ;
}