#[macro_use]
extern crate json;
mod chomp;
use std::error;
use std::ffi::OsStr;
use std::fmt::{self, Display, Formatter};
use std::io::{self, Write};
use std::process::{Command, Stdio};
use std::str::{self, Utf8Error};
use std::string;
use json::JsonValue;
use Error::*;
use chomp::Chomp;
macro_rules! validate_path {
($path:expr) => {
if $path.trim().is_empty() {
return Err(InvalidInput);
}
};
}
const MSG_SIZE: usize = 4;
#[derive(Debug)]
pub enum Error {
FromUtf8(string::FromUtf8Error),
Json(json::Error),
Io(io::Error),
InvalidInput,
InvalidOutput,
Pass(String),
Utf8(Utf8Error),
}
impl From<json::Error> for Error {
fn from(error: json::Error) -> Self {
Json(error)
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Io(error)
}
}
impl From<Utf8Error> for Error {
fn from(error: Utf8Error) -> Self {
Utf8(error)
}
}
impl From<string::FromUtf8Error> for Error {
fn from(error: string::FromUtf8Error) -> Self {
FromUtf8(error)
}
}
impl Display for Error {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
let string =
match *self {
FromUtf8(ref error) => error.to_string(),
Json(ref error) => error.to_string(),
Io(ref error) => error.to_string(),
InvalidInput => "invalid input".to_string(),
InvalidOutput => "invalid output".to_string(),
Pass(ref error) => error.clone(),
Utf8(ref error) => error.to_string(),
};
write!(formatter, "{}", string)
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
FromUtf8(ref error) => error.description(),
Json(ref error) => error.description(),
Io(ref error) => error.description(),
InvalidInput => "invalid input",
InvalidOutput => "invalid output",
Pass(ref error) => error,
Utf8(ref error) => error.description(),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct PasswordStore;
impl PasswordStore {
pub fn get(path: &str) -> Result<(String, String)> {
validate_path!(path);
let mut response = gopass_ipc(object! {
"type" => "getLogin",
"entry" => path
})?;
if let (Some(mut username), Some(password)) = (response["username"].take_string(),
response["password"].take_string())
{
if username.is_empty() {
username = path.to_string();
}
Ok((username, password))
}
else {
Err(InvalidOutput)
}
}
pub fn get_usernames(path: &str) -> Result<Vec<String>> {
validate_path!(path);
let response = gopass_ipc(object! {
"type" => "query",
"query" => path
})?;
let mut result = vec![];
match response {
JsonValue::Array(usernames) => {
for username in usernames {
let username =
match username.as_str() {
Some(username) => username,
None => return Err(InvalidOutput),
};
let index = username.rfind('/').map(|index| index + 1).unwrap_or(0);
result.push(username[index..].to_string());
}
},
_ => return Err(InvalidOutput),
}
Ok(result)
}
pub fn generate(path: &str, use_symbols: bool, length: i32) -> Result<()> {
validate_path!(path);
let response = gopass_ipc(object! {
"type" => "create",
"entry_name" => path,
"password" => "",
"generate" => true,
"length" => length,
"use_symbols" => use_symbols
})?;
if response["username"].as_str().is_none() {
return Err(InvalidOutput);
}
Ok(())
}
pub fn insert(path: &str, password: &str) -> Result<()> {
validate_path!(path);
let response = gopass_ipc(object! {
"type" => "create",
"entry_name" => path,
"password" => password
})?;
if let Some(inserted_password) = response["password"].as_str() {
if password != inserted_password {
return Err(InvalidOutput);
}
}
Ok(())
}
pub fn remove(path: &str) -> Result<()> {
validate_path!(path);
exec_pass("rm", &["-f", path])?;
Ok(())
}
}
fn exec_pass<S: AsRef<OsStr>>(command: &str, args: &[S]) -> Result<String> {
let mut process = Command::new("gopass");
if !command.trim().is_empty() {
process.arg(command);
}
let child = process.args(args)
.stderr(Stdio::piped())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let output = child.wait_with_output()?;
let mut stderr = String::from_utf8(output.stderr)?;
if !stderr.is_empty() {
stderr.chomp();
Err(Pass(stderr))
}
else {
Ok(String::from_utf8(output.stdout)?)
}
}
fn gopass_ipc(json_query: JsonValue) -> Result<JsonValue> {
let mut process = Command::new("gopass-jsonapi");
let mut child = process.args(&["listen"])
.stderr(Stdio::piped())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
if let Some(stdin) = child.stdin.as_mut() {
let json_string = json_query.dump();
stdin.write_all(&i32_to_bytes(json_string.len() as i32))?;
write!(stdin, "{}", json_string)?;
}
let output = child.wait_with_output()?;
let mut stderr = String::from_utf8(output.stderr)?;
if !stderr.is_empty() {
stderr.chomp();
Err(Pass(stderr))
}
else {
json::parse(str::from_utf8(&output.stdout[MSG_SIZE..])?) .map_err(Into::into)
}
}
fn i32_to_bytes(num: i32) -> Vec<u8> {
vec![
(num & 0xFF) as u8,
((num >> 8) & 0xFF) as u8,
((num >> 16) & 0xFF) as u8,
((num >> 24) & 0xFF) as u8,
]
}