Files
peazyrsa/src/common.rs

226 lines
6.2 KiB
Rust

use anyhow::{anyhow, Context, Result};
use async_stream::stream;
use clap::Parser;
use encoding::{label::encoding_from_whatwg_label, EncoderTrap};
use std::{
collections::BTreeMap,
fmt::Display,
path::{Path, PathBuf},
str::FromStr,
};
use tokio::{
fs::{self, File},
io::{AsyncBufReadExt, BufReader},
};
use futures_core::stream::Stream;
pub(crate) type VarsMap = BTreeMap<String, String>;
#[derive(Debug, Clone, PartialEq)]
pub enum OpenSSLProviderArg {
Internal,
ExternalBin(String),
}
impl FromStr for OpenSSLProviderArg {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"internal" => Ok(OpenSSLProviderArg::Internal),
x => Ok(OpenSSLProviderArg::ExternalBin(x.to_string())),
}
}
}
impl Display for OpenSSLProviderArg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OpenSSLProviderArg::ExternalBin(x) => write!(f, "{}", x),
_ => write!(f, "internal"),
}
}
}
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub(crate) struct Args {
/// new client name
pub(crate) name: String,
/// pki directory
#[arg(short, long)]
pub(crate) directory: Option<String>,
/// client email
#[arg(short, long)]
pub(crate) email: Option<String>,
/// files encoding
#[arg(short = 'c', long)]
pub(crate) encoding: Option<String>,
/// keys subdir
#[arg(long, default_value = "keys")]
pub(crate) keys_dir: String,
/// config subdir
#[arg(long, default_value = "config")]
pub(crate) config_dir: String,
/// valid days
#[arg(long, default_value = "3650")]
pub(crate) days: u32,
/// openssl binary or (internal)
#[arg(long, short, default_value = "internal")]
pub(crate) openssl: OpenSSLProviderArg,
/// template file
#[arg(long, default_value = "template.ovpn")]
pub(crate) template_file: String,
}
pub(crate) struct AppConfig {
pub(crate) encoding: String,
pub(crate) req_days: u32,
pub(crate) keys_subdir: String,
pub(crate) config_subdir: String,
pub(crate) template_file: String,
pub(crate) openssl_default_cnf: String,
pub(crate) openssl_cnf_env: String,
pub(crate) ca_filename: String,
pub(crate) default_email_domain: String,
pub(crate) openssl: OpenSSLProviderArg,
pub(crate) base_directory: String,
pub(crate) email: String,
pub(crate) name: String,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
encoding: "cp866".into(),
req_days: 30650,
keys_subdir: "keys".into(),
config_subdir: "config".into(),
template_file: "template.ovpn".into(),
openssl_default_cnf: "openssl-1.0.0.cnf".into(),
openssl_cnf_env: "KEY_CONFIG".into(),
ca_filename: "ca.crt".into(),
default_email_domain: "example.com".into(),
openssl: OpenSSLProviderArg::Internal,
base_directory: ".".into(),
email: "name@example.com".into(),
name: "user".into(),
}
}
}
impl From<&Args> for AppConfig {
fn from(args: &Args) -> Self {
let defaults = Self::default();
let base_directory = args
.directory
.as_ref()
.unwrap_or(&defaults.base_directory)
.clone();
let email = args.email.clone().unwrap_or(format!(
"{}@{}",
&args.name,
defaults.default_email_domain.clone()
));
let encoding = if let Some(enc) = args.encoding.clone() {
enc.to_string()
} else {
defaults.encoding.clone()
};
let name = args.name.clone();
let openssl = args.openssl.clone();
let template_file = args.template_file.clone();
let req_days = args.days;
let keys_subdir = args.keys_dir.clone();
let config_subdir = args.config_dir.clone();
Self {
base_directory,
email,
encoding,
name,
openssl,
template_file,
req_days,
keys_subdir,
config_subdir,
..defaults
}
}
}
pub(crate) async fn is_file_exist(filepath: &PathBuf) -> bool {
let metadata = tokio::fs::metadata(&filepath).await;
if metadata.is_err() {
return false;
}
if !metadata.unwrap().is_file() {
return false;
}
true
}
pub(crate) async fn read_file<'a, S, P>(filepath: P, encoding: S) -> Result<String>
where
S: AsRef<str> + std::cmp::PartialEq<&'a str>,
P: AsRef<Path>,
{
let filepath = PathBuf::from(filepath.as_ref());
if encoding == "utf8" {
return Ok(fs::read_to_string(filepath).await?);
}
let enc = encoding_from_whatwg_label(encoding.as_ref()).ok_or(anyhow!("encoding not found"))?;
let bytes = fs::read(filepath).await?;
enc.decode(&bytes, encoding::DecoderTrap::Ignore)
.map_err(|_| anyhow!("could not read file"))
}
pub(crate) async fn write_file(filepath: &PathBuf, text: String, encoding: &str) -> Result<()> {
if encoding == "utf8" {
return Ok(fs::write(filepath, text).await?);
}
let enc = encoding_from_whatwg_label(encoding).ok_or(anyhow!("encoding not found"))?;
let mut bytes = Vec::new();
enc.encode_to(&text, EncoderTrap::Ignore, &mut bytes)
.map_err(|_| anyhow!("can't encode"))?;
fs::write(filepath, bytes).await.context("can't write file")
}
pub(crate) async fn read_file_by_lines(
filepath: &PathBuf,
encoding: &str,
) -> Result<Box<dyn Stream<Item = String>>> {
Ok(if encoding == "utf8" {
let f = File::open(filepath).await?;
let reader = BufReader::new(f);
let mut lines = reader.lines();
Box::new(stream! {
while let Ok(Some(line)) = lines.next_line().await {
yield line
}
})
} else {
let text = read_file(filepath, encoding).await?;
Box::new(stream! {
for line in text.lines() {
yield line.to_string()
}
})
})
}