2021-03-24 22:49:09 +01:00
extern crate rpassword ;
2021-08-18 00:12:49 +02:00
use clap ::{ crate_version , App , Arg , Shell } ;
2021-03-27 15:35:52 +01:00
use log ::* ;
2021-08-18 00:12:49 +02:00
use std ::str ::FromStr ;
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 ::{
2021-08-14 16:01:25 +02:00
steamapi , AccountLinkError , AccountLinker , Confirmation , FinalizeLinkError , LoginError ,
SteamGuardAccount , UserLogin ,
2021-08-01 14:43:18 +02:00
} ;
2021-03-24 22:49:09 +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 ;
2021-08-14 16:01:25 +02:00
mod demos ;
2021-08-19 22:54:18 +02:00
mod encryption ;
2021-08-14 16:01:25 +02:00
mod tui ;
2021-04-04 16:40:16 +02:00
2021-08-18 00:12:49 +02:00
fn cli ( ) -> App < 'static , 'static > {
App ::new ( " steamguard-cli " )
2021-03-27 14:31:38 +01:00
. version ( crate_version! ( ) )
. bin_name ( " steamguard " )
. author ( " dyc3 (Carson McManus) " )
. about ( " Generate Steam 2FA codes and confirm Steam trades from the command line. " )
. arg (
Arg ::with_name ( " username " )
. long ( " username " )
. short ( " u " )
2021-08-10 04:25:39 +02:00
. takes_value ( true )
2021-09-01 14:56:24 +02:00
. help ( " Select the account you want by steam username. Case-sensitive. By default, the first account in the manifest is selected. " )
2021-08-12 01:58:18 +02:00
. conflicts_with ( " all " )
2021-03-27 14:31:38 +01:00
)
. arg (
Arg ::with_name ( " all " )
. long ( " all " )
. short ( " a " )
2021-03-27 17:14:34 +01:00
. takes_value ( false )
2021-03-27 14:31:38 +01:00
. help ( " Select all accounts in the manifest. " )
2021-08-12 01:58:18 +02:00
. conflicts_with ( " username " )
2021-03-27 14:31:38 +01:00
)
. arg (
Arg ::with_name ( " mafiles-path " )
. long ( " mafiles-path " )
. short ( " m " )
. default_value ( " ~/maFiles " )
2021-08-13 00:06:18 +02:00
. help ( " Specify which folder your maFiles are in. This should be a path to a folder that contains manifest.json. " )
2021-03-27 14:31:38 +01:00
)
. arg (
Arg ::with_name ( " passkey " )
. long ( " passkey " )
. short ( " p " )
. help ( " Specify your encryption passkey. " )
2021-08-18 01:04:02 +02:00
. takes_value ( true )
2021-03-27 14:31:38 +01:00
)
2021-03-27 15:35:52 +01:00
. arg (
Arg ::with_name ( " verbosity " )
. short ( " v " )
. help ( " Log what is going on verbosely. " )
. takes_value ( false )
. multiple ( true )
)
2021-08-18 00:12:49 +02:00
. subcommand (
App ::new ( " completion " )
. about ( " Generate shell completions " )
. arg (
Arg ::with_name ( " shell " )
. long ( " shell " )
. takes_value ( true )
. possible_values ( & Shell ::variants ( ) )
)
)
2021-03-27 14:31:38 +01:00
. subcommand (
App ::new ( " trade " )
. about ( " Interactive interface for trade confirmations " )
. arg (
Arg ::with_name ( " accept-all " )
. short ( " a " )
. long ( " accept-all " )
2021-03-27 17:14:34 +01:00
. takes_value ( false )
2021-03-27 14:31:38 +01:00
. help ( " Accept all open trade confirmations. Does not open interactive interface. " )
)
)
2021-07-27 22:24:56 +02:00
. subcommand (
App ::new ( " setup " )
. about ( " Set up a new account with steamguard-cli " )
)
2021-08-14 01:04:03 +02:00
. subcommand (
App ::new ( " import " )
. about ( " Import an account with steamguard already set up " )
. arg (
Arg ::with_name ( " files " )
. required ( true )
. multiple ( true )
)
)
2021-08-12 01:39:29 +02:00
. subcommand (
App ::new ( " remove " )
. about ( " Remove the authenticator from an account. " )
)
2021-08-16 05:20:49 +02:00
. subcommand (
App ::new ( " encrypt " )
. about ( " Encrypt maFiles. " )
)
. subcommand (
App ::new ( " decrypt " )
. about ( " Decrypt maFiles. " )
)
2021-07-30 01:42:45 +02:00
. subcommand (
App ::new ( " debug " )
. arg (
Arg ::with_name ( " demo-conf-menu " )
. help ( " Show an example confirmation menu using dummy data. " )
. takes_value ( false )
)
)
2021-08-18 00:12:49 +02:00
}
fn main ( ) {
let matches = cli ( ) . get_matches ( ) ;
2021-03-27 14:31:38 +01:00
2021-08-08 18:54:46 +02:00
let verbosity = matches . occurrences_of ( " verbosity " ) as usize + 2 ;
stderrlog ::new ( )
. verbosity ( verbosity )
. 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
2021-08-08 18:54:46 +02:00
if let Some ( demo_matches ) = matches . subcommand_matches ( " debug " ) {
if demo_matches . is_present ( " demo-conf-menu " ) {
2021-08-14 16:01:25 +02:00
demos ::demo_confirmation_menu ( ) ;
2021-08-08 18:54:46 +02:00
}
return ;
}
2021-08-18 00:12:49 +02:00
if let Some ( completion_matches ) = matches . subcommand_matches ( " completion " ) {
cli ( ) . gen_completions_to (
" steamguard " ,
Shell ::from_str ( completion_matches . value_of ( " shell " ) . unwrap ( ) ) . unwrap ( ) ,
& mut std ::io ::stdout ( ) ,
) ;
return ;
}
2021-07-30 01:42:45 +02:00
2021-08-13 00:36:03 +02:00
let mafiles_dir = if matches . occurrences_of ( " mafiles-path " ) > 0 {
matches . value_of ( " mafiles-path " ) . unwrap ( ) . into ( )
} 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! " ) ;
return ;
}
_ = > { }
2021-08-08 18:54:46 +02:00
}
2021-08-13 00:06:18 +02:00
std ::fs ::create_dir_all ( mafiles_dir ) . expect ( " failed to create directory " ) ;
manifest = accountmanager ::Manifest ::new ( path . as_path ( ) ) ;
2021-08-30 02:17:17 +02:00
manifest . save ( & None ) . expect ( " Failed to save manifest " ) ;
2021-08-13 00:54:38 +02:00
} else {
2021-08-13 00:06:18 +02:00
match accountmanager ::Manifest ::load ( path . as_path ( ) ) {
Ok ( m ) = > {
manifest = m ;
}
Err ( e ) = > {
error! ( " Could not load manifest: {} " , e ) ;
return ;
}
2021-08-08 18:54:46 +02:00
}
}
2021-03-27 15:35:52 +01:00
2021-08-17 03:13:58 +02:00
let mut passkey : Option < String > = matches . value_of ( " passkey " ) . map ( | s | s . into ( ) ) ;
2021-08-16 05:20:49 +02:00
2021-08-17 03:13:58 +02:00
loop {
match manifest . load_accounts ( & passkey ) {
Ok ( _ ) = > break ,
Err (
accountmanager ::ManifestAccountLoadError ::MissingPasskey
2021-08-20 16:01:23 +02:00
| accountmanager ::ManifestAccountLoadError ::IncorrectPasskey ,
2021-08-17 03:13:58 +02:00
) = > {
2021-08-18 01:20:57 +02:00
if passkey . is_some ( ) {
error! ( " Incorrect passkey " ) ;
}
2021-08-17 03:13:58 +02:00
passkey = rpassword ::prompt_password_stdout ( " Enter encryption passkey: " ) . ok ( ) ;
}
Err ( e ) = > {
error! ( " Could not load accounts: {} " , e ) ;
return ;
}
}
}
2021-07-27 22:24:56 +02:00
2021-08-08 18:54:46 +02:00
if matches . is_present ( " setup " ) {
2021-08-09 00:32:50 +02:00
println! ( " Log in to the account that you want to link to steamguard-cli " ) ;
2021-09-06 22:51:44 +02:00
print! ( " Username: " ) ;
let username = tui ::prompt ( ) ;
if manifest . account_exists ( & username ) {
error! ( " Account {} already exists in manifest, remove it first " , username ) ;
}
let session = do_login_raw ( username ) . expect ( " Failed to log in. Account has not been linked. " ) ;
2021-08-09 00:32:50 +02:00
let mut linker = AccountLinker ::new ( session ) ;
let account : SteamGuardAccount ;
loop {
match linker . link ( ) {
Ok ( a ) = > {
account = a ;
break ;
}
2021-08-10 01:09:48 +02:00
Err ( AccountLinkError ::MustRemovePhoneNumber ) = > {
println! ( " There is already a phone number on this account, please remove it and try again. " ) ;
return ;
}
Err ( AccountLinkError ::MustProvidePhoneNumber ) = > {
2021-08-10 03:41:20 +02:00
println! ( " Enter your phone number in the following format: +1 123-456-7890 " ) ;
print! ( " Phone number: " ) ;
2021-08-14 16:01:25 +02:00
linker . phone_number = tui ::prompt ( ) . replace ( & [ '(' , ')' , '-' ] [ .. ] , " " ) ;
2021-08-10 01:09:48 +02:00
}
Err ( AccountLinkError ::AuthenticatorPresent ) = > {
println! ( " An authenticator is already present on this account. " ) ;
return ;
}
Err ( AccountLinkError ::MustConfirmEmail ) = > {
println! ( " Check your email and click the link. " ) ;
2021-08-14 16:01:25 +02:00
tui ::pause ( ) ;
2021-08-10 01:09:48 +02:00
}
2021-08-09 00:32:50 +02:00
Err ( err ) = > {
error! (
" Failed to link authenticator. Account has not been linked. {} " ,
err
) ;
return ;
}
}
}
manifest . add_account ( account ) ;
2021-08-17 03:13:58 +02:00
match manifest . save ( & passkey ) {
2021-08-09 00:32:50 +02:00
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 {:?} " ,
2021-08-09 06:09:34 +02:00
manifest . accounts . last ( ) . unwrap ( ) . lock ( ) . unwrap ( )
2021-08-09 00:32:50 +02:00
) ;
return ;
}
}
2021-08-09 06:09:34 +02:00
let mut account = manifest
. accounts
. last ( )
. as_ref ( )
. unwrap ( )
. clone ( )
. lock ( )
. unwrap ( ) ;
2021-08-25 15:06:42 +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 ) ;
tui ::pause ( ) ;
2021-08-09 00:32:50 +02:00
debug! ( " attempting link finalization " ) ;
print! ( " Enter SMS code: " ) ;
2021-08-14 16:01:25 +02:00
let sms_code = tui ::prompt ( ) ;
2021-08-09 06:09:34 +02:00
let mut tries = 0 ;
loop {
2021-08-10 00:44:42 +02:00
match linker . finalize ( & mut account , sms_code . clone ( ) ) {
2021-08-09 06:09:34 +02:00
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 " ) ;
2021-08-25 15:07:58 +02:00
return ;
2021-08-09 06:09:34 +02:00
}
2021-08-10 00:44:42 +02:00
}
2021-08-09 06:09:34 +02:00
Err ( err ) = > {
error! ( " Failed to finalize: {} " , err ) ;
2021-08-25 15:07:58 +02:00
return ;
2021-08-09 06:09:34 +02:00
}
}
}
2021-08-09 00:32:50 +02:00
2021-08-11 03:10:04 +02:00
println! ( " Authenticator finalized. " ) ;
2021-08-17 03:13:58 +02:00
match manifest . save ( & None ) {
2021-08-11 03:10:04 +02:00
Ok ( _ ) = > { }
Err ( err ) = > {
2021-08-11 04:45:17 +02:00
println! (
" Failed to save manifest, but we were able to save it before. {} " ,
err
) ;
2021-08-11 03:10:04 +02:00
return ;
}
}
2021-08-25 15:09:02 +02:00
println! (
" Authenticator has been finalized. Please actually write down your revocation code: {} " ,
account . revocation_code
) ;
2021-08-25 15:06:42 +02:00
2021-08-14 01:04:03 +02:00
return ;
} else if let Some ( import_matches ) = matches . subcommand_matches ( " import " ) {
for file_path in import_matches . values_of ( " files " ) . unwrap ( ) {
match manifest . import_account ( file_path . into ( ) ) {
Ok ( _ ) = > {
info! ( " Imported account: {} " , file_path ) ;
}
Err ( err ) = > {
error! ( " Failed to import account: {} {} " , file_path , err ) ;
}
}
}
2021-08-17 03:13:58 +02:00
manifest . save ( & passkey ) . expect ( " Failed to save manifest. " ) ;
2021-08-16 05:20:49 +02:00
return ;
} else if matches . is_present ( " encrypt " ) {
2021-08-18 01:20:57 +02:00
if passkey . is_none ( ) {
loop {
passkey = rpassword ::prompt_password_stdout ( " Enter encryption passkey: " ) . ok ( ) ;
2021-08-19 18:54:52 +02:00
let passkey_confirm =
rpassword ::prompt_password_stdout ( " Confirm encryption passkey: " ) . ok ( ) ;
2021-08-18 01:20:57 +02:00
if passkey = = passkey_confirm {
break ;
}
error! ( " Passkeys do not match, try again. " ) ;
}
}
2021-08-16 05:20:49 +02:00
for entry in & mut manifest . entries {
entry . encryption = Some ( accountmanager ::EntryEncryptionParams ::generate ( ) ) ;
}
2021-08-17 03:13:58 +02:00
manifest . save ( & passkey ) . expect ( " Failed to save manifest. " ) ;
2021-08-08 18:54:46 +02:00
return ;
2021-08-18 00:54:16 +02:00
} else if matches . is_present ( " decrypt " ) {
for entry in & mut manifest . entries {
entry . encryption = None ;
}
manifest . save ( & passkey ) . expect ( " Failed to save manifest. " ) ;
return ;
2021-08-08 18:54:46 +02:00
}
2021-07-27 22:24:56 +02:00
2021-08-08 18:54:46 +02:00
let mut selected_accounts : Vec < Arc < Mutex < SteamGuardAccount > > > = vec! [ ] ;
if matches . is_present ( " all " ) {
// manifest.accounts.iter().map(|a| selected_accounts.push(a.b));
for account in & manifest . accounts {
selected_accounts . push ( account . clone ( ) ) ;
}
} else {
for account in & manifest . accounts {
if ! matches . is_present ( " username " ) {
selected_accounts . push ( account . clone ( ) ) ;
break ;
}
if matches . value_of ( " username " ) . unwrap ( ) = = account . lock ( ) . unwrap ( ) . account_name {
selected_accounts . push ( account . clone ( ) ) ;
break ;
}
}
}
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
2021-08-08 18:54:46 +02:00
if let Some ( trade_matches ) = matches . subcommand_matches ( " trade " ) {
info! ( " trade " ) ;
for a in selected_accounts . iter_mut ( ) {
let mut account = a . lock ( ) . unwrap ( ) ;
2021-04-04 20:07:06 +02:00
2021-08-08 18:54:46 +02:00
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 " ) ;
2021-08-14 17:25:53 +02:00
do_login ( & mut account ) . expect ( " Failed to log in " ) ;
2021-08-08 18:54:46 +02:00
}
}
}
2021-07-29 15:08:06 +02:00
2021-08-08 18:54:46 +02:00
if trade_matches . is_present ( " accept-all " ) {
info! ( " accepting all confirmations " ) ;
for conf in & confirmations {
let result = account . accept_confirmation ( conf ) ;
debug! ( " accept confirmation result: {:?} " , result ) ;
}
} else {
if termion ::is_tty ( & stdout ( ) ) {
2021-08-14 16:01:25 +02:00
let ( accept , deny ) = tui ::prompt_confirmation_menu ( confirmations ) ;
2021-08-08 18:54:46 +02:00
for conf in & accept {
let result = account . accept_confirmation ( conf ) ;
debug! ( " accept confirmation result: {:?} " , result ) ;
}
for conf in & deny {
let result = account . deny_confirmation ( conf ) ;
debug! ( " deny confirmation result: {:?} " , result ) ;
}
} else {
warn! ( " not a tty, not showing menu " ) ;
for conf in & confirmations {
println! ( " {} " , conf . description ( ) ) ;
}
}
}
}
2021-08-01 17:20:57 +02:00
2021-08-17 03:13:58 +02:00
manifest . save ( & passkey ) . expect ( " Failed to save manifest " ) ;
2021-08-12 01:39:29 +02:00
} else if let Some ( _ ) = matches . subcommand_matches ( " remove " ) {
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 ( " , " )
) ;
2021-08-14 17:10:21 +02:00
match tui ::prompt_char ( " Do you want to continue? " , " yN " ) {
'y' = > { }
2021-08-12 01:39:29 +02:00
_ = > {
2021-08-14 17:10:21 +02:00
info! ( " Aborting! " ) ;
2021-08-12 01:39:29 +02:00
return ;
}
}
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
) ;
2021-09-06 22:14:57 +02:00
match tui ::prompt_char ( " Would you like to remove it from the manifest anyway? " , " yN " ) {
'y' = > {
successful . push ( account . account_name . clone ( ) ) ;
}
_ = > { }
}
2021-08-12 01:39:29 +02:00
}
}
Err ( err ) = > {
2021-09-06 22:14:57 +02:00
error! (
2021-08-12 01:39:29 +02:00
" Unexpected error when removing authenticator from {}: {} " ,
account . account_name , err
) ;
}
}
}
for account_name in successful {
manifest . remove_account ( account_name ) ;
}
2021-08-17 03:13:58 +02:00
manifest . save ( & passkey ) . expect ( " Failed to save manifest. " ) ;
2021-08-08 18:54:46 +02:00
} else {
let server_time = steamapi ::get_server_time ( ) ;
2021-09-06 21:41:22 +02:00
debug! ( " Time used to generate codes: {} " , server_time ) ;
2021-08-08 18:54:46 +02:00
for account in selected_accounts {
2021-09-06 22:05:26 +02:00
info! (
" Generating code for {} " ,
account . lock ( ) . unwrap ( ) . account_name
) ;
2021-08-08 18:54:46 +02:00
trace! ( " {:?} " , account ) ;
let code = account . lock ( ) . unwrap ( ) . generate_code ( server_time ) ;
println! ( " {} " , code ) ;
}
}
2021-03-22 02:21:29 +01:00
}
2021-04-04 16:40:16 +02:00
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 " ) ;
}
2021-08-14 17:24:15 +02:00
account . session = Some ( do_login_impl (
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 ) = > {
let server_time = steamapi ::get_server_time ( ) ;
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 ( ) ;
}