use std::process::{ Command, Output, Stdio };
use std::io::{self, Write, BufRead, BufReader};
use std::io::ErrorKind;
use std::fs;
pub struct PinEntry;
static prompt_commands: &str = r#"SETDESC {}
GETPIN"#;
impl PinEntry {
pub fn pinentry_prompt(
tty_path: &str,
commands: Vec<(&str, Vec<&str>)>
) -> io::Result<String> {
let mut child = Command::new("pinentry")
.arg("--ttyname")
.arg(tty_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
{
let mut stdin = child.stdin.as_mut().unwrap();
for (template, values) in commands {
let line = match values.len() {
0 => template.to_string(),
1 => template.replace("{}", values[0]),
2 => template.replace("{}", values[0]).replacen("{}", values[1], 1),
3 => template.replace("{}", values[0])
.replacen("{}", values[1], 1)
.replacen("{}", values[2], 1),
_ => return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Too many format arguments (max 3 supported)",
)),
};
writeln!(stdin, "{}", line)?;
}
writeln!(stdin, "BYE")?;
}
let stdout = child.stdout.take().unwrap();
let reader = BufReader::new(stdout);
for line in reader.lines() {
let line = line?;
if line.starts_with("D ") {
return Ok(line[2..].to_string());
} else if line.starts_with("ERR") {
return Err(io::Error::new(io::ErrorKind::Other, line));
}
}
Err(io::Error::new(io::ErrorKind::Other, "No data received from pinentry"))
}
pub fn prompt(desc: &str) -> io::Result<String> {
let commands = vec![
("SETDESC {}", vec![desc]),
("SETPROMPT Password:", vec![]),
("GETPIN", vec![]),
];
PinEntry::pinentry_prompt("/dev/tty", commands)
}
pub fn prompt_repeat(desc: &str) -> io::Result<String> {
let commands = vec![
("SETDESC {}", vec![desc]),
("SETPROMPT New password:", vec![]),
("SETREPEATERROR does not match - try again", vec![]),
("SETREPEATOK Passwords match.", vec![]),
("SETREPEAT Repeat: ", vec![]),
("GETPIN", vec![]),
];
PinEntry::pinentry_prompt("/dev/tty", commands)
}
}