@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/Cargo.lock
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "certutil"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
mod refresh;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
enum Cli {
|
||||||
|
Refresh(refresh::Cli),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let result = match cli {
|
||||||
|
Cli::Refresh(cli) => refresh::main(cli),
|
||||||
|
};
|
||||||
|
if let Err(err) = result {
|
||||||
|
eprintln!("error: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use clap::Parser;
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{Read, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[arg(long, default_value = "/share/ca-certificates")]
|
||||||
|
scan: Vec<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(long, default_value = "/var/config/ssl")]
|
||||||
|
ssl_config_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||||
|
_ = std::fs::create_dir_all(&cli.ssl_config_dir);
|
||||||
|
|
||||||
|
let certs_dir = cli.ssl_config_dir.join("certs");
|
||||||
|
let bundle_path = cli.ssl_config_dir.join("ca-certificates.crt");
|
||||||
|
|
||||||
|
let certs_obj_dir = certs_dir.with_added_extension(timestamp_ms());
|
||||||
|
std::fs::create_dir(&certs_obj_dir)
|
||||||
|
.map_err(|e| anyhow!("failed to create \"{}\": {e}", certs_obj_dir.display()))?;
|
||||||
|
|
||||||
|
let mut bundle_content = Vec::new();
|
||||||
|
|
||||||
|
for scan in cli.scan {
|
||||||
|
let read_dir = std::fs::read_dir(&scan)
|
||||||
|
.map_err(|e| anyhow!("failed to open \"{}\": {e}", scan.display()))?;
|
||||||
|
for it in read_dir {
|
||||||
|
let it = it.map_err(|e| anyhow!("failed to read \"{}\": {e}", scan.display()))?;
|
||||||
|
|
||||||
|
let content = match read_file_limited(&it.path(), 4096) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("warning: {err}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bundle_content.append(&mut content.clone());
|
||||||
|
bundle_content.push(b'\n');
|
||||||
|
|
||||||
|
let copy_dst = certs_obj_dir.join(it.file_name());
|
||||||
|
let mut copy_dst_file = File::options()
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.write(true)
|
||||||
|
.open(©_dst)
|
||||||
|
.map_err(|e| anyhow!("failed to open \"{}\": {e}", copy_dst.display()))?;
|
||||||
|
copy_dst_file
|
||||||
|
.write_all(&content)
|
||||||
|
.map_err(|e| anyhow!("failed to write \"{}\": {e}", copy_dst.display()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_openssl_rehash(&certs_obj_dir)?;
|
||||||
|
write_swp(&bundle_path, &bundle_content)?;
|
||||||
|
|
||||||
|
let old_certs_obj_dir = std::fs::read_link(&certs_dir).ok();
|
||||||
|
let certs_swp_dir = certs_dir.with_added_extension("swp");
|
||||||
|
std::os::unix::fs::symlink(&certs_obj_dir, &certs_swp_dir).map_err(|e| {
|
||||||
|
anyhow!(
|
||||||
|
"failed to create symbolic link at \"{}\": {e}",
|
||||||
|
certs_swp_dir.display(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
std::fs::rename(&certs_swp_dir, &certs_dir).map_err(|e| {
|
||||||
|
anyhow!(
|
||||||
|
"failed to rename \"{}\" to \"{}\": {e}",
|
||||||
|
certs_swp_dir.display(),
|
||||||
|
certs_dir.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(path) = old_certs_obj_dir {
|
||||||
|
_ = std::fs::remove_dir_all(&path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_file_limited(path: &Path, limit: u64) -> anyhow::Result<Vec<u8>> {
|
||||||
|
let mut buf = Vec::with_capacity(limit as _);
|
||||||
|
let file =
|
||||||
|
File::open(path).map_err(|e| anyhow!("failed to open \"{}\": {e}", path.display()))?;
|
||||||
|
file.take(limit).read_to_end(&mut buf)?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_swp(path: &Path, content: &[u8]) -> anyhow::Result<()> {
|
||||||
|
let swp = path.with_added_extension("swp");
|
||||||
|
std::fs::write(&swp, content)
|
||||||
|
.map_err(|e| anyhow!("failed to write \"{}\": {e}", swp.display()))?;
|
||||||
|
std::fs::rename(&swp, path).map_err(|e| {
|
||||||
|
anyhow!(
|
||||||
|
"failed to rename \"{}\" to \"{}\": {e}",
|
||||||
|
swp.display(),
|
||||||
|
path.display(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_openssl() -> String {
|
||||||
|
"openssl".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_openssl_rehash(path: &Path) -> anyhow::Result<()> {
|
||||||
|
let openssl_status = std::process::Command::new(find_openssl())
|
||||||
|
.arg("rehash")
|
||||||
|
.arg(&path)
|
||||||
|
.spawn()
|
||||||
|
.map(|mut x| x.wait())
|
||||||
|
.flatten()
|
||||||
|
.map_err(|e| anyhow!("failed to run \"{}\": {e}", find_openssl()))?;
|
||||||
|
if openssl_status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"openssl rehash failed, see the messages above to get more information"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp_ms() -> String {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_millis()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user