diff options
Diffstat (limited to 'rust/src')
-rw-r--r-- | rust/src/getpass.rs | 65 | ||||
-rw-r--r-- | rust/src/main.rs | 54 |
2 files changed, 115 insertions, 4 deletions
diff --git a/rust/src/getpass.rs b/rust/src/getpass.rs new file mode 100644 index 0000000..f40da0e --- /dev/null +++ b/rust/src/getpass.rs @@ -0,0 +1,65 @@ +extern crate libc; + +use std::io::{self, Write}; +use std::mem; +use std::str; + +fn error_checker(ret: i32) -> io::Result<()> { + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +fn tcgetattr(fd: i32) -> io::Result<libc::termios> { + unsafe { + let mut tios: libc::termios = mem::zeroed(); + error_checker(libc::tcgetattr(fd, &mut tios))?; + Ok(tios) + } +} + +fn tcsetattr(fd: i32, other_options: i32, tios: &libc::termios) -> io::Result<()> { + unsafe { + error_checker(libc::tcsetattr(fd, other_options, tios))?; + Ok(()) + } +} + +fn without_echo<T, F: FnOnce() -> io::Result<T>>(f: F) -> io::Result<T> { + let tios_old = tcgetattr(0)?; + + let mut tios_new = tios_old; + tios_new.c_lflag &= !libc::ECHO; + tios_new.c_lflag |= libc::ECHONL; + + match tcsetattr(0, libc::TCSANOW, &tios_new) { + Ok(()) => {}, + Err(e) => { + // Just blindly try to reset the terminal, in case something changed + let _ = tcsetattr(0, libc::TCSANOW, &tios_old); + return Err(e); + } + } + + let ret = f()?; + + tcsetattr(0, libc::TCSANOW, &tios_old)?; + + Ok(ret) +} + +pub fn get_pass(prompt: &str) -> io::Result<String> { + print!("{}", prompt); + io::stdout().flush()?; + + without_echo(|| { + let mut line = String::new(); + io::stdin().read_line(&mut line)?; + if line.ends_with('\n') { + line.pop(); + } + Ok(line) + }) +} diff --git a/rust/src/main.rs b/rust/src/main.rs index f7a2660..8c7e617 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::io::{self, Write}; use std::sync::{Arc, Mutex}; use std::convert::TryFrom; +use argparse::{ArgumentParser, Store, StoreOption}; use futures::channel::mpsc; use futures::stream::StreamExt; use once_cell::sync::Lazy; @@ -22,6 +23,7 @@ mod auth; mod bel; mod editor; mod error; +mod getpass; static DEBUG_FILE: Lazy<Mutex<Option<File>>> = Lazy::new(|| { Mutex::new( @@ -624,14 +626,58 @@ async fn pushchan_thread(mut chan: mpsc::Receiver<PushMessage>, app: Arc<App>) { } } +struct Options { + server: String, + username: Option<String>, +} + +impl Default for Options { + fn default() -> Self { + Self { server: String::from("127.0.0.1:29538"), username: None } + } +} + +fn parse_options() -> Options { + let mut options = Options::default(); + let defserver = options.server.clone(); + let server_descr = format!("Address (host:port) of tomsg server (default: {})", defserver); + + { + let mut ap = ArgumentParser::new(); + ap.set_description("Simple client for tomsg with auto-generated credentials."); + + ap.refer(&mut options.server) + .add_option(&["-s", "--server"], Store, &server_descr) + .metavar("ADDR"); + ap.refer(&mut options.username) + .add_option(&["-u", "--user"], StoreOption, + "Username to connect as (if given, will prompt for password on stdin) (default: current system username)") + .metavar("USER"); + + ap.parse_args_or_exit(); + } + options +} + async fn async_main() -> io::Result<()> { - let addr = ("127.0.0.1", 29538); - let (conn, pushchan) = Connection::connect(connection::Type::Plain, addr).await?; + let options = parse_options(); + + let (conn, pushchan) = Connection::connect(connection::Type::Plain, options.server).await?; let (user, pass) = auth::get_auth_info()?; - let user = Word::try_from(user.to_string()).unwrap(); - let pass = Line::try_from(pass.to_string()).unwrap(); + let (user, pass) = match options.username { + None => (Word::try_from(user.to_string()).unwrap() + ,Line::try_from(pass.to_string()).unwrap()), + Some(user) => { + let pass = getpass::get_pass(&format!("Password for user '{}': ", user))?; + if pass.len() == 0 { + println!("Cancelled."); + std::process::exit(1); + } + (Word::try_from(user).unwrap(), Line::try_from(pass).unwrap()) + } + }; send_command(&conn, Command::Register { username: user.clone(), password: pass.clone() }).await?; |