1#[macro_use]
23extern crate json;
24
25mod chomp;
26
27use std::error;
28use std::ffi::OsStr;
29use std::fmt::{self, Display, Formatter};
30use std::io::{self, Write};
31use std::process::{Command, Stdio};
32use std::str::{self, Utf8Error};
33use std::string;
34
35use json::JsonValue;
36
37use Error::*;
38use chomp::Chomp;
39
40macro_rules! validate_path {
41 ($path:expr) => {
42 if $path.trim().is_empty() {
43 return Err(InvalidInput);
44 }
45 };
46}
47
48const MSG_SIZE: usize = 4;
49
50#[derive(Debug)]
52pub enum Error {
53 FromUtf8(string::FromUtf8Error),
54 Json(json::Error),
55 Io(io::Error),
56 InvalidInput,
57 InvalidOutput,
58 Pass(String),
59 Utf8(Utf8Error),
60}
61
62impl From<json::Error> for Error {
63 fn from(error: json::Error) -> Self {
64 Json(error)
65 }
66}
67
68impl From<io::Error> for Error {
69 fn from(error: io::Error) -> Self {
70 Io(error)
71 }
72}
73
74impl From<Utf8Error> for Error {
75 fn from(error: Utf8Error) -> Self {
76 Utf8(error)
77 }
78}
79
80impl From<string::FromUtf8Error> for Error {
81 fn from(error: string::FromUtf8Error) -> Self {
82 FromUtf8(error)
83 }
84}
85
86impl Display for Error {
87 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
88 let string =
89 match *self {
90 FromUtf8(ref error) => error.to_string(),
91 Json(ref error) => error.to_string(),
92 Io(ref error) => error.to_string(),
93 InvalidInput => "invalid input".to_string(),
94 InvalidOutput => "invalid output".to_string(),
95 Pass(ref error) => error.clone(),
96 Utf8(ref error) => error.to_string(),
97 };
98 write!(formatter, "{}", string)
99 }
100}
101
102impl error::Error for Error {
103 fn description(&self) -> &str {
104 match *self {
105 FromUtf8(ref error) => error.description(),
106 Json(ref error) => error.description(),
107 Io(ref error) => error.description(),
108 InvalidInput => "invalid input",
109 InvalidOutput => "invalid output",
110 Pass(ref error) => error,
111 Utf8(ref error) => error.description(),
112 }
113 }
114}
115
116pub type Result<T> = std::result::Result<T, Error>;
118
119pub struct PasswordStore;
121
122impl PasswordStore {
123 pub fn get(path: &str) -> Result<(String, String)> {
125 validate_path!(path);
126 let mut response = gopass_ipc(object! {
127 "type" => "getLogin",
128 "entry" => path
129 })?;
130 if let (Some(mut username), Some(password)) = (response["username"].take_string(),
131 response["password"].take_string())
132 {
133 if username.is_empty() {
134 username = path.to_string();
135 }
136 Ok((username, password))
137 }
138 else {
139 Err(InvalidOutput)
140 }
141 }
142
143 pub fn get_usernames(path: &str) -> Result<Vec<String>> {
145 validate_path!(path);
146 let response = gopass_ipc(object! {
147 "type" => "query",
148 "query" => path
149 })?;
150 let mut result = vec![];
151 match response {
152 JsonValue::Array(usernames) => {
153 for username in usernames {
154 let username =
155 match username.as_str() {
156 Some(username) => username,
157 None => return Err(InvalidOutput),
158 };
159 let index = username.rfind('/').map(|index| index + 1).unwrap_or(0);
160 result.push(username[index..].to_string());
161 }
162 },
163 _ => return Err(InvalidOutput),
164 }
165 Ok(result)
166 }
167
168 pub fn generate(path: &str, use_symbols: bool, length: i32) -> Result<()> {
170 validate_path!(path);
171 let response = gopass_ipc(object! {
172 "type" => "create",
173 "entry_name" => path,
174 "password" => "",
175 "generate" => true,
176 "length" => length,
177 "use_symbols" => use_symbols
178 })?;
179 if response["username"].as_str().is_none() {
180 return Err(InvalidOutput);
181 }
182 Ok(())
183 }
184
185 pub fn insert(path: &str, password: &str) -> Result<()> {
187 validate_path!(path);
188 let response = gopass_ipc(object! {
189 "type" => "create",
190 "entry_name" => path,
191 "password" => password
192 })?;
193 if let Some(inserted_password) = response["password"].as_str() {
194 if password != inserted_password {
195 return Err(InvalidOutput);
196 }
197 }
198 Ok(())
199 }
200
201 pub fn remove(path: &str) -> Result<()> {
203 validate_path!(path);
204 exec_pass("rm", &["-f", path])?;
205 Ok(())
206 }
207}
208
209fn exec_pass<S: AsRef<OsStr>>(command: &str, args: &[S]) -> Result<String> {
211 let mut process = Command::new("gopass");
212 if !command.trim().is_empty() {
213 process.arg(command);
214 }
215 let child = process.args(args)
216 .stderr(Stdio::piped())
217 .stdin(Stdio::piped())
218 .stdout(Stdio::piped())
219 .spawn()?;
220 let output = child.wait_with_output()?;
221 let mut stderr = String::from_utf8(output.stderr)?;
222 if !stderr.is_empty() {
223 stderr.chomp();
224 Err(Pass(stderr))
225 }
226 else {
227 Ok(String::from_utf8(output.stdout)?)
228 }
229}
230
231fn gopass_ipc(json_query: JsonValue) -> Result<JsonValue> {
233 let mut process = Command::new("gopass-jsonapi");
234 let mut child = process.args(&["listen"])
235 .stderr(Stdio::piped())
236 .stdin(Stdio::piped())
237 .stdout(Stdio::piped())
238 .spawn()?;
239 if let Some(stdin) = child.stdin.as_mut() {
240 let json_string = json_query.dump();
241 stdin.write_all(&i32_to_bytes(json_string.len() as i32))?;
242 write!(stdin, "{}", json_string)?;
243 }
244 let output = child.wait_with_output()?;
245 let mut stderr = String::from_utf8(output.stderr)?;
246 if !stderr.is_empty() {
247 stderr.chomp();
248 Err(Pass(stderr))
249 }
250 else {
251 json::parse(str::from_utf8(&output.stdout[MSG_SIZE..])?) .map_err(Into::into)
253 }
254}
255
256fn i32_to_bytes(num: i32) -> Vec<u8> {
257 vec![
258 (num & 0xFF) as u8,
259 ((num >> 8) & 0xFF) as u8,
260 ((num >> 16) & 0xFF) as u8,
261 ((num >> 24) & 0xFF) as u8,
262 ]
263}