From 9d5bd561419e1145aea81ee820bc3e8405c4fd57 Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Tue, 13 Oct 2020 21:46:42 +0200 Subject: rust: Server and account command-line arguments --- rust/Cargo.toml | 2 ++ rust/src/getpass.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ rust/src/main.rs | 54 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 rust/src/getpass.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 869baeb..bd0e51c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -14,3 +14,5 @@ unicode-width = "^0.1.8" users = "0.10" dirs = "3.0" rand = "0.7" +argparse = "0.2" +libc = "^0.2.79" 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 { + 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 io::Result>(f: F) -> io::Result { + 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 { + 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>> = Lazy::new(|| { Mutex::new( @@ -624,14 +626,58 @@ async fn pushchan_thread(mut chan: mpsc::Receiver, app: Arc) { } } +struct Options { + server: String, + username: Option, +} + +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?; -- cgit v1.2.3-70-g09d2