mirror of https://github.com/breard-r/acmed.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
7.9 KiB
295 lines
7.9 KiB
use crate::hooks::{self, FileStorageHookData, Hook, HookEnvData, HookType};
|
|
use crate::logs::HasLogger;
|
|
use crate::template::render_template;
|
|
use acme_common::b64_encode;
|
|
use acme_common::crypto::{KeyPair, X509Certificate};
|
|
use acme_common::error::Error;
|
|
use serde::Serialize;
|
|
use std::collections::HashMap;
|
|
use std::fmt;
|
|
use std::fs::{File, OpenOptions};
|
|
use std::io::{Read, Write};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[cfg(target_family = "unix")]
|
|
use std::os::unix::fs::OpenOptionsExt;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct FileManager {
|
|
pub account_name: String,
|
|
pub account_directory: String,
|
|
pub crt_name: String,
|
|
pub crt_name_format: String,
|
|
pub crt_directory: String,
|
|
pub crt_key_type: String,
|
|
pub cert_file_mode: u32,
|
|
pub cert_file_owner: Option<String>,
|
|
pub cert_file_group: Option<String>,
|
|
pub pk_file_mode: u32,
|
|
pub pk_file_owner: Option<String>,
|
|
pub pk_file_group: Option<String>,
|
|
pub hooks: Vec<Hook>,
|
|
pub env: HashMap<String, String>,
|
|
}
|
|
|
|
impl HasLogger for FileManager {
|
|
fn warn(&self, msg: &str) {
|
|
log::warn!("{}: {}", self, msg);
|
|
}
|
|
|
|
fn info(&self, msg: &str) {
|
|
log::info!("{}: {}", self, msg);
|
|
}
|
|
|
|
fn debug(&self, msg: &str) {
|
|
log::debug!("{}: {}", self, msg);
|
|
}
|
|
|
|
fn trace(&self, msg: &str) {
|
|
log::trace!("{}: {}", self, msg);
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for FileManager {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let s = if !self.crt_name.is_empty() {
|
|
format!("certificate \"{}_{}\"", self.crt_name, self.crt_key_type)
|
|
} else {
|
|
format!("account \"{}\"", self.account_name)
|
|
};
|
|
write!(f, "{}", s)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum FileType {
|
|
Account,
|
|
PrivateKey,
|
|
Certificate,
|
|
}
|
|
|
|
impl fmt::Display for FileType {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let s = match self {
|
|
FileType::Account => "account",
|
|
FileType::PrivateKey => "pk",
|
|
FileType::Certificate => "crt",
|
|
};
|
|
write!(f, "{}", s)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Serialize)]
|
|
pub struct CertFileFormat {
|
|
pub ext: String,
|
|
pub file_type: String,
|
|
pub key_type: String,
|
|
pub name: String,
|
|
}
|
|
|
|
fn get_file_full_path(
|
|
fm: &FileManager,
|
|
file_type: FileType,
|
|
) -> Result<(String, String, PathBuf), Error> {
|
|
let base_path = match file_type {
|
|
FileType::Account => &fm.account_directory,
|
|
FileType::PrivateKey => &fm.crt_directory,
|
|
FileType::Certificate => &fm.crt_directory,
|
|
};
|
|
let file_name = match file_type {
|
|
FileType::Account => format!(
|
|
"{account}.{file_type}.{ext}",
|
|
account = b64_encode(&fm.account_name),
|
|
file_type = file_type,
|
|
ext = "bin"
|
|
),
|
|
FileType::PrivateKey | FileType::Certificate => {
|
|
let fmt_data = CertFileFormat {
|
|
key_type: fm.crt_key_type.to_string(),
|
|
ext: "pem".into(),
|
|
file_type: file_type.to_string(),
|
|
name: fm.crt_name.to_owned(),
|
|
};
|
|
render_template(&fm.crt_name_format, &fmt_data)?
|
|
}
|
|
};
|
|
let mut path = PathBuf::from(&base_path);
|
|
path.push(&file_name);
|
|
Ok((base_path.to_string(), file_name, path))
|
|
}
|
|
|
|
fn get_file_path(fm: &FileManager, file_type: FileType) -> Result<PathBuf, Error> {
|
|
let (_, _, path) = get_file_full_path(fm, file_type)?;
|
|
Ok(path)
|
|
}
|
|
|
|
fn read_file(fm: &FileManager, path: &Path) -> Result<Vec<u8>, Error> {
|
|
fm.trace(&format!("reading file {:?}", path));
|
|
let mut file =
|
|
File::open(path).map_err(|e| Error::from(e).prefix(&path.display().to_string()))?;
|
|
let mut contents = vec![];
|
|
file.read_to_end(&mut contents)?;
|
|
Ok(contents)
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn set_owner(fm: &FileManager, path: &Path, file_type: FileType) -> Result<(), Error> {
|
|
let (uid, gid) = match file_type {
|
|
FileType::Certificate => (fm.cert_file_owner.to_owned(), fm.cert_file_group.to_owned()),
|
|
FileType::PrivateKey => (fm.pk_file_owner.to_owned(), fm.pk_file_group.to_owned()),
|
|
FileType::Account => {
|
|
// The account file does not need to be accessible to users other different from the current one.
|
|
return Ok(());
|
|
}
|
|
};
|
|
let uid = match uid {
|
|
Some(u) => {
|
|
if u.bytes().all(|b| b.is_ascii_digit()) {
|
|
let raw_uid = u
|
|
.parse::<u32>()
|
|
.map_err(|_| Error::from("unable to parse the UID"))?;
|
|
let nix_uid = nix::unistd::Uid::from_raw(raw_uid);
|
|
Some(nix_uid)
|
|
} else {
|
|
let user = nix::unistd::User::from_name(&u)?;
|
|
user.map(|u| u.uid)
|
|
}
|
|
}
|
|
None => None,
|
|
};
|
|
let gid = match gid {
|
|
Some(g) => {
|
|
if g.bytes().all(|b| b.is_ascii_digit()) {
|
|
let raw_gid = g
|
|
.parse::<u32>()
|
|
.map_err(|_| Error::from("unable to parse the GID"))?;
|
|
let nix_gid = nix::unistd::Gid::from_raw(raw_gid);
|
|
Some(nix_gid)
|
|
} else {
|
|
let grp = nix::unistd::Group::from_name(&g)?;
|
|
grp.map(|g| g.gid)
|
|
}
|
|
}
|
|
None => None,
|
|
};
|
|
match uid {
|
|
Some(u) => fm.trace(&format!("{:?}: setting the uid to {}", path, u.as_raw())),
|
|
None => fm.trace(&format!("{:?}: uid unchanged", path)),
|
|
};
|
|
match gid {
|
|
Some(g) => fm.trace(&format!("{:?}: setting the gid to {}", path, g.as_raw())),
|
|
None => fm.trace(&format!("{:?}: gid unchanged", path)),
|
|
};
|
|
match nix::unistd::chown(path, uid, gid) {
|
|
Ok(_) => Ok(()),
|
|
Err(e) => Err(format!("{}", e).into()),
|
|
}
|
|
}
|
|
|
|
fn write_file(fm: &FileManager, file_type: FileType, data: &[u8]) -> Result<(), Error> {
|
|
let (file_directory, file_name, path) = get_file_full_path(fm, file_type.clone())?;
|
|
let mut hook_data = FileStorageHookData {
|
|
file_name,
|
|
file_directory,
|
|
file_path: path.to_owned(),
|
|
env: HashMap::new(),
|
|
};
|
|
hook_data.set_env(&fm.env);
|
|
let is_new = !path.is_file();
|
|
|
|
if is_new {
|
|
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreCreate)?;
|
|
} else {
|
|
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreEdit)?;
|
|
}
|
|
|
|
fm.trace(&format!("writing file {:?}", path));
|
|
let mut file = if cfg!(unix) {
|
|
let mut options = OpenOptions::new();
|
|
options.mode(match &file_type {
|
|
FileType::Certificate => fm.cert_file_mode,
|
|
FileType::PrivateKey => fm.pk_file_mode,
|
|
FileType::Account => crate::DEFAULT_ACCOUNT_FILE_MODE,
|
|
});
|
|
options
|
|
.write(true)
|
|
.create(true)
|
|
.open(&path)
|
|
.map_err(|e| Error::from(e).prefix(&path.display().to_string()))?
|
|
} else {
|
|
File::create(&path).map_err(|e| Error::from(e).prefix(&path.display().to_string()))?
|
|
};
|
|
file.write_all(data)
|
|
.map_err(|e| Error::from(e).prefix(&path.display().to_string()))?;
|
|
if cfg!(unix) {
|
|
set_owner(fm, &path, file_type).map_err(|e| e.prefix(&path.display().to_string()))?;
|
|
}
|
|
|
|
if is_new {
|
|
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostCreate)?;
|
|
} else {
|
|
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostEdit)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_account_data(fm: &FileManager) -> Result<Vec<u8>, Error> {
|
|
let path = get_file_path(fm, FileType::Account)?;
|
|
read_file(fm, &path)
|
|
}
|
|
|
|
pub fn set_account_data(fm: &FileManager, data: &[u8]) -> Result<(), Error> {
|
|
write_file(fm, FileType::Account, data)
|
|
}
|
|
|
|
pub fn get_keypair(fm: &FileManager) -> Result<KeyPair, Error> {
|
|
let path = get_file_path(fm, FileType::PrivateKey)?;
|
|
let raw_key = read_file(fm, &path)?;
|
|
let key = KeyPair::from_pem(&raw_key)?;
|
|
Ok(key)
|
|
}
|
|
|
|
pub fn set_keypair(fm: &FileManager, key_pair: &KeyPair) -> Result<(), Error> {
|
|
let data = key_pair.private_key_to_pem()?;
|
|
write_file(fm, FileType::PrivateKey, &data)
|
|
}
|
|
|
|
pub fn get_certificate(fm: &FileManager) -> Result<X509Certificate, Error> {
|
|
let path = get_file_path(fm, FileType::Certificate)?;
|
|
let raw_crt = read_file(fm, &path)?;
|
|
let crt = X509Certificate::from_pem(&raw_crt)?;
|
|
Ok(crt)
|
|
}
|
|
|
|
pub fn write_certificate(fm: &FileManager, data: &[u8]) -> Result<(), Error> {
|
|
write_file(fm, FileType::Certificate, data)
|
|
}
|
|
|
|
fn check_files(fm: &FileManager, file_types: &[FileType]) -> bool {
|
|
for t in file_types.iter().cloned() {
|
|
let path = match get_file_path(fm, t) {
|
|
Ok(p) => p,
|
|
Err(_) => {
|
|
return false;
|
|
}
|
|
};
|
|
fm.trace(&format!(
|
|
"testing file path: {}",
|
|
path.to_str().unwrap_or_default()
|
|
));
|
|
if !path.is_file() {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
pub fn account_files_exists(fm: &FileManager) -> bool {
|
|
let file_types = vec![FileType::Account];
|
|
check_files(fm, &file_types)
|
|
}
|
|
|
|
pub fn certificate_files_exists(fm: &FileManager) -> bool {
|
|
let file_types = vec![FileType::PrivateKey, FileType::Certificate];
|
|
check_files(fm, &file_types)
|
|
}
|