2016-06-20 10:06:49 +02:00
|
|
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
|
|
|
// This file is part of Parity.
|
|
|
|
|
|
|
|
// Parity is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
|
|
|
// Parity is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2016-07-25 16:09:47 +02:00
|
|
|
use std::{fs, io};
|
2016-06-20 00:10:34 +02:00
|
|
|
use std::path::{PathBuf, Path};
|
|
|
|
use std::collections::HashMap;
|
2016-07-25 10:45:45 +02:00
|
|
|
use time;
|
2016-06-20 00:10:34 +02:00
|
|
|
use ethkey::Address;
|
2016-07-25 16:09:47 +02:00
|
|
|
use {json, SafeAccount, Error};
|
2016-06-20 00:10:34 +02:00
|
|
|
use super::KeyDirectory;
|
|
|
|
|
2016-08-10 16:42:15 +02:00
|
|
|
const IGNORED_FILES: &'static [&'static str] = &["thumbs.db"];
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
#[cfg(not(windows))]
|
|
|
|
fn restrict_permissions_to_owner(file_path: &Path) -> Result<(), i32> {
|
2016-07-25 16:09:47 +02:00
|
|
|
use std::ffi;
|
|
|
|
use libc;
|
2016-06-20 00:10:34 +02:00
|
|
|
let cstr = ffi::CString::new(file_path.to_str().unwrap()).unwrap();
|
|
|
|
match unsafe { libc::chmod(cstr.as_ptr(), libc::S_IWUSR | libc::S_IRUSR) } {
|
|
|
|
0 => Ok(()),
|
|
|
|
x => Err(x),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
2016-07-25 16:09:47 +02:00
|
|
|
fn restrict_permissions_to_owner(_file_path: &Path) -> Result<(), i32> {
|
2016-06-20 00:10:34 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct DiskDirectory {
|
|
|
|
path: PathBuf,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DiskDirectory {
|
|
|
|
pub fn create<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
|
|
|
|
try!(fs::create_dir_all(&path));
|
|
|
|
Ok(Self::at(path))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn at<P>(path: P) -> Self where P: AsRef<Path> {
|
|
|
|
DiskDirectory {
|
|
|
|
path: path.as_ref().to_path_buf(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// all accounts found in keys directory
|
|
|
|
fn files(&self) -> Result<HashMap<PathBuf, SafeAccount>, Error> {
|
|
|
|
// it's not done using one iterator cause
|
|
|
|
// there is an issue with rustc and it takes tooo much time to compile
|
|
|
|
let paths = try!(fs::read_dir(&self.path))
|
|
|
|
.flat_map(Result::ok)
|
|
|
|
.filter(|entry| {
|
|
|
|
let metadata = entry.metadata();
|
2016-08-10 16:42:15 +02:00
|
|
|
let file_name = entry.file_name();
|
|
|
|
let name = file_name.to_str().unwrap();
|
|
|
|
// filter directories
|
|
|
|
metadata.is_ok() && !metadata.unwrap().is_dir() &&
|
|
|
|
// hidden files
|
|
|
|
!name.starts_with(".") &&
|
|
|
|
// other ignored files
|
|
|
|
!IGNORED_FILES.contains(&name)
|
2016-06-20 00:10:34 +02:00
|
|
|
})
|
|
|
|
.map(|entry| entry.path())
|
|
|
|
.collect::<Vec<PathBuf>>();
|
|
|
|
|
|
|
|
let files: Result<Vec<_>, _> = paths.iter()
|
|
|
|
.map(fs::File::open)
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let files = try!(files);
|
|
|
|
|
2016-07-28 20:26:07 +02:00
|
|
|
files.into_iter()
|
2016-06-20 00:10:34 +02:00
|
|
|
.map(json::KeyFile::load)
|
|
|
|
.zip(paths.into_iter())
|
2016-07-28 20:26:07 +02:00
|
|
|
.map(|(file, path)| match file {
|
2016-08-03 17:58:22 +02:00
|
|
|
Ok(file) => Ok((path.clone(), SafeAccount::from_file(
|
2016-08-10 17:57:40 +02:00
|
|
|
file, Some(path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned())
|
2016-08-03 17:58:22 +02:00
|
|
|
))),
|
2016-07-28 20:26:07 +02:00
|
|
|
Err(err) => Err(Error::InvalidKeyFile(format!("{:?}: {}", path, err))),
|
|
|
|
})
|
|
|
|
.collect()
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl KeyDirectory for DiskDirectory {
|
|
|
|
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
|
|
|
let accounts = try!(self.files())
|
|
|
|
.into_iter()
|
|
|
|
.map(|(_, account)| account)
|
|
|
|
.collect();
|
|
|
|
Ok(accounts)
|
|
|
|
}
|
|
|
|
|
2016-07-25 10:45:45 +02:00
|
|
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
2016-06-20 00:10:34 +02:00
|
|
|
// transform account into key file
|
2016-07-25 10:45:45 +02:00
|
|
|
let keyfile: json::KeyFile = account.clone().into();
|
2016-06-20 00:10:34 +02:00
|
|
|
|
|
|
|
// build file path
|
2016-08-03 17:58:22 +02:00
|
|
|
let filename = account.filename.as_ref().cloned().unwrap_or_else(|| {
|
|
|
|
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
|
|
|
|
format!("UTC--{}Z--{:?}", timestamp, account.address)
|
2016-07-25 10:45:45 +02:00
|
|
|
});
|
|
|
|
|
2016-08-03 17:58:22 +02:00
|
|
|
// update account filename
|
|
|
|
let mut account = account;
|
|
|
|
account.filename = Some(filename.clone());
|
|
|
|
|
2016-07-25 10:45:45 +02:00
|
|
|
{
|
2016-08-03 17:58:22 +02:00
|
|
|
// Path to keyfile
|
|
|
|
let mut keyfile_path = self.path.clone();
|
|
|
|
keyfile_path.push(filename.as_str());
|
|
|
|
|
2016-07-25 10:45:45 +02:00
|
|
|
// save the file
|
2016-08-03 17:58:22 +02:00
|
|
|
let mut file = try!(fs::File::create(&keyfile_path));
|
2016-07-25 10:45:45 +02:00
|
|
|
try!(keyfile.write(&mut file).map_err(|e| Error::Custom(format!("{:?}", e))));
|
|
|
|
|
2016-08-03 17:58:22 +02:00
|
|
|
if let Err(_) = restrict_permissions_to_owner(keyfile_path.as_path()) {
|
|
|
|
fs::remove_file(keyfile_path).expect("Expected to remove recently created file");
|
2016-07-25 10:45:45 +02:00
|
|
|
return Err(Error::Io(io::Error::last_os_error()));
|
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2016-07-25 10:45:45 +02:00
|
|
|
Ok(account)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn remove(&self, address: &Address) -> Result<(), Error> {
|
|
|
|
// enumerate all entries in keystore
|
|
|
|
// and find entry with given address
|
|
|
|
let to_remove = try!(self.files())
|
|
|
|
.into_iter()
|
|
|
|
.find(|&(_, ref account)| &account.address == address);
|
|
|
|
|
|
|
|
// remove it
|
|
|
|
match to_remove {
|
|
|
|
None => Err(Error::InvalidAccount),
|
|
|
|
Some((path, _)) => fs::remove_file(path).map_err(From::from)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-03 17:58:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use std::{env, fs};
|
|
|
|
use super::DiskDirectory;
|
|
|
|
use dir::KeyDirectory;
|
|
|
|
use account::SafeAccount;
|
|
|
|
use ethkey::{Random, Generator};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_create_new_account() {
|
|
|
|
// given
|
|
|
|
let dir = env::temp_dir();
|
|
|
|
let keypair = Random.generate().unwrap();
|
|
|
|
let password = "hello world";
|
|
|
|
let directory = DiskDirectory::create(dir.clone()).unwrap();
|
|
|
|
|
|
|
|
// when
|
|
|
|
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
|
|
|
|
let res = directory.insert(account);
|
|
|
|
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert!(res.is_ok(), "Should save account succesfuly.");
|
|
|
|
assert!(res.unwrap().filename.is_some(), "Filename has been assigned.");
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
let _ = fs::remove_dir_all(dir);
|
|
|
|
}
|
|
|
|
}
|